Skip to content

Commit

Permalink
Additional rule options for object-literal-key-quotes (palantir#1733)
Browse files Browse the repository at this point in the history
  • Loading branch information
nchen63 authored and adidahiya committed Nov 18, 2016
1 parent 1657468 commit b858cae
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ v4.0.0
* [new-fixer] `ordered-imports` auto fixed (#1640)
* [new-fixer] `arrow-parens` auto fixed (#1731)
* [rule-change] `indent` rule now ignores template strings (#1611)
* [new-rule-option] `object-literal-key-quotes` adds the options `consistent` and `consistent-as-needed` (#1733)
* [enhancement] `--fix` option added to automatically fix selected rules (#1697)
* [enhancement] Updated recommend rules (#1717)
* [enhancement] `adjacent-overload-signatures` now works with classes, source files, modules, and namespaces (#1707)
Expand Down
6 changes: 4 additions & 2 deletions docs/_data/rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -925,12 +925,14 @@
"ruleName": "object-literal-key-quotes",
"description": "Enforces consistent object literal property quote style.",
"descriptionDetails": "\nObject literal property names can be defined in two ways: using literals or using strings.\nFor example, these two objects are equivalent:\n\nvar object1 = {\n property: true\n};\n\nvar object2 = {\n \"property\": true\n};\n\nIn many cases, it doesn’t matter if you choose to use an identifier instead of a string\nor vice-versa. Even so, you might decide to enforce a consistent style in your code.\n\nThis rules lets you enforce consistent quoting of property names. Either they should always\nbe quoted (default behavior) or quoted only as needed (\"as-needed\").",
"optionsDescription": "\nPossible settings are:\n\n* `\"always\"`: Property names should always be quoted. (This is the default.)\n* `\"as-needed\"`: Only property names which require quotes may be quoted (e.g. those with spaces in them).\n\nFor ES6, computed property names (`{[name]: value}`) and methods (`{foo() {}}`) never need\nto be quoted.",
"optionsDescription": "\nPossible settings are:\n\n* `\"always\"`: Property names should always be quoted. (This is the default.)\n* `\"as-needed\"`: Only property names which require quotes may be quoted (e.g. those with spaces in them).\n* `\"consistent\"`: Property names should either all be quoted or unquoted.\n* `\"consistent-as-needed\"`: If any property name requires quotes, then all properties must be quoted. Otherwise, no \nproperty names may be quoted.\n\nFor ES6, computed property names (`{[name]: value}`) and methods (`{foo() {}}`) never need\nto be quoted.",
"options": {
"type": "string",
"enum": [
"always",
"as-needed"
"as-needed",
"consistent",
"consistent-as-needed"
]
},
"optionExamples": [
Expand Down
9 changes: 8 additions & 1 deletion docs/rules/object-literal-key-quotes/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

* `"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).
* `"consistent"`: Property names should either all be quoted or unquoted.
* `"consistent-as-needed"`: If any property name requires quotes, then all properties must be quoted. Otherwise, no
property names may be quoted.

For ES6, computed property names (`{[name]: value}`) and methods (`{foo() {}}`) never need
to be quoted.
Expand All @@ -33,6 +36,8 @@
enum:
- always
- as-needed
- consistent
- consistent-as-needed
optionExamples:
- '[true, "as-needed"]'
- '[true, "always"]'
Expand All @@ -45,7 +50,9 @@
"type": "string",
"enum": [
"always",
"as-needed"
"as-needed",
"consistent",
"consistent-as-needed"
]
}
---
2 changes: 1 addition & 1 deletion src/configs/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const rules = {
"no-use-before-declare": false,
"no-var-keyword": true,
"no-var-requires": true,
"object-literal-key-quotes": [true, "as-needed"],
"object-literal-key-quotes": [true, "consistent-as-needed"],
"object-literal-shorthand": true,
"object-literal-sort-keys": true,
"one-line": [true,
Expand Down
87 changes: 66 additions & 21 deletions src/rules/objectLiteralKeyQuotesRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export class Rule extends Lint.Rules.AbstractRule {
* \`"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).
* \`"consistent"\`: Property names should either all be quoted or unquoted.
* \`"consistent-as-needed"\`: If any property name requires quotes, then all properties must be quoted. Otherwise, no
property names may be quoted.
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.
enum: ["always", "as-needed", "consistent", "consistent-as-needed"],
// TODO: eslint supports "keywords", "unnecessary" and "numbers" options.
},
optionExamples: ["[true, \"as-needed\"]", "[true, \"always\"]"],
Expand All @@ -43,6 +45,7 @@ export class Rule extends Lint.Rules.AbstractRule {
};
/* tslint:enable:object-literal-sort-keys */

public static INCONSISTENT_PROPERTY = `All property names in this object literal must be consistently quoted or unquoted.`;
public static UNNEEDED_QUOTES = (name: string) => `Unnecessarily quoted property '${name}' found.`;
public static UNQUOTED_PROPERTY = (name: string) => `Unquoted property '${name}' found.`;

Expand All @@ -57,10 +60,20 @@ const IDENTIFIER_NAME_REGEX = /^(?:[\$A-Z_a-z])*$/;

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

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

interface IObjectLiteralState {
// potential failures for properties that have quotes but don't need them
quotesNotNeededProperties: Lint.RuleFailure[];
// potential failures for properties that don't have quotes
unquotedProperties: Lint.RuleFailure[];
// whether or not any of the properties require quotes
hasQuotesNeededProperty: boolean;
}

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

constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
super(sourceFile, options);
Expand All @@ -70,27 +83,59 @@ class ObjectLiteralKeyQuotesWalker extends Lint.RuleWalker {

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)));
}
if (name.kind !== ts.SyntaxKind.StringLiteral &&
name.kind !== ts.SyntaxKind.ComputedPropertyName) {

const errorText = Rule.UNQUOTED_PROPERTY(name.getText());
this.currentState.unquotedProperties.push(this.createFailure(name.getStart(), name.getWidth(), errorText));
}
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)) {
const errorText = Rule.UNNEEDED_QUOTES(property);
const failure = this.createFailure(stringNode.getStart(), stringNode.getWidth(), errorText);
this.currentState.quotesNotNeededProperties.push(failure);
} else {
this.currentState.hasQuotesNeededProperty = true;
}
}

super.visitPropertyAssignment(node);
}

public visitObjectLiteralExpression(node: ts.ObjectLiteralExpression) {
let state: IObjectLiteralState = {
hasQuotesNeededProperty: false,
quotesNotNeededProperties: [],
unquotedProperties: [],
};
// a nested object literal should store its parent state to restore when finished
let previousState = this.currentState;
this.currentState = state;

super.visitObjectLiteralExpression(node);

if (this.mode === "always" || (this.mode === "consistent-as-needed" && state.hasQuotesNeededProperty)) {
for (const failure of state.unquotedProperties) {
this.addFailure(failure);
}
} else if (this.mode === "as-needed" || (this.mode === "consistent-as-needed" && !state.hasQuotesNeededProperty)) {
for (const failure of state.quotesNotNeededProperties) {
this.addFailure(failure);
}
} else if (this.mode === "consistent") {
const hasQuotedProperties = state.hasQuotesNeededProperty || state.quotesNotNeededProperties.length > 0;
const hasUnquotedProperties = state.unquotedProperties.length > 0;
if (hasQuotedProperties && hasUnquotedProperties) {
this.addFailure(this.createFailure(node.getStart(), 1, Rule.INCONSISTENT_PROPERTY));
}
}

this.currentState = previousState;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

const o = {
'hello': 123,
~~~~~~~ [Unnecessarily quoted property 'hello' found.]
"bye": 45,
~~~~~ [Unnecessarily quoted property 'bye' found.]
};
const v = {
"hello": 123,
~~~~~~~ [Unnecessarily quoted property 'hello' found.]
"bye": 45,
~~~~~ [Unnecessarily quoted property 'bye' found.]
};
const s = {
hello: 123,
bye: 45,
};
const r = {
"hello": 123,
"bye-bye": 45,
};
const p = {
hello: 123,
"bye": 45,
~~~~~ [Unnecessarily quoted property 'bye' found.]
};
const q = {
hello: 123,
~~~~~ [Unquoted property 'hello' found.]
"bye-bye": 45,
};
const t = {
hello: 123,
bye-bye: 45,
nested: {
"bird": 2,
~~~~~~ [Unnecessarily quoted property 'bird' found.]
egg: 3,
}
};
const u = {
hello: 123,
bye: 45,
nested: {
"bird": 1,
~~~~~~ [Unnecessarily quoted property 'bird' found.]
"egg": 2,
~~~~~ [Unnecessarily quoted property 'egg' found.]
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

const o = {
'hello': 123,
~~~~~~~ [Unnecessarily quoted property 'hello' found.]
"bye": 45,
~~~~~ [Unnecessarily quoted property 'bye' found.]
};
const v = {
"hello": 123,
~~~~~~~ [Unnecessarily quoted property 'hello' found.]
"bye": 45,
~~~~~ [Unnecessarily quoted property 'bye' found.]
};
const s = {
hello: 123,
bye: 45,
};
const r = {
"hello": 123,
"bye-bye": 45,
};
const p = {
hello: 123,
"bye": 45,
~~~~~ [Unnecessarily quoted property 'bye' found.]
};
const q = {
hello: 123,
~~~~~ [Unquoted property 'hello' found.]
"bye-bye": 45,
};
const t = {
hello: 123,
bye-bye: 45,
nested: {
"bird": 2,
~~~~~~ [Unnecessarily quoted property 'bird' found.]
egg: 3,
}
};
const u = {
hello: 123,
bye: 45,
nested: {
"bird": 1,
~~~~~~ [Unnecessarily quoted property 'bird' found.]
"egg": 2,
~~~~~ [Unnecessarily quoted property 'egg' found.]
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"rules": {
"object-literal-key-quotes": [true, "consistent-as-needed"]
},
"jsRules": {
"object-literal-key-quotes": [true, "consistent-as-needed"]
}
}
44 changes: 44 additions & 0 deletions test/rules/object-literal-key-quotes/consistent/test.js.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

const o = {
'hello': 123,
"bye": 45,
};
const v = {
"hello": 123,
"bye": 45,
};
const s = {
hello: 123,
bye: 45,
};
const r = {
"hello": 123,
"bye-bye": 45,
};
const p = {
~ [All property names in this object literal must be consistently quoted or unquoted.]
hello: 123,
"bye": 45,
};
const q = {
~ [All property names in this object literal must be consistently quoted or unquoted.]
hello: 123,
"bye-bye": 45,
};
const t = {
hello: 123,
bye-bye: 45,
nested: {
~ [All property names in this object literal must be consistently quoted or unquoted.]
"bird": 2,
egg: 3,
}
};
const u = {
hello: 123,
bye: 45,
nested: {
"bird": 1,
"egg": 2,
}
};
44 changes: 44 additions & 0 deletions test/rules/object-literal-key-quotes/consistent/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

const o = {
'hello': 123,
"bye": 45,
};
const v = {
"hello": 123,
"bye": 45,
};
const s = {
hello: 123,
bye: 45,
};
const r = {
"hello": 123,
"bye-bye": 45,
};
const p = {
~ [All property names in this object literal must be consistently quoted or unquoted.]
hello: 123,
"bye": 45,
};
const q = {
~ [All property names in this object literal must be consistently quoted or unquoted.]
hello: 123,
"bye-bye": 45,
};
const t = {
hello: 123,
bye-bye: 45,
nested: {
~ [All property names in this object literal must be consistently quoted or unquoted.]
"bird": 2,
egg: 3,
}
};
const u = {
hello: 123,
bye: 45,
nested: {
"bird": 1,
"egg": 2,
}
};
Loading

0 comments on commit b858cae

Please sign in to comment.