Skip to content

Commit

Permalink
Core object-literal-key-quotes rule (palantir#1364)
Browse files Browse the repository at this point in the history
* Core quote-props rule

* Tweaks for code review

* rename
  • Loading branch information
danvk authored and Nina Hartmann committed Aug 31, 2016
1 parent 6776fc3 commit 139cbfa
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/ruleLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export function loadRules(ruleConfiguration: {[name: string]: any},
Try upgrading TSLint and/or ensuring that you have all necessary custom rules installed.
If TSLint was recently upgraded, you may have old rules configured which need to be cleaned up.
`;

throw new Error(ERROR_MESSAGE);
} else {
return rules;
Expand Down
95 changes: 95 additions & 0 deletions src/rules/objectLiteralKeyQuotesRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as Lint from "../lint";
import * as ts from "typescript";

export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "object-literal-key-quotes",
description: "Enforces consistent object literal property quote style.",
descriptionDetails: Lint.Utils.dedent`
Object literal property names can be defined in two ways: using literals or using strings.
For example, these two objects are equivalent:
var object1 = {
property: true
};
var object2 = {
"property": true
};
In many cases, it doesn’t matter if you choose to use an identifier instead of a string
or vice-versa. Even so, you might decide to enforce a consistent style in your code.
This rules lets you enforce consistent quoting of property names. Either they should always
be quoted (default behavior) or quoted only as needed ("as-needed").`,
optionsDescription: Lint.Utils.dedent`
Possible settings are:
* \`"always"\`: Property names should always be quoted. (This is the default.)
* \`"as-needed"\`: Only property names which require quotes may be quoted (e.g. those with spaces in them).
For ES6, computed property names (\`{[name]: value}\`) and methods (\`{foo() {}}\`) never need
to be quoted.`,
options: {
type: "string",
enum: ["always", "as-needed"],
// TODO: eslint also supports "consistent", "consistent-as-needed" modes.
// TODO: eslint supports "keywords", "unnecessary" and "numbers" options.
},
optionExamples: ["[true, \"as-needed\"]", "[true, \"always\"]"],
type: "style",
};
/* tslint:enable:object-literal-sort-keys */

public static UNNEEDED_QUOTES = (name: string) => `Unnecessarily quoted property '${name}' found.`;
public static UNQUOTED_PROPERTY = (name: string) => `Unquoted property '${name}' found.`;

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

// This is simplistic. See https://mothereff.in/js-properties for the gorey details.
const IDENTIFIER_NAME_REGEX = /^(?:[\$A-Z_a-z])*$/;

const NUMBER_REGEX = /^[0-9]+$/;

type QuotesMode = "always" | "as-needed";

class ObjectLiteralKeyQuotesWalker extends Lint.RuleWalker {
private mode: QuotesMode;

constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
super(sourceFile, options);

this.mode = this.getOptions()[0] || "always";
}

public visitPropertyAssignment(node: ts.PropertyAssignment) {
const name = node.name;
if (this.mode === "always") {
if (name.kind !== ts.SyntaxKind.StringLiteral &&
name.kind !== ts.SyntaxKind.ComputedPropertyName) {
this.addFailure(this.createFailure(name.getStart(), name.getWidth(),
Rule.UNQUOTED_PROPERTY(name.getText())));
}
} else if (this.mode === "as-needed") {
if (name.kind === ts.SyntaxKind.StringLiteral) {
// Check if the quoting is necessary.
const stringNode = name as ts.StringLiteral;
const property = stringNode.text;

const isIdentifier = IDENTIFIER_NAME_REGEX.test(property);
const isNumber = NUMBER_REGEX.test(property);
if (isIdentifier || (isNumber && Number(property).toString() === property)) {
this.addFailure(this.createFailure(stringNode.getStart(), stringNode.getWidth(),
Rule.UNNEEDED_QUOTES(property)));
}
}
}

super.visitPropertyAssignment(node);
}
}
1 change: 1 addition & 0 deletions src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"rules/noUseBeforeDeclareRule.ts",
"rules/noVarKeywordRule.ts",
"rules/noVarRequiresRule.ts",
"rules/objectLiteralKeyQuotesRule.ts",
"rules/objectLiteralSortKeysRule.ts",
"rules/oneLineRule.ts",
"rules/oneVariablePerDeclarationRule.ts",
Expand Down
23 changes: 23 additions & 0 deletions test/rules/object-literal-key-quotes/always/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const o = {
'hello': 123,
goodbye: 234, // failure
~~~~~~~ [Unquoted property 'goodbye' found.]
"quote": 345,
"needs quote": 789,
"hyphens-need-quotes": null,
[computed]: 456,
123: "hello", // failure
~~~ [Unquoted property '123' found.]
1e4: "something", // failure
~~~ [Unquoted property '1e4' found.]
.123: "float", // failure
~~~~ [Unquoted property '.123' found.]
'123': 'numbers do not need quotes',
'010': 'but this one does.',
'.123': 'as does this one',
fn() { return },
true: 0, // failure
~~~~ [Unquoted property 'true' found.]
"0x0": 0,
"true": 0,
};
5 changes: 5 additions & 0 deletions test/rules/object-literal-key-quotes/always/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"object-literal-key-quotes": [true, "always"]
}
}
22 changes: 22 additions & 0 deletions test/rules/object-literal-key-quotes/as-needed/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const o = {
'hello': 123, // failure
~~~~~~~ [Unnecessarily quoted property 'hello' found.]
goodbye: 234,
"quote": 345, // failure
~~~~~~~ [Unnecessarily quoted property 'quote' found.]
"needs quote": 789,
"hyphens-need-quotes": null,
[computed]: 456,
123: "hello",
1e4: "something",
.123: "float",
'123': 'numbers do not need quotes', // failure
~~~~~ [Unnecessarily quoted property '123' found.]
'010': 'but this one does.',
'.123': 'as does this one',
fn() { return },
true: 0,
"0x0": 0,
"true": 0, // failure
~~~~~~ [Unnecessarily quoted property 'true' found.]
};
5 changes: 5 additions & 0 deletions test/rules/object-literal-key-quotes/as-needed/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"object-literal-key-quotes": [true, "as-needed"]
}
}
1 change: 1 addition & 0 deletions test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"../src/rules/noUseBeforeDeclareRule.ts",
"../src/rules/noVarKeywordRule.ts",
"../src/rules/noVarRequiresRule.ts",
"../src/rules/objectLiteralKeyQuotesRule.ts",
"../src/rules/objectLiteralSortKeysRule.ts",
"../src/rules/oneLineRule.ts",
"../src/rules/oneVariablePerDeclarationRule.ts",
Expand Down

0 comments on commit 139cbfa

Please sign in to comment.