From 28d190264017dbaa29f2ab218f73b623143cd1af Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 15 Oct 2022 00:02:29 +0900 Subject: [PATCH] feat: `no-implicit-globals` supports `exported` block comment (#16343) * feat: support `"exported"` block comments test: add valid tests test: add invalid tests * docs: mention about `exported` comment docs: add correct example * chore: use `variable.eslintExported` directly * docs: add `:::` to close correct example * fix: fix `exported` comment for multiple variables * fix: add more invalid tests for global variable leaks --- docs/src/rules/no-implicit-globals.md | 17 ++ lib/linter/linter.js | 1 + lib/rules/no-implicit-globals.js | 5 + tests/lib/rules/no-implicit-globals.js | 379 ++++++++++++++++++++++++- 4 files changed, 401 insertions(+), 1 deletion(-) diff --git a/docs/src/rules/no-implicit-globals.md b/docs/src/rules/no-implicit-globals.md index 15c03861d55..37871f0ea0c 100644 --- a/docs/src/rules/no-implicit-globals.md +++ b/docs/src/rules/no-implicit-globals.md @@ -5,6 +5,7 @@ rule_type: suggestion related_rules: - no-undef - no-global-assign +- no-unused-vars further_reading: - https://benalman.com/news/2010/11/immediately-invoked-function-expression/ - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Undeclared_var @@ -255,6 +256,22 @@ window.MyGlobalFunction = (function() { ::: +### exported + +You can use `/* exported variableName */` block comments in the same way as in [`no-unused-vars`](./no-unused-vars). See the [`no-unused-vars` exported section](./no-unused-vars#exported) for details. + +Examples of **correct** code for `/* exported variableName */` operation: + +::: correct + +```js +/* exported global_var */ + +var global_var = 42; +``` + +::: + ## When Not To Use It In the case of a browser script, if you want to be able to explicitly declare variables and functions in the global scope, diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 16059623472..764ea5f5795 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -213,6 +213,7 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena if (variable) { variable.eslintUsed = true; + variable.eslintExported = true; } }); diff --git a/lib/rules/no-implicit-globals.js b/lib/rules/no-implicit-globals.js index 934630ea070..c2cdd03f2ce 100644 --- a/lib/rules/no-implicit-globals.js +++ b/lib/rules/no-implicit-globals.js @@ -77,6 +77,11 @@ module.exports = { return; } + // Variables exported by "exported" block comments + if (variable.eslintExported) { + return; + } + variable.defs.forEach(def => { const defNode = def.node; diff --git a/tests/lib/rules/no-implicit-globals.js b/tests/lib/rules/no-implicit-globals.js index 348ef6feb55..412ba766134 100644 --- a/tests/lib/rules/no-implicit-globals.js +++ b/tests/lib/rules/no-implicit-globals.js @@ -412,7 +412,92 @@ ruleTester.run("no-implicit-globals", rule, { // This rule doesn't disallow assignments to properties of readonly globals "Array.from = 1;", "Object['assign'] = 1;", - "/*global foo:readonly*/ foo.bar = 1;" + "/*global foo:readonly*/ foo.bar = 1;", + + + //------------------------------------------------------------------------------ + // exported + //------------------------------------------------------------------------------ + + // `var` and functions + "/* exported foo */ var foo = 'foo';", + "/* exported foo */ function foo() {}", + { + code: "/* exported foo */ function *foo() {}", + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported foo */ async function foo() {}", + parserOptions: { ecmaVersion: 2017 } + }, + { + code: "/* exported foo */ async function *foo() {}", + parserOptions: { ecmaVersion: 2018 } + }, + "/* exported foo */ var foo = function() {};", + "/* exported foo */ var foo = function foo() {};", + { + code: "/* exported foo */ var foo = function*() {};", + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported foo */ var foo = function *foo() {};", + parserOptions: { ecmaVersion: 2015 } + }, + "/* exported foo, bar */ var foo = 1, bar = 2;", + + + // `const`, `let` and `class` + { + code: "/* exported a */ const a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a */ let a;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a */ let a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported A */ class A {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b */ const a = 1; const b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b */ const a = 1, b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b */ let a, b = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b, C */ const a = 1; let b; class C {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b, c */ const [a, b, ...c] = [];", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "/* exported a, b, c */ let { a, foo: b, bar: { c } } = {};", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 } + } ], invalid: [ @@ -1241,6 +1326,298 @@ ruleTester.run("no-implicit-globals", rule, { type: "VariableDeclarator" } ] + }, + + //------------------------------------------------------------------------------ + // exported + //------------------------------------------------------------------------------ + + // `var` and `function` + { + code: "/* exported bar */ var foo = 'text';", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ function foo() {}", + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ function *foo() {}", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ async function foo() {}", + parserOptions: { ecmaVersion: 2017 }, + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ async function *foo() {}", + parserOptions: { ecmaVersion: 2018 }, + errors: [ + { + message: functionMessage, + type: "FunctionDeclaration" + } + ] + }, + { + code: "/* exported bar */ var foo = function() {};", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = function foo() {};", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = function*() {};", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = function *foo() {};", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported bar */ var foo = 1, bar = 2;", + errors: [ + { + message: varMessage, + type: "VariableDeclarator" + } + ] + }, + + // `let`, `const` and `class` + { + code: "/* exported b */ const a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported b */ let a;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported b */ let a = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported B */ class A {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: classMessage, + type: "ClassDeclaration" + } + ] + }, + { + code: "/* exported a */ const a = 1; const b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ const a = 1, b = 2;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ let a, b = 1;", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ const a = 1; let b; class C {}", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + }, + { + message: classMessage, + type: "ClassDeclaration" + } + ] + }, + { + code: "/* exported a */ const [a, b, ...c] = [];", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: constMessage, + type: "VariableDeclarator" + }, + { + message: constMessage, + type: "VariableDeclarator" + } + ] + }, + { + code: "/* exported a */ let { a, foo: b, bar: { c } } = {};", + options: [{ lexicalBindings: true }], + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: letMessage, + type: "VariableDeclarator" + }, + { + message: letMessage, + type: "VariableDeclarator" + } + ] + }, + + // Global variable leaks + { + code: "/* exported foo */ foo = 1", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ foo = function() {};", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ foo = function*() {};", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ window.foo = function() { bar = 1; }", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ (function() {}(foo = 1));", + errors: [ + { + message: leakMessage, + type: "AssignmentExpression" + } + ] + }, + { + code: "/* exported foo */ for (foo in {});", + errors: [ + { + message: leakMessage, + type: "ForInStatement" + } + ] + }, + { + code: "/* exported foo */ for (foo of []);", + parserOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: leakMessage, + type: "ForOfStatement" + } + ] } ] });