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

[new-rule] no-restricted-globals #3824

Merged
merged 12 commits into from
Feb 23, 2019
1 change: 1 addition & 0 deletions src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const rules = {
"no-namespace": true,
"no-non-null-assertion": true,
"no-reference": true,
"no-restricted-globals": true,
"no-this-assignment": true,
"no-var-requires": true,
"only-arrow-functions": true,
Expand Down
97 changes: 97 additions & 0 deletions src/rules/noRestrictedGlobalsRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* @license
* Copyright 2014 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 ts from "typescript";

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

export class Rule extends Lint.Rules.TypedRule {
/* tslint:disable:object-literal-sort-keys */
public static metadata: Lint.IRuleMetadata = {
ruleName: "no-restricted-globals",
description: "Disallow specific global variables.",
descriptionDetails: Lint.Utils.dedent`
Disallowing usage of specific global variables can be useful if you want to allow
a set of global variables by enabling an environment, but still want to disallow
some of those. For instance, early Internet Explorer versions exposed the current
DOM event as a global variable event, but using this variable has been considered
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: escape the "event" in "global variable event" with code to make it a little more clear.

global variable \`event\`, but using

as a bad practice for a long time. Restricting this will make sure this variable
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: just "considered a bad practice"

isn’t used in browser code.
`,
optionsDescription: "This rule takes a list of strings, where each string is a global to be restricted.",
options: {
type: "list",
items: {type: "string"},
},
optionExamples: [
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
[
true,
"name",
"length",
"event",
],
],
type: "functionality",
typescriptOnly: false,
requiresTypeInfo: true,
};
/* tslint:enable:object-literal-sort-keys */

public static FAILURE_STRING(name: string) {
return `Unexpected global variable '${name}'. Use local parameter instead.`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Or a local variable, right?

Use a local parameter or variable instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated. thanks

}

public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
const bannedGlobals = new Set(this.ruleArguments as string[]);
return this.applyWithFunction(sourceFile, walk, bannedGlobals, program.getTypeChecker());
}
}

function walk(ctx: Lint.WalkContext<Set<string>>, checker: ts.TypeChecker): void {
return ts.forEachChild(ctx.sourceFile, function recur(node: ts.Node): void {
switch (node.kind) {
Zzzen marked this conversation as resolved.
Show resolved Hide resolved
case ts.SyntaxKind.TypeReference:
// Ignore types.
return;
case ts.SyntaxKind.PropertyAccessExpression:
// Ignore `y` in `x.y`, but recurse to `x`.
return recur((node as ts.PropertyAccessExpression).expression);
case ts.SyntaxKind.Identifier:
return checkIdentifier(node as ts.Identifier);
default:
Zzzen marked this conversation as resolved.
Show resolved Hide resolved
return ts.forEachChild(node, recur);
}
});

function checkIdentifier(node: ts.Identifier): void {
if (!ctx.options.has(node.text)) {
return;
}

const symbol = checker.getSymbolAtLocation(node);
const declarations = symbol === undefined ? undefined : symbol.declarations;
if (declarations === undefined || declarations.length === 0) {
return;
}

const declaredInLibDom = declarations.some((decl) => decl.getSourceFile().fileName.endsWith(".d.ts"));
Copy link
Contributor

Choose a reason for hiding this comment

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

What if you declare something in an externals.d.ts file, or define your own lib.dom.d.ts with either that name or my-lib.d.ts?

Or what about a project that uses namespaces and has a const name = "HA!"; somewhere?

Maybe the rule should get the list of files TS is compiling with (can we do that?) along with its compiler options, and add lib.d.ts-like files as per lib and --noLib compiler settings?
Maybe the rule should keep a default list of regular expressions to match names against, like lib*.d.ts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMO, custom definition files should be considered the same as official lib.dom.d.ts. There is no need to get compiler options.


if (declaredInLibDom) {
ctx.addFailureAtNode(node, Rule.FAILURE_STRING(node.text));
}
}
}
15 changes: 15 additions & 0 deletions test/rules/no-restricted-globals/test.ts.lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function foo(evt: Event) {
let length: number = 1;
console.log(length);

Event.target;

event.target;
~~~~~ [Unexpected global variable 'event'. Use local parameter instead.]
}

console.log(length);
~~~~~~ [Unexpected global variable 'length'. Use local parameter instead.]

import { name } from "./foo";
console.log(name);
8 changes: 8 additions & 0 deletions test/rules/no-restricted-globals/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"dom"
]
}
}
10 changes: 10 additions & 0 deletions test/rules/no-restricted-globals/tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"rules": {
"no-restricted-globals": [
Zzzen marked this conversation as resolved.
Show resolved Hide resolved
true,
"name",
"length",
"event"
]
}
}