-
Notifications
You must be signed in to change notification settings - Fork 889
[enhancement] object-literal-shorthand / config to disallow shorthand #3268
Changes from 3 commits
b1a3c8b
524bd38
c0ea4ff
248b8d1
7b52c56
fbdf337
46a4c59
1b6ac0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,46 +15,90 @@ | |
* limitations under the License. | ||
*/ | ||
|
||
import { getChildOfKind, hasModifier, isFunctionExpression, isIdentifier, isPropertyAssignment } from "tsutils"; | ||
import { | ||
getChildOfKind, | ||
hasModifier, | ||
isFunctionExpression, | ||
isIdentifier, | ||
isMethodDeclaration, | ||
isPropertyAssignment, | ||
isShorthandPropertyAssignment, | ||
} from "tsutils"; | ||
import * as ts from "typescript"; | ||
import * as Lint from "../index"; | ||
|
||
const OPTION_NEVER = "never"; | ||
|
||
interface Options { | ||
allowShorthandAssignments: boolean; | ||
} | ||
|
||
export class Rule extends Lint.Rules.AbstractRule { | ||
/* tslint:disable:object-literal-sort-keys */ | ||
public static metadata: Lint.IRuleMetadata = { | ||
ruleName: "object-literal-shorthand", | ||
description: "Enforces use of ES6 object literal shorthand when possible.", | ||
description: "Enforces/disallows use of ES6 object literal shorthand.", | ||
hasFix: true, | ||
optionsDescription: "Not configurable.", | ||
options: null, | ||
optionExamples: [true], | ||
optionsDescription: Lint.Utils.dedent` | ||
If the \'never\' option is provided, any shorthand object literal syntax will cause a failure.`, | ||
options: { | ||
type: "string", | ||
enum: [OPTION_NEVER], | ||
}, | ||
optionExamples: [true, [true, OPTION_NEVER]], | ||
type: "style", | ||
typescriptOnly: false, | ||
}; | ||
/* tslint:enable:object-literal-sort-keys */ | ||
|
||
public static LONGHAND_PROPERTY = "Expected property shorthand in object literal "; | ||
public static LONGHAND_METHOD = "Expected method shorthand in object literal "; | ||
public static SHORTHAND_ASSIGNMENT = "Shorthand property assignments have been disallowed."; | ||
|
||
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { | ||
return this.applyWithFunction(sourceFile, walk); | ||
return this.applyWithFunction(sourceFile, walk, { | ||
allowShorthandAssignments: this.ruleArguments.indexOf("never") === -1, | ||
}); | ||
} | ||
} | ||
|
||
function walk(ctx: Lint.WalkContext<void>) { | ||
function walk(ctx: Lint.WalkContext<Options>) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The branches for both options don't share any code. Consider splitting into two separate functions. That will make it easier to understand and avoids unnecessary CPU cycles. |
||
const { options: { allowShorthandAssignments } } = ctx; | ||
allowShorthandAssignments ? enforceShorthand(ctx) : disallowShorthand(ctx); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can also do that in this.applyWithFunction(sourceFile, this.ruleArguments.indexOf(OPTION_NEVER) === -1 ? enforceShorthand : disallowShorthand); |
||
} | ||
|
||
function disallowShorthand(ctx: Lint.WalkContext<Options>): void { | ||
return ts.forEachChild(ctx.sourceFile, function cb(node): void { | ||
if (isShorthandAssignment(node)) { | ||
ctx.addFailureAtNode( | ||
node, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looking at the tests, I think it's better to just show the error at There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Went ahead and updated the error messages for shorthand-only too |
||
Rule.SHORTHAND_ASSIGNMENT, | ||
fixShorthandToLonghand(node as ts.ShorthandPropertyAssignment | ts.MethodDeclaration), | ||
); | ||
return; | ||
} | ||
return ts.forEachChild(node, cb); | ||
}); | ||
} | ||
|
||
function enforceShorthand(ctx: Lint.WalkContext<Options>): void { | ||
return ts.forEachChild(ctx.sourceFile, function cb(node): void { | ||
if (isPropertyAssignment(node)) { | ||
if (node.name.kind === ts.SyntaxKind.Identifier && | ||
if ( | ||
node.name.kind === ts.SyntaxKind.Identifier && | ||
isIdentifier(node.initializer) && | ||
node.name.text === node.initializer.text) { | ||
node.name.text === node.initializer.text | ||
) { | ||
ctx.addFailureAtNode( | ||
node, | ||
`${Rule.LONGHAND_PROPERTY}('{${node.name.text}}').`, | ||
Lint.Replacement.deleteFromTo(node.name.end, node.end), | ||
); | ||
} else if (isFunctionExpression(node.initializer) && | ||
// allow named function expressions | ||
node.initializer.name === undefined) { | ||
} else if ( | ||
isFunctionExpression(node.initializer) && | ||
// allow named function expressions | ||
node.initializer.name === undefined | ||
) { | ||
const [name, fix] = handleLonghandMethod(node.name, node.initializer, ctx.sourceFile); | ||
ctx.addFailureAtNode(node, `${Rule.LONGHAND_METHOD}('{${name}() {...}}').`, fix); | ||
} | ||
|
@@ -63,9 +107,29 @@ function walk(ctx: Lint.WalkContext<void>) { | |
}); | ||
} | ||
|
||
function fixShorthandToLonghand(node: ts.ShorthandPropertyAssignment | ts.MethodDeclaration): Lint.Fix { | ||
let replacementText = isMethodDeclaration(node) | ||
? ": function" | ||
: `: ${node.name.getText()}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can use |
||
|
||
replacementText = isGeneratorFunction(node) | ||
? `${replacementText}*` | ||
: replacementText; | ||
|
||
const fixes: Lint.Fix = [Lint.Replacement.appendText(node.name.end, replacementText)]; | ||
if (isGeneratorFunction(node)) { | ||
const asteriskPosition = (node as ts.MethodDeclaration).asteriskToken!.getStart(); | ||
fixes.push(Lint.Replacement.replaceFromTo(asteriskPosition, asteriskPosition + 1, "")); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs special handling for before you do that, just a suggestion to simplify the fix: replace the name with the |
||
return fixes; | ||
} | ||
|
||
function handleLonghandMethod(name: ts.PropertyName, initializer: ts.FunctionExpression, sourceFile: ts.SourceFile): [string, Lint.Fix] { | ||
const nameStart = name.getStart(sourceFile); | ||
let fix: Lint.Fix = Lint.Replacement.deleteFromTo(name.end, getChildOfKind(initializer, ts.SyntaxKind.OpenParenToken)!.pos); | ||
let fix: Lint.Fix = Lint.Replacement.deleteFromTo( | ||
name.end, | ||
getChildOfKind(initializer, ts.SyntaxKind.OpenParenToken)!.pos, | ||
); | ||
let prefix = ""; | ||
if (initializer.asteriskToken !== undefined) { | ||
prefix = "*"; | ||
|
@@ -78,3 +142,16 @@ function handleLonghandMethod(name: ts.PropertyName, initializer: ts.FunctionExp | |
} | ||
return [prefix + sourceFile.text.substring(nameStart, name.end), fix]; | ||
} | ||
|
||
function isGeneratorFunction(node: ts.ShorthandPropertyAssignment | ts.MethodDeclaration): boolean { | ||
return isMethodDeclaration(node) && node.asteriskToken !== undefined; | ||
} | ||
|
||
function isShorthandAssignment(node: ts.Node): boolean { | ||
return ( | ||
isShorthandPropertyAssignment(node) || | ||
(isMethodDeclaration(node) && node.parent !== undefined | ||
? node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression | ||
: false) | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"rules": { | ||
"object-literal-shorthand": [true, "always"] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
const bad = { | ||
w: function() {}, | ||
x: function*() {}, | ||
[y]: function() {}, | ||
z: z, | ||
nest: { | ||
nestBad: function() {}, | ||
nextGood: function(prop: string): void {} | ||
} | ||
}; | ||
|
||
const good = { | ||
w: function() {}, | ||
x: function *() {}, | ||
[y]: function() {} | ||
}; | ||
|
||
const arrows = { | ||
x: (y) => y // this is OK. | ||
}; | ||
|
||
const namedFunctions = { | ||
x: function y() {} // named function expressions are also OK. | ||
}; | ||
|
||
const quotes = { | ||
"foo-bar": function() {}, | ||
"foo-bar": function() {} | ||
}; | ||
|
||
const extraCases = { | ||
x: x, | ||
a: 123, | ||
b: "hello", | ||
c: 'c', | ||
["a" + "nested"]: { | ||
x: x | ||
} | ||
}; | ||
|
||
const asyncFn = { | ||
foo: async function() {}, | ||
bar: async function*() {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
const bad = { | ||
w() {}, | ||
~~~~~~ [OBJECT_LITERAL_DISALLOWED] | ||
*x() {}, | ||
~~~~~~~ [OBJECT_LITERAL_DISALLOWED] | ||
[y]() {}, | ||
~~~~~~~~ [OBJECT_LITERAL_DISALLOWED] | ||
z, | ||
~ [OBJECT_LITERAL_DISALLOWED] | ||
nest: { | ||
nestBad() {}, | ||
~~~~~~~~~~~~ [OBJECT_LITERAL_DISALLOWED] | ||
nextGood: function(prop: string): void {} | ||
} | ||
}; | ||
|
||
const good = { | ||
w: function() {}, | ||
x: function *() {}, | ||
[y]: function() {} | ||
}; | ||
|
||
const arrows = { | ||
x: (y) => y // this is OK. | ||
}; | ||
|
||
const namedFunctions = { | ||
x: function y() {} // named function expressions are also OK. | ||
}; | ||
|
||
const quotes = { | ||
"foo-bar": function() {}, | ||
"foo-bar"() {} | ||
~~~~~~~~~~~~~~ [OBJECT_LITERAL_DISALLOWED] | ||
}; | ||
|
||
const extraCases = { | ||
x, | ||
~ [OBJECT_LITERAL_DISALLOWED] | ||
a: 123, | ||
b: "hello", | ||
c: 'c', | ||
["a" + "nested"]: { | ||
x: x | ||
} | ||
}; | ||
|
||
const asyncFn = { | ||
foo: async function() {}, | ||
bar: async function*() {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs a test for async shorthand |
||
} | ||
[OBJECT_LITERAL_DISALLOWED]: Shorthand property assignments have been disallowed. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a test case with class methods. these should be ignored by the rule There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean class members which instantiate object literals with shorthand props? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean regular method declarations inside a class. They are ignored by the rule. I just want a test to ensure this won't regress. class C {
doStuff() {} // shouldn't be an error
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh of course. Thanks |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"rules": { | ||
"object-literal-shorthand": [true, "never"] | ||
} | ||
} | ||
|
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the current implementation
"always"
is not a valid option.