From c30a984fc18e90b0f704a609f01370df57e4ae45 Mon Sep 17 00:00:00 2001 From: Bryce Osterhaus Date: Thu, 21 Oct 2021 13:36:17 -0700 Subject: [PATCH 1/3] feat(eslint-plugin): add new rule to disallow 'use strict' in es modules --- projects/eslint-plugin/configs/general.js | 1 + .../docs/rules/no-use-strict-in-module.md | 40 ++++++++ projects/eslint-plugin/rules/general/index.js | 1 + .../lib/rules/no-use-strict-in-module.js | 57 ++++++++++++ .../lib/rules/no-use-strict-in-module.js | 92 +++++++++++++++++++ 5 files changed, 191 insertions(+) create mode 100644 projects/eslint-plugin/rules/general/docs/rules/no-use-strict-in-module.md create mode 100644 projects/eslint-plugin/rules/general/lib/rules/no-use-strict-in-module.js create mode 100644 projects/eslint-plugin/rules/general/tests/lib/rules/no-use-strict-in-module.js diff --git a/projects/eslint-plugin/configs/general.js b/projects/eslint-plugin/configs/general.js index ae8cc4a5ed..c9e7b0e051 100644 --- a/projects/eslint-plugin/configs/general.js +++ b/projects/eslint-plugin/configs/general.js @@ -107,6 +107,7 @@ const config = { '@liferay/no-it-should': 'error', '@liferay/no-length-jsx-expression': 'error', '@liferay/no-require-and-call': 'error', + '@liferay/no-use-strict-in-module': 'error', '@liferay/padded-test-blocks': 'error', '@liferay/ref-name-suffix': 'error', '@liferay/sort-import-destructures': 'error', diff --git a/projects/eslint-plugin/rules/general/docs/rules/no-use-strict-in-module.md b/projects/eslint-plugin/rules/general/docs/rules/no-use-strict-in-module.md new file mode 100644 index 0000000000..4c1286e058 --- /dev/null +++ b/projects/eslint-plugin/rules/general/docs/rules/no-use-strict-in-module.md @@ -0,0 +1,40 @@ +# Disallow 'use strict' in es modules (no-use-strict-in-module) + +You never need `'use strict'` in an ES module ([spec](http://www.ecma-international.org/ecma-262/6.0/#sec-strict-mode-code)). We can infer if file is an esmodule if it uses export/import syntax. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +'use strict'; + +export default () => {}; +``` + +```js +'use strict'; + +import foo from 'bar'; +``` + +Examples of **correct** code for this rule: + +```js +import foo from 'bar'; +//... +``` + +```js +//... +export default foo; +``` + +```js +//... +export foo; +``` + +## Further Reading + +- [Initial motivation for this rule](https://github.com/liferay/liferay-frontend-projects/issues/20). diff --git a/projects/eslint-plugin/rules/general/index.js b/projects/eslint-plugin/rules/general/index.js index 5e07a33a61..f89b25e25c 100644 --- a/projects/eslint-plugin/rules/general/index.js +++ b/projects/eslint-plugin/rules/general/index.js @@ -21,6 +21,7 @@ module.exports = { 'no-it-should': require('./lib/rules/no-it-should'), 'no-length-jsx-expression': require('./lib/rules/no-length-jsx-expression'), 'no-require-and-call': require('./lib/rules/no-require-and-call'), + 'no-use-strict-in-module': require('./lib/rules/no-use-strict-in-module'), 'padded-test-blocks': require('./lib/rules/padded-test-blocks'), 'ref-name-suffix': require('./lib/rules/ref-name-suffix'), 'sort-class-names': require('./lib/rules/sort-class-names'), diff --git a/projects/eslint-plugin/rules/general/lib/rules/no-use-strict-in-module.js b/projects/eslint-plugin/rules/general/lib/rules/no-use-strict-in-module.js new file mode 100644 index 0000000000..110948f404 --- /dev/null +++ b/projects/eslint-plugin/rules/general/lib/rules/no-use-strict-in-module.js @@ -0,0 +1,57 @@ +/** + * SPDX-FileCopyrightText: © 2021 Liferay, Inc. + * SPDX-License-Identifier: MIT + */ + +const message = `'use strict' is unnecessary inside of modules`; + +module.exports = { + create(context) { + const useStrictExpressions = []; + let esModule; + + const checkEsModule = () => { + if (!esModule) { + esModule = true; + } + }; + + return { + 'ExportDefaultDeclaration': checkEsModule, + 'ExportNamedDeclaration': checkEsModule, + 'ExpressionStatement'(node) { + if ( + node.expression.type === 'Literal' && + node.expression.value === 'use strict' + ) { + useStrictExpressions.push(node.expression); + } + }, + 'ImportDeclaration': checkEsModule, + 'ImportNamespaceSpecifier': checkEsModule, + 'Program:exit': () => { + if (esModule) { + useStrictExpressions.forEach((expression) => { + context.report({ + message, + node: expression, + }); + }); + } + }, + }; + }, + + meta: { + docs: { + category: 'Best Practices', + description: message, + recommended: false, + url: + 'https://github.com/liferay/liferay-frontend-projects/issues/20', + }, + fixable: 'code', + schema: [], + type: 'problem', + }, +}; diff --git a/projects/eslint-plugin/rules/general/tests/lib/rules/no-use-strict-in-module.js b/projects/eslint-plugin/rules/general/tests/lib/rules/no-use-strict-in-module.js new file mode 100644 index 0000000000..f7169d938f --- /dev/null +++ b/projects/eslint-plugin/rules/general/tests/lib/rules/no-use-strict-in-module.js @@ -0,0 +1,92 @@ +/** + * SPDX-FileCopyrightText: © 2017 Liferay, Inc. + * SPDX-License-Identifier: MIT + */ + +const MultiTester = require('../../../../../scripts/MultiTester'); +const rule = require('../../../lib/rules/no-use-strict-in-module'); + +const parserOptions = { + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, +}; + +const ruleTester = new MultiTester(parserOptions); + +ruleTester.run('no-use-strict-in-module', rule, { + invalid: [ + { + code: ` + 'use strict' + + import {test} from 'test'; + `, + errors: [ + { + message: `'use strict' is unnecessary inside of modules`, + type: 'Literal', + }, + ], + }, + { + code: ` + 'use strict' + + function test() { + 'use strict' + + return 'test'; + } + export default test; + `, + errors: [ + { + message: `'use strict' is unnecessary inside of modules`, + type: 'Literal', + }, + { + message: `'use strict' is unnecessary inside of modules`, + type: 'Literal', + }, + ], + }, + { + code: ` + 'use strict' + + function test() {} + export {test}; + `, + errors: [ + { + message: `'use strict' is unnecessary inside of modules`, + type: 'Literal', + }, + ], + }, + ], + + valid: [ + { + code: ` + import {test} from 'test'; + `, + }, + { + code: ` + 'use strict' + + module.export = function test() {} + `, + }, + { + code: ` + 'use strict' + + console.log('foo'); + `, + }, + ], +}); From 24b8ab384a63be835d9f0124b19118dea4d12a2e Mon Sep 17 00:00:00 2001 From: Bryce Osterhaus Date: Thu, 21 Oct 2021 14:25:44 -0700 Subject: [PATCH 2/3] feat(eslint-plugin): add new rule to disalllow anonymous functions as exports --- projects/eslint-plugin/configs/general.js | 1 + .../docs/rules/no-anonymous-exports.md | 25 ++++++++++ projects/eslint-plugin/rules/general/index.js | 1 + .../general/lib/rules/no-anonymous-exports.js | 46 +++++++++++++++++ .../tests/lib/rules/no-anonymous-exports.js | 49 +++++++++++++++++++ 5 files changed, 122 insertions(+) create mode 100644 projects/eslint-plugin/rules/general/docs/rules/no-anonymous-exports.md create mode 100644 projects/eslint-plugin/rules/general/lib/rules/no-anonymous-exports.js create mode 100644 projects/eslint-plugin/rules/general/tests/lib/rules/no-anonymous-exports.js diff --git a/projects/eslint-plugin/configs/general.js b/projects/eslint-plugin/configs/general.js index c9e7b0e051..0097ace93f 100644 --- a/projects/eslint-plugin/configs/general.js +++ b/projects/eslint-plugin/configs/general.js @@ -101,6 +101,7 @@ const config = { '@liferay/imports-first': 'error', '@liferay/no-abbreviations': 'error', '@liferay/no-absolute-import': 'error', + '@liferay/no-anonymous-exports': 'error', '@liferay/no-duplicate-imports': 'error', '@liferay/no-dynamic-require': 'error', '@liferay/no-get-data-attribute': 'error', diff --git a/projects/eslint-plugin/rules/general/docs/rules/no-anonymous-exports.md b/projects/eslint-plugin/rules/general/docs/rules/no-anonymous-exports.md new file mode 100644 index 0000000000..a26d9f46af --- /dev/null +++ b/projects/eslint-plugin/rules/general/docs/rules/no-anonymous-exports.md @@ -0,0 +1,25 @@ +# Prefer exporting named functions (no-anonymous-exports) + +To improve debugging and readability, it is preferable to export named functions to that they can be located in a callstack. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```js +export const x = () => {}; + +export default () => {}; +``` + +Examples of **correct** code for this rule: + +```js +export function x() {} + +export default function x() {} +``` + +## See also + +- https://github.com/liferay/liferay-frontend-projects/issues/25 diff --git a/projects/eslint-plugin/rules/general/index.js b/projects/eslint-plugin/rules/general/index.js index f89b25e25c..51dcdd0552 100644 --- a/projects/eslint-plugin/rules/general/index.js +++ b/projects/eslint-plugin/rules/general/index.js @@ -13,6 +13,7 @@ module.exports = { 'imports-first': require('./lib/rules/imports-first'), 'no-abbreviations': require('./lib/rules/no-abbreviations'), 'no-absolute-import': require('./lib/rules/no-absolute-import'), + 'no-anonymous-exports': require('./lib/rules/no-anonymous-exports'), 'no-arrow': require('./lib/rules/no-arrow'), 'no-duplicate-class-names': require('./lib/rules/no-duplicate-class-names'), 'no-duplicate-imports': require('./lib/rules/no-duplicate-imports'), diff --git a/projects/eslint-plugin/rules/general/lib/rules/no-anonymous-exports.js b/projects/eslint-plugin/rules/general/lib/rules/no-anonymous-exports.js new file mode 100644 index 0000000000..ba54d10fed --- /dev/null +++ b/projects/eslint-plugin/rules/general/lib/rules/no-anonymous-exports.js @@ -0,0 +1,46 @@ +/** + * SPDX-FileCopyrightText: © 2021 Liferay, Inc. + * SPDX-License-Identifier: MIT + */ + +module.exports = { + create(context) { + return { + ExportDefaultDeclaration(node) { + if ( + node.declaration && + node.declaration.type === 'ArrowFunctionExpression' + ) { + context.report({ + message: + "Use named function for export. Example: 'function fooBar() {}'", + node, + }); + } + }, + ExportNamedDeclaration(node) { + if ( + node.declaration && + node.declaration.type === 'VariableDeclaration' && + node.declaration.declarations && + node.declaration.declarations[0] && + node.declaration.declarations[0].init && + node.declaration.declarations[0].init.type === + 'ArrowFunctionExpression' + ) { + context.report({ + message: + "Use named function for export instead of arrow function. Example: 'function fooBar() {}'", + node, + }); + } + }, + }; + }, + meta: { + category: 'Best Practices', + description: 'Prefer exporting named functions', + recommended: false, + url: 'https://github.com/liferay/liferay-frontend-projects/issues/25', + }, +}; diff --git a/projects/eslint-plugin/rules/general/tests/lib/rules/no-anonymous-exports.js b/projects/eslint-plugin/rules/general/tests/lib/rules/no-anonymous-exports.js new file mode 100644 index 0000000000..b93ff43fe7 --- /dev/null +++ b/projects/eslint-plugin/rules/general/tests/lib/rules/no-anonymous-exports.js @@ -0,0 +1,49 @@ +/** + * SPDX-FileCopyrightText: © 2017 Liferay, Inc. + * SPDX-License-Identifier: MIT + */ + +const MultiTester = require('../../../../../scripts/MultiTester'); +const rule = require('../../../lib/rules/no-anonymous-exports'); + +const parserOptions = { + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, +}; + +const ruleTester = new MultiTester(parserOptions); + +ruleTester.run('no-anonymous-exports', rule, { + invalid: [ + { + code: `export const test = () => {}`, + errors: [ + { + message: + "Use named function for export instead of arrow function. Example: 'function fooBar() {}'", + type: 'ExportNamedDeclaration', + }, + ], + }, + { + code: `export default () => {}`, + errors: [ + { + message: + "Use named function for export. Example: 'function fooBar() {}'", + type: 'ExportDefaultDeclaration', + }, + ], + }, + ], + valid: [ + { + code: ` + export function test() {} + export default function fooBar() {} + `, + }, + ], +}); From 6472007b487eca6f60ce77045a78195335d5279f Mon Sep 17 00:00:00 2001 From: Bryce Osterhaus Date: Thu, 21 Oct 2021 14:28:11 -0700 Subject: [PATCH 3/3] chore: apply new lint rules --- maintenance/projects/senna/.eslintrc.js | 1 + projects/js-toolkit/packages/dev-server/src/reload.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/maintenance/projects/senna/.eslintrc.js b/maintenance/projects/senna/.eslintrc.js index 0dcda86cf0..513e267972 100644 --- a/maintenance/projects/senna/.eslintrc.js +++ b/maintenance/projects/senna/.eslintrc.js @@ -12,6 +12,7 @@ module.exports = { rules: { '@liferay/no-abbreviations': 'off', '@liferay/no-it-should': 'warn', + '@liferay/no-use-strict-in-module': 'off', 'curly': 'warn', 'default-case': 'warn', 'no-console': 'warn', diff --git a/projects/js-toolkit/packages/dev-server/src/reload.ts b/projects/js-toolkit/packages/dev-server/src/reload.ts index 2dcafe2dfb..8237bdd867 100644 --- a/projects/js-toolkit/packages/dev-server/src/reload.ts +++ b/projects/js-toolkit/packages/dev-server/src/reload.ts @@ -46,9 +46,9 @@ function setupLiveSession(): void { const CLOSE_BODY_TAG = ''; const RELOAD_SNIPPET = ``; -export default (content: string): string => { +export default function (content: string): string { return content.replace( CLOSE_BODY_TAG, `${RELOAD_SNIPPET}${CLOSE_BODY_TAG}` ); -}; +}