Skip to content

Commit

Permalink
Merge pull request palantir#416 from CalvinFernandez/sort-object-lite…
Browse files Browse the repository at this point in the history
…ral-keys

Added rule to sort object literal keys alphabetically
  • Loading branch information
gscshoyru committed Jun 3, 2015
2 parents 66cb6c0 + 4d60998 commit 17ae8b5
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 0 deletions.
60 changes: 60 additions & 0 deletions src/rules/sortObjectLiteralKeysRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2013 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export class Rule extends Lint.Rules.AbstractRule {
public static FAILURE_STRING = "unsorted key '";

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new SortedKeyWalker(sourceFile, this.getOptions()));
}
}

class SortedKeyWalker extends Lint.RuleWalker {
// Stacks are used to maintain state while
// recursing through nested object literals.
private lastSortedKeyStack: string[] = [];
private sortedStateStack: boolean[] = [];

public visitObjectLiteralExpression(node: ts.ObjectLiteralExpression): void {
this.lastSortedKeyStack.push(""); // Char code 0; every string should be >= to this
this.sortedStateStack.push(true); // Sorted state is always initially true
super.visitObjectLiteralExpression(node);
this.lastSortedKeyStack.pop();
this.sortedStateStack.pop();
}

public visitPropertyAssignment(node: ts.PropertyAssignment): void {
const sortedState = this.sortedStateStack[this.sortedStateStack.length - 1];
// Skip remainder of object literal scan if a previous key was found
// in an unsorted position. This ensures only one error is thrown at
// a time and keeps error output clean.
if (sortedState) {
const lastSortedKey = this.lastSortedKeyStack[this.lastSortedKeyStack.length - 1];
const keyNode = node.name;
if (keyNode.kind === ts.SyntaxKind.Identifier) {
const key = (<ts.Identifier> keyNode).text;
if (key < lastSortedKey) {
const failureString = Rule.FAILURE_STRING + key + "'";
this.addFailure(this.createFailure(keyNode.getStart(), keyNode.getWidth(), failureString));
this.sortedStateStack[this.sortedStateStack.length - 1] = false;
} else {
this.lastSortedKeyStack[this.lastSortedKeyStack.length - 1] = key;
}
}
}
super.visitPropertyAssignment(node);
}
}
79 changes: 79 additions & 0 deletions test/files/rules/sortedkey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
var passA = {
a: 1,
b: 2
};

var failA = {
b: 1,
a: 2
};

var passB = {
a: 1,
b: 2,
c: 3,
d: 4
};

var failB = {
c: 3,
a: 1,
b: 2,
d: 4
};

var passC = {
a: 1,
b: {
aa: 1,
bb: 2
}
};

var failC = {
a: 1,
b: {
bb: 2,
aa: 1
}
};

var passD = {
a: 1,
b: {
aa: 1,
bb: 2
},
c: 3
};

var failD = {
a: 1,
c: {
aa: 1,
bb: 2
},
b: 3
};

var passE = {};

var passF = {
asdf: [1, 2, 3],
sdfa: {}
};

var failF = {
sdfa: {},
asdf: [1, 2, 3]
};

var passG = {
asdfn: function () {},
sdafn: function () {}
};

var failG = {
sdafn: function () {},
asdfn: function () {}
};
41 changes: 41 additions & 0 deletions test/rules/sortObjectLiteralKeysRuleTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2013 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

describe("<sort-object-literal-keys>", () => {
it("forbids unsorted keys in object literals", () => {
const fileName = "rules/sortedkey.test.ts";
const SortedKeyRule = Lint.Test.getRule("sort-object-literal-keys");
const failureString = SortedKeyRule.FAILURE_STRING;

const actualFailures = Lint.Test.applyRuleOnFile(fileName, SortedKeyRule);
const createFailure1 = Lint.Test.createFailuresOnFile(fileName, failureString + "a'");
const createFailure2 = Lint.Test.createFailuresOnFile(fileName, failureString + "a'");
const createFailure3 = Lint.Test.createFailuresOnFile(fileName, failureString + "aa'");
const createFailure4 = Lint.Test.createFailuresOnFile(fileName, failureString + "b'");
const createFailure5 = Lint.Test.createFailuresOnFile(fileName, failureString + "asdf'");
const createFailure6 = Lint.Test.createFailuresOnFile(fileName, failureString + "asdfn'");
const expectedFailures = [
createFailure1([8, 5], [8, 6]),
createFailure2([20, 5], [20, 6]),
createFailure3([37, 9], [37, 11]),
createFailure4([56, 5], [56, 6]),
createFailure5([68, 5], [68, 9]),
createFailure6([78, 5], [78, 10])
];

Lint.Test.assertFailuresEqual(actualFailures, expectedFailures);
});
});

0 comments on commit 17ae8b5

Please sign in to comment.