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

[enhancement] object-literal-shorthand / config to disallow shorthand #3268

Merged
merged 8 commits into from
Oct 3, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export const rules = {
"no-unnecessary-type-assertion": true,
"number-literal-format": true,
"object-literal-key-quotes": [true, "consistent-as-needed"],
"object-literal-shorthand": true,
"object-literal-shorthand": [true, "always"],
Copy link
Contributor

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.

"one-line": [
true,
"check-catch",
Expand Down
103 changes: 90 additions & 13 deletions src/rules/objectLiteralShorthandRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can also do that in Rule#apply:

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,
Copy link
Contributor

Choose a reason for hiding this comment

The 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 node.name

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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);
}
Expand All @@ -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()}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use node.name.text here


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, ""));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs special handling for async methods as well.

before you do that, just a suggestion to simplify the fix: replace the name with the function keyword and insert the name plus the colon at the start of the declaration.
That way you don't have to handle the async keyword or the asterisk at all

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 = "*";
Expand All @@ -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)
);
}
5 changes: 5 additions & 0 deletions test/rules/object-literal-shorthand/always/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"object-literal-shorthand": [true, "always"]
}
}
44 changes: 44 additions & 0 deletions test/rules/object-literal-shorthand/never/test.ts.fix
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*() {}
}
52 changes: 52 additions & 0 deletions test/rules/object-literal-shorthand/never/test.ts.lint
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*() {}
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean class members which instantiate object literals with shorthand props?

Copy link
Contributor

Choose a reason for hiding this comment

The 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
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh of course. Thanks

6 changes: 6 additions & 0 deletions test/rules/object-literal-shorthand/never/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"rules": {
"object-literal-shorthand": [true, "never"]
}
}

5 changes: 0 additions & 5 deletions test/rules/object-literal-shorthand/tslint.json

This file was deleted.