Skip to content

Commit

Permalink
[enhancement] object-literal-shorthand / config to disallow shorthand (
Browse files Browse the repository at this point in the history
  • Loading branch information
aervin_ authored and HyphnKnight committed Apr 9, 2018
1 parent 0f29468 commit 7a9579c
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 13 deletions.
96 changes: 88 additions & 8 deletions src/rules/objectLiteralShorthandRule.ts
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
@@ -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
@@ -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
@@ -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
@@ -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.

0 comments on commit 7a9579c

Please sign in to comment.