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 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
96 changes: 88 additions & 8 deletions src/rules/objectLiteralShorthandRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,70 @@
* limitations under the License.
*/

import { getChildOfKind, hasModifier, isFunctionExpression, isIdentifier, isPropertyAssignment } from "tsutils";
import {
getChildOfKind,
getModifier,
hasModifier,
isFunctionExpression,
isIdentifier,
isMethodDeclaration,
isPropertyAssignment,
isShorthandPropertyAssignment,
} from "tsutils";
import * as ts from "typescript";
import * as Lint from "../index";
import * as Lint from "..";

const OPTION_NEVER = "never";

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,
this.ruleArguments.indexOf(OPTION_NEVER) === -1
? enforceShorthandWalker
: disallowShorthandWalker,
);
}
}

function walk(ctx: Lint.WalkContext<void>) {
function disallowShorthandWalker(ctx: Lint.WalkContext<void>) {
return ts.forEachChild(ctx.sourceFile, function cb(node): void {
if (isShorthandAssignment(node)) {
ctx.addFailureAtNode(
(node as ts.ShorthandPropertyAssignment).name,
Rule.SHORTHAND_ASSIGNMENT,
isMethodDeclaration(node)
? fixShorthandMethodDeclaration(node)
: fixShorthandPropertyAssignment(node as ts.ShorthandPropertyAssignment),
);
return;
}
return ts.forEachChild(node, cb);
});
}

function enforceShorthandWalker(ctx: Lint.WalkContext<void>) {
return ts.forEachChild(ctx.sourceFile, function cb(node): void {
if (isPropertyAssignment(node)) {
if (node.name.kind === ts.SyntaxKind.Identifier &&
Expand All @@ -63,6 +100,42 @@ function walk(ctx: Lint.WalkContext<void>) {
});
}

function fixShorthandMethodDeclaration(node: ts.MethodDeclaration): Lint.Fix {
const isGenerator = node.asteriskToken !== undefined;
const isAsync = hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword);
let
replacementStart =
(isAsync && node.modifiers !== undefined)
? getModifier(node, ts.SyntaxKind.AsyncKeyword)!.getStart()
: node.name.getStart();
replacementStart =
(isGenerator && !isAsync)
? node.asteriskToken!.getStart()
: node.name.getStart();

const fixes: Lint.Fix = [
Lint.Replacement.replaceFromTo(
replacementStart,
node.name.end,
`${node.name.getText()}:${isAsync ? " async" : ""} function${isGenerator ? "*" : ""}`,
),
];

if (isAsync) {
fixes.unshift(
Lint.Replacement.deleteFromTo(
getModifier(node, ts.SyntaxKind.AsyncKeyword)!.getStart(),
node.name.getStart(),
),
);
}
return fixes;
}

function fixShorthandPropertyAssignment(node: ts.ShorthandPropertyAssignment): Lint.Fix {
return Lint.Replacement.appendText(node.name.getStart(), `${node.name.text}: `);
}

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);
Expand All @@ -78,3 +151,10 @@ function handleLonghandMethod(name: ts.PropertyName, initializer: ts.FunctionExp
}
return [prefix + sourceFile.text.substring(nameStart, name.end), fix];
}

function isShorthandAssignment(node: ts.Node): boolean {
return (
isShorthandPropertyAssignment(node) ||
(isMethodDeclaration(node) && node.parent!.kind === ts.SyntaxKind.ObjectLiteralExpression)
);
}
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"]
}
}
52 changes: 52 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,52 @@
const asyncFn = {
f: async function() {
await some_promise;
},
fa: async function*() {
await some_promise;
}
};

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

export class ClassA extends ClassZ {
testMethod() {}
}
62 changes: 62 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,62 @@
const asyncFn = {
async f() {
~ [OBJECT_LITERAL_DISALLOWED]
await some_promise;
},
async* fa() {
~~ [OBJECT_LITERAL_DISALLOWED]
await some_promise;
}
};

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

export class ClassA extends ClassZ {
testMethod() {}
}
[OBJECT_LITERAL_DISALLOWED]: Shorthand property assignments have been disallowed.
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.