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

Commit 0f2a540

Browse files
pablobirukovadidahiya
authored andcommitted
Support granular config in 'object-literal-shorthand' (#4842)
1 parent 44947c5 commit 0f2a540

File tree

6 files changed

+260
-71
lines changed

6 files changed

+260
-71
lines changed

src/rules/objectLiteralShorthandRule.ts

Lines changed: 123 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,19 @@ import * as ts from "typescript";
2828

2929
import * as Lint from "..";
3030

31-
const OPTION_NEVER = "never";
31+
const OPTION_VALUE_NEVER = "never";
32+
const OPTION_KEY_PROPERTY = "property";
33+
const OPTION_KEY_METHOD = "method";
34+
35+
interface RawOptions {
36+
[OPTION_KEY_PROPERTY]?: "never" | "always";
37+
[OPTION_KEY_METHOD]?: "never" | "always";
38+
}
39+
40+
interface Options {
41+
enforceShorthandMethods: boolean;
42+
enforceShorthandProperties: boolean;
43+
}
3244

3345
export class Rule extends Lint.Rules.AbstractRule {
3446
/* tslint:disable:object-literal-sort-keys */
@@ -37,12 +49,43 @@ export class Rule extends Lint.Rules.AbstractRule {
3749
description: "Enforces/disallows use of ES6 object literal shorthand.",
3850
hasFix: true,
3951
optionsDescription: Lint.Utils.dedent`
40-
If the \'never\' option is provided, any shorthand object literal syntax will cause a failure.`,
52+
\`"always"\` assumed to be default option, thus with no options provided
53+
the rule enforces object literal methods and properties shorthands.
54+
With \`"never"\` option provided, any shorthand object literal syntax causes an error.
55+
56+
The rule can be configured in a more granular way.
57+
With \`{"property": "never"}\` provided (which is equivalent to \`{"property": "never", "method": "always"}\`),
58+
the rule only flags property shorthand assignments,
59+
and respectively with \`{"method": "never"}\` (equivalent to \`{"property": "always", "method": "never"}\`),
60+
the rule fails only on method shorthands.`,
4161
options: {
42-
type: "string",
43-
enum: [OPTION_NEVER],
62+
oneOf: [
63+
{
64+
type: "string",
65+
enum: [OPTION_VALUE_NEVER],
66+
},
67+
{
68+
type: "object",
69+
properties: {
70+
[OPTION_KEY_PROPERTY]: {
71+
type: "string",
72+
enum: [OPTION_VALUE_NEVER],
73+
},
74+
[OPTION_KEY_METHOD]: {
75+
type: "string",
76+
enum: [OPTION_VALUE_NEVER],
77+
},
78+
},
79+
minProperties: 1,
80+
maxProperties: 2,
81+
},
82+
],
4483
},
45-
optionExamples: [true, [true, OPTION_NEVER]],
84+
optionExamples: [
85+
true,
86+
[true, OPTION_VALUE_NEVER],
87+
[true, { [OPTION_KEY_PROPERTY]: OPTION_VALUE_NEVER }],
88+
],
4689
type: "style",
4790
typescriptOnly: false,
4891
};
@@ -52,74 +95,104 @@ export class Rule extends Lint.Rules.AbstractRule {
5295
public static LONGHAND_METHOD = "Expected method shorthand in object literal ";
5396
public static SHORTHAND_ASSIGNMENT = "Shorthand property assignments have been disallowed.";
5497

98+
public static getLonghandPropertyErrorMessage(nodeText: string) {
99+
return `Expected property shorthand in object literal ('${nodeText}').`;
100+
}
101+
public static getLonghandMethodErrorMessage(nodeText: string) {
102+
return `Expected method shorthand in object literal ('${nodeText}').`;
103+
}
104+
public static getDisallowedShorthandErrorMessage(options: Options) {
105+
if (options.enforceShorthandMethods && !options.enforceShorthandProperties) {
106+
return "Shorthand property assignments have been disallowed.";
107+
} else if (!options.enforceShorthandMethods && options.enforceShorthandProperties) {
108+
return "Shorthand method assignments have been disallowed.";
109+
}
110+
return "Shorthand property and method assignments have been disallowed.";
111+
}
112+
55113
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
56-
return this.applyWithFunction(
57-
sourceFile,
58-
this.ruleArguments.indexOf(OPTION_NEVER) === -1
59-
? enforceShorthandWalker
60-
: disallowShorthandWalker,
114+
return this.applyWithFunction(sourceFile, walk, this.parseOptions(this.ruleArguments));
115+
}
116+
117+
private parseOptions(options: Array<string | RawOptions>): Options {
118+
if (options.indexOf(OPTION_VALUE_NEVER) !== -1) {
119+
return {
120+
enforceShorthandMethods: false,
121+
enforceShorthandProperties: false,
122+
};
123+
}
124+
const optionsObject: RawOptions | undefined = options.find(
125+
(el: string | RawOptions): el is RawOptions =>
126+
typeof el === "object" &&
127+
(el[OPTION_KEY_PROPERTY] === OPTION_VALUE_NEVER ||
128+
el[OPTION_KEY_METHOD] === OPTION_VALUE_NEVER),
61129
);
130+
if (optionsObject !== undefined) {
131+
return {
132+
enforceShorthandMethods: !(optionsObject[OPTION_KEY_METHOD] === OPTION_VALUE_NEVER),
133+
enforceShorthandProperties: !(
134+
optionsObject[OPTION_KEY_PROPERTY] === OPTION_VALUE_NEVER
135+
),
136+
};
137+
} else {
138+
return {
139+
enforceShorthandMethods: true,
140+
enforceShorthandProperties: true,
141+
};
142+
}
62143
}
63144
}
64145

65-
function disallowShorthandWalker(ctx: Lint.WalkContext) {
146+
function walk(ctx: Lint.WalkContext<Options>) {
147+
const { enforceShorthandMethods, enforceShorthandProperties } = ctx.options;
66148
return ts.forEachChild(ctx.sourceFile, function cb(node): void {
67-
if (isShorthandPropertyAssignment(node)) {
149+
if (
150+
enforceShorthandProperties &&
151+
isPropertyAssignment(node) &&
152+
node.name.kind === ts.SyntaxKind.Identifier &&
153+
isIdentifier(node.initializer) &&
154+
node.name.text === node.initializer.text
155+
) {
156+
ctx.addFailureAtNode(
157+
node,
158+
Rule.getLonghandPropertyErrorMessage(`{${node.name.text}}`),
159+
Lint.Replacement.deleteFromTo(node.name.end, node.end),
160+
);
161+
} else if (
162+
enforceShorthandMethods &&
163+
isPropertyAssignment(node) &&
164+
isFunctionExpression(node.initializer) &&
165+
// allow named function expressions
166+
node.initializer.name === undefined
167+
) {
168+
const [name, fix] = handleLonghandMethod(node.name, node.initializer, ctx.sourceFile);
169+
ctx.addFailure(
170+
node.getStart(ctx.sourceFile),
171+
getChildOfKind(node.initializer, ts.SyntaxKind.OpenParenToken, ctx.sourceFile)!.pos,
172+
Rule.getLonghandMethodErrorMessage(`{${name}() {...}}`),
173+
fix,
174+
);
175+
} else if (!enforceShorthandProperties && isShorthandPropertyAssignment(node)) {
68176
ctx.addFailureAtNode(
69177
node.name,
70-
Rule.SHORTHAND_ASSIGNMENT,
178+
Rule.getDisallowedShorthandErrorMessage(ctx.options),
71179
Lint.Replacement.appendText(node.getStart(ctx.sourceFile), `${node.name.text}: `),
72180
);
73181
} else if (
182+
!enforceShorthandMethods &&
74183
isMethodDeclaration(node) &&
75184
node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression
76185
) {
77186
ctx.addFailureAtNode(
78187
node.name,
79-
Rule.SHORTHAND_ASSIGNMENT,
188+
Rule.getDisallowedShorthandErrorMessage(ctx.options),
80189
fixShorthandMethodDeclaration(node, ctx.sourceFile),
81190
);
82191
}
83192
return ts.forEachChild(node, cb);
84193
});
85194
}
86195

87-
function enforceShorthandWalker(ctx: Lint.WalkContext) {
88-
return ts.forEachChild(ctx.sourceFile, function cb(node): void {
89-
if (isPropertyAssignment(node)) {
90-
if (
91-
node.name.kind === ts.SyntaxKind.Identifier &&
92-
isIdentifier(node.initializer) &&
93-
node.name.text === node.initializer.text
94-
) {
95-
ctx.addFailureAtNode(
96-
node,
97-
`${Rule.LONGHAND_PROPERTY}('{${node.name.text}}').`,
98-
Lint.Replacement.deleteFromTo(node.name.end, node.end),
99-
);
100-
} else if (
101-
isFunctionExpression(node.initializer) &&
102-
// allow named function expressions
103-
node.initializer.name === undefined
104-
) {
105-
const [name, fix] = handleLonghandMethod(
106-
node.name,
107-
node.initializer,
108-
ctx.sourceFile,
109-
);
110-
ctx.addFailure(
111-
node.getStart(ctx.sourceFile),
112-
getChildOfKind(node.initializer, ts.SyntaxKind.OpenParenToken, ctx.sourceFile)!
113-
.pos,
114-
`${Rule.LONGHAND_METHOD}('{${name}() {...}}').`,
115-
fix,
116-
);
117-
}
118-
}
119-
return ts.forEachChild(node, cb);
120-
});
121-
}
122-
123196
function fixShorthandMethodDeclaration(node: ts.MethodDeclaration, sourceFile: ts.SourceFile) {
124197
const isGenerator = node.asteriskToken !== undefined;
125198
const isAsync = hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword);

test/rules/object-literal-shorthand/always/test.ts.lint

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
const bad = {
22
w: function() {},
3-
~~~~~~~~~~~ [Expected method shorthand in object literal ('{w() {...}}').]
3+
~~~~~~~~~~~ [LONGHAND_METHOD % ("('{w() {...}}')")]
44
x: function *() {},
5-
~~~~~~~~~~~~~ [Expected method shorthand in object literal ('{*x() {...}}').]
5+
~~~~~~~~~~~~~ [LONGHAND_METHOD % ("('{*x() {...}}')")]
66
[y]: function() {},
7-
~~~~~~~~~~~~~ [Expected method shorthand in object literal ('{[y]() {...}}').]
7+
~~~~~~~~~~~~~ [LONGHAND_METHOD % ("('{[y]() {...}}')")]
88
z: z
99
~~~~ [Expected property shorthand in object literal ('{z}').]
1010
};
@@ -26,7 +26,7 @@ const namedFunctions = {
2626

2727
const quotes = {
2828
"foo-bar": function() {},
29-
~~~~~~~~~~~~~~~~~~~ [Expected method shorthand in object literal ('{"foo-bar"() {...}}').]
29+
~~~~~~~~~~~~~~~~~~~ [LONGHAND_METHOD % ("('{\"foo-bar\"() {...}}')")]
3030
"foo-bar"() {}
3131
};
3232

@@ -43,11 +43,13 @@ const extraCases = {
4343

4444
const asyncFn = {
4545
foo: async function() {},
46-
~~~~~~~~~~~~~~~~~~~ [Expected method shorthand in object literal ('{async foo() {...}}').]
46+
~~~~~~~~~~~~~~~~~~~ [LONGHAND_METHOD % ("('{async foo() {...}}')")]
4747
bar: async function*() {}
48-
~~~~~~~~~~~~~~~~~~~~ [Expected method shorthand in object literal ('{async *bar() {...}}').]
48+
~~~~~~~~~~~~~~~~~~~~ [LONGHAND_METHOD % ("('{async *bar() {...}}')")]
4949
}
5050

5151
({foo: foo} = {foo: foo});
52-
~~~~~~~~ [Expected property shorthand in object literal ('{foo}').]
53-
~~~~~~~~ [Expected property shorthand in object literal ('{foo}').]
52+
~~~~~~~~ [LONGHAND_PROPERTY % ("('{foo}')")]
53+
~~~~~~~~ [LONGHAND_PROPERTY % ("('{foo}')")]
54+
[LONGHAND_METHOD]: Expected method shorthand in object literal %s.
55+
[LONGHAND_PROPERTY]: Expected property shorthand in object literal %s.

test/rules/object-literal-shorthand/never/test.ts.lint

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
const asyncFn = {
22
async f() {
3-
~ [OBJECT_LITERAL_DISALLOWED]
3+
~ [SHORTHAND_ASSIGNMENT]
44
await some_promise;
55
},
66
async* fa() {
7-
~~ [OBJECT_LITERAL_DISALLOWED]
7+
~~ [SHORTHAND_ASSIGNMENT]
88
await some_promise;
99
}
1010
};
1111

1212
const bad = {
1313
w() {
14-
~ [OBJECT_LITERAL_DISALLOWED]
14+
~ [SHORTHAND_ASSIGNMENT]
1515
const alsoBad = {
1616
bad,
17-
~~~ [OBJECT_LITERAL_DISALLOWED]
17+
~~~ [SHORTHAND_ASSIGNMENT]
1818
};
1919
},
2020
*x() {},
21-
~ [OBJECT_LITERAL_DISALLOWED]
21+
~ [SHORTHAND_ASSIGNMENT]
2222
[y]() {},
23-
~~~ [OBJECT_LITERAL_DISALLOWED]
23+
~~~ [SHORTHAND_ASSIGNMENT]
2424
z,
25-
~ [OBJECT_LITERAL_DISALLOWED]
25+
~ [SHORTHAND_ASSIGNMENT]
2626
nest: {
2727
nestBad() {},
28-
~~~~~~~ [OBJECT_LITERAL_DISALLOWED]
28+
~~~~~~~ [SHORTHAND_ASSIGNMENT]
2929
nextGood: function(prop: string): void {}
3030
}
3131
};
@@ -47,12 +47,12 @@ const namedFunctions = {
4747
const quotes = {
4848
"foo-bar": function() {},
4949
"foo-bar"() {}
50-
~~~~~~~~~ [OBJECT_LITERAL_DISALLOWED]
50+
~~~~~~~~~ [SHORTHAND_ASSIGNMENT]
5151
};
5252

5353
const extraCases = {
5454
x,
55-
~ [OBJECT_LITERAL_DISALLOWED]
55+
~ [SHORTHAND_ASSIGNMENT]
5656
a: 123,
5757
b: "hello",
5858
c: 'c',
@@ -66,7 +66,7 @@ export class ClassA extends ClassZ {
6666
}
6767

6868
({foo} = {foo});
69-
~~~ [OBJECT_LITERAL_DISALLOWED]
70-
~~~ [OBJECT_LITERAL_DISALLOWED]
69+
~~~ [SHORTHAND_ASSIGNMENT]
70+
~~~ [SHORTHAND_ASSIGNMENT]
7171

72-
[OBJECT_LITERAL_DISALLOWED]: Shorthand property assignments have been disallowed.
72+
[SHORTHAND_ASSIGNMENT]: Shorthand property and method assignments have been disallowed.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const badMethodsGoodProps = {
2+
w() {},
3+
*x() {},
4+
[y]() {},
5+
z: z
6+
};
7+
8+
const goodMethodsBadProps = {
9+
w() {},
10+
*x() {},
11+
[y]() {},
12+
z: z
13+
};
14+
15+
const arrows = {
16+
x: (y) => y // this is OK.
17+
};
18+
19+
const namedFunctions = {
20+
x: function y() {} // named function expressions are also OK.
21+
};
22+
23+
const quotes = {
24+
"foo-bar"() {},
25+
"foo-bar"() {}
26+
};
27+
28+
const extraCases = {
29+
x: x,
30+
a: 123,
31+
b: "hello",
32+
c: 'c',
33+
["a" + "nested"]: {
34+
x: x
35+
}
36+
};
37+
38+
const asyncFn = {
39+
async foo() {},
40+
async *bar() {}
41+
}
42+
43+
({foo: foo} = {foo: foo});
44+
({foo: foo} = {foo: foo});
45+

0 commit comments

Comments
 (0)