Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Core object-literal-key-quotes rule #1364

Merged
merged 3 commits into from
Jul 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -119,6 +119,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"]
}
}
3 changes: 2 additions & 1 deletion test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,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 Expand Up @@ -162,4 +163,4 @@
"rule-tester/testData.ts",
"rule-tester/utilsTests.ts"
]
}
}