Skip to content

Commit

Permalink
Added no-dynamic-delete rule (palantir#3573)
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Goldberg authored and HyphnKnight committed Apr 9, 2018
1 parent 880373e commit 5320ba0
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export const rules = {
true,
"check-parameters",
],
"no-dynamic-delete": true,
"no-empty": true,
"no-eval": true,
"no-floating-promises": true,
Expand Down
1 change: 1 addition & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ export function loadConfigurationFromPath(configFilePath?: string, originalFileP
}
} else {
rawConfigFile = require(resolvedConfigFilePath) as RawConfigFile;
// tslint:disable-next-line:no-dynamic-delete
delete (require.cache as { [key: string]: any })[resolvedConfigFilePath];
}

Expand Down
102 changes: 102 additions & 0 deletions src/rules/noDynamicDeleteRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* @license
* Copyright 2013 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as tsutils from "tsutils";
import * as ts from "typescript";

import * as Lint from "../index";

export class Rule extends Lint.Rules.AbstractRule {
public static metadata: Lint.IRuleMetadata = {
description: "Bans usage of the delete operator with computed key expressions.",
optionExamples: [true],
options: null,
optionsDescription: "Not configurable.",
rationale: "Deleting dynamically computed keys is dangerous and not well optimized.",
ruleName: "no-dynamic-delete",
type: "functionality",
typescriptOnly: false,
};

public static FAILURE_STRING = "Do not delete dynamically computed property keys.";

public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}

function walk(context: Lint.WalkContext<void>) {
function checkDeleteAccessExpression(node: ts.Expression | undefined): void {
if (node === undefined || !tsutils.isElementAccessExpression(node)) {
return;
}

const { argumentExpression } = node;
if (argumentExpression === undefined || isNecessaryDynamicAccess(argumentExpression)) {
return;
}

const start = argumentExpression.getStart(context.sourceFile) - 1;
const width = argumentExpression.getWidth() + 2;
let fix: Lint.Replacement | undefined;

if (tsutils.isPrefixUnaryExpression(argumentExpression)) {
const convertedOperand = convertUnaryOperand(argumentExpression);
if (convertedOperand !== undefined) {
fix = Lint.Replacement.replaceFromTo(start, start + width, `[${convertedOperand}]`);
}
} else if (tsutils.isStringLiteral(argumentExpression)) {
fix = Lint.Replacement.replaceFromTo(start, start + width, `.${argumentExpression.text}`);
}

context.addFailureAt(start, width, Rule.FAILURE_STRING, fix);
}

return ts.forEachChild(context.sourceFile, function callback(node: ts.Node): void {
if (isDeleteExpression(node)) {
checkDeleteAccessExpression(node.expression);
}

return ts.forEachChild(node, callback);
});
}

function convertUnaryOperand(node: ts.PrefixUnaryExpression) {
return tsutils.isNumericLiteral(node.operand)
? node.operand.text
: undefined;
}

function isDeleteExpression(node: ts.Node): node is ts.DeleteExpression {
return node.kind === ts.SyntaxKind.DeleteExpression;
}

function isNumberLike(node: ts.Node): boolean {
if (tsutils.isPrefixUnaryExpression(node)) {
return tsutils.isNumericLiteral(node.operand) && node.operator === ts.SyntaxKind.MinusToken;
}

return tsutils.isNumericLiteral(node);
}

function isNecessaryDynamicAccess(argumentExpression: ts.Expression): boolean {
if (isNumberLike(argumentExpression)) {
return true;
}

return tsutils.isStringLiteral(argumentExpression) && !tsutils.isValidPropertyAccess(argumentExpression.text);
}
19 changes: 19 additions & 0 deletions test/rules/no-dynamic-delete/test.ts.fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const container: { [i: string]: 0 } = {};

const getName = () => "";

delete container.aaa;
delete container["bb" + "b"];
delete container.ccc;
delete container.delete;
delete container.delete;
delete container[7];
delete container[-7];
delete container[7];
delete container[-Infinity];
delete container["-Infinity"];
delete container[+Infinity];
delete container["+Infinity"];
delete container[NaN];
delete container.NaN;
delete container[getName()];
29 changes: 29 additions & 0 deletions test/rules/no-dynamic-delete/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const container: { [i: string]: 0 } = {};

const getName = () => "";

delete container.aaa;
delete container["bb" + "b"];
~~~~~~~~~~~~ [0]
delete container["ccc"];
~~~~~~~ [0]
delete container.delete;
delete container["delete"];
~~~~~~~~~~ [0]
delete container[7];
delete container[-7];
delete container[+7];
~~~~ [0]
delete container[-Infinity];
~~~~~~~~~~~ [0]
delete container["-Infinity"];
delete container[+Infinity];
~~~~~~~~~~~ [0]
delete container["+Infinity"];
delete container[NaN];
~~~~~ [0]
delete container["NaN"];
~~~~~~~ [0]
delete container[getName()];
~~~~~~~~~~~ [0]
[0]: Do not delete dynamically computed property keys.
5 changes: 5 additions & 0 deletions test/rules/no-dynamic-delete/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"rules": {
"no-dynamic-delete": true
}
}

0 comments on commit 5320ba0

Please sign in to comment.