diff --git a/.eslintplugin.js b/.eslintplugin.js deleted file mode 100644 index cc2b90e5ebc6..000000000000 --- a/.eslintplugin.js +++ /dev/null @@ -1,285 +0,0 @@ -// @ts-check - -/** - * To handle PrivateIdentifier and Identifier nodes. - * @param { {type: string, name?: string;} } node - * @returns {string} - */ -function handleIdentifier(node) { - if (node.type === 'PrivateIdentifier' || node.type === 'Identifier') { - return node.name; - } - throw new Error('Cannot extract name of function by MethodDefintion'); -} - -/** - * A little helper function to get the correct naming of the function. - * @param {import('eslint').Rule.Node} node - * @returns {string} - */ -function getNameOfFunction(node) { - switch (node.type) { - case 'FunctionDeclaration': - return node.id.name; - case 'FunctionExpression': - // Check if we can node.id - if (node.id) { - return node.id.name; - } - // Okay let's get their parent node and see if we can - // extract the name from them. - return getNameOfFunction(node.parent); - case 'MethodDefinition': - case 'Property': - // @ts-ignore - case 'ClassProperty': - return handleIdentifier(node.key); - case 'ArrowFunctionExpression': - return getNameOfFunction(node.parent); - case 'VariableDeclarator': - // @ts-ignore - return node.id.name; - default: - throw new Error('Incorrect type of node has been passed to getNameOfFunction'); - } -} - -// A little helper function to check if the node should be linted. -/** - * @param {import('eslint').Rule.Node} node - * @returns {boolean} returns if we should skip. - */ -function shouldSkipNewlineCheck(node) { - // Skip if the function declaration is a one-liner. - // Ex: - // const returnType = (node) => node.type; - if (node.type === 'ArrowFunctionExpression') { - if (node.loc.start.line === node.loc.end.line) { - return true; - } - } - const parentNode = node.parent; - const possibleEndLine = node.loc.end.line + 1; - // Most of the time the function is part of a bigger node. - // So when available, we want to compare against the parent's parent. - if (parentNode.parent) { - return possibleEndLine === parentNode.parent.loc.end.line; - } - return possibleEndLine === parentNode.loc.end.line; -} - -// A little helper function that filter select nodes, -// To check if we should handle those nodes for -// the rule. -/** - * @param {import('eslint').Rule.Node} node - * @returns {boolean} returns if we should skip. - */ -function filterNodes(node) { - if (node.type === 'ArrowFunctionExpression') { - switch (node.parent.type) { - case 'CallExpression': - case 'NewExpression': - case 'AssignmentExpression': - case 'AssignmentPattern': - case 'ConditionalExpression': - case 'LogicalExpression': - case 'ReturnStatement': - case 'Property': - // @ts-ignore - case 'TSAsExpression': - // @ts-ignore - case 'JSXExpressionContainer': - return true; - default: - return false; - } - } else { - switch (node.parent.type) { - case 'AssignmentExpression': - case 'ReturnStatement': - case 'Property': - return true; - default: - return false; - } - } -} - -/** - * @type {{[ruleName: string]: import('eslint').Rule.RuleModule}} - */ -const rules = { - 'jsx-uses-m-pragma': { - /** - * @param {{ name: any; }} node - */ - create(context) { - const pragma = 'm'; - const usePragma = () => context.markVariableAsUsed(pragma); - return { - JSXOpeningElement: usePragma, - JSXOpeningFragment: usePragma, - }; - }, - }, - 'jsx-uses-vars': { - create(context) { - return { - /** - * @param {{ name: any; }} node - */ - JSXOpeningElement(node) { - let variable; - const jsxTag = node.name; - if (jsxTag.type === 'JSXIdentifier') { - variable = jsxTag.name; - } else if (jsxTag.type === 'JSXMemberExpression') { - variable = jsxTag.object.name; - } else { - console.warn('Unsupported JSX identifier', jsxTag); - } - context.markVariableAsUsed(variable); - }, - }; - }, - }, - 'consistent-new-lines': { - meta: { - messages: { - 'incorrect-end': 'Function "{{ name }}" doesn\'t end at {{ loc }}.', - 'no-newline-after-declaration': 'Function "{{ name }}" doesn\'t has a new line after it\'s declaration.', - 'too-many-new-lines': 'Function "{{ name }}" has too many new lines after it\'s declaration, it should only have 1 new line.', - }, - fixable: 'whitespace', - type: 'layout', - - }, - create(context) { - // Request the splitted(on \n) lines from eslint. - const lines = context.getSourceCode().getLines(); - /** - * @param {import('eslint').Rule.Node} node - */ - const checkNewLine = (node) => { - // Check if this node should be scanned. - if (filterNodes(node)) { - return; - } - // Get the 2 tokens after this node - const endLoc = node.loc.end; - // Get the line where the function declaration ends. - const endLine = lines[endLoc.line - 1]; - // Just ensure that the `}` is at the correct line + column. - if (endLine.length !== endLoc.column) { - // A special edge-case, - // - // We might have `};`, let's check for that. - if (endLine.length !== endLoc.column + 1 && endLine[endLoc.column] === ';') { - // Cannot really apply any fix here without a expensive - // context-aware system. Developer should be smart enough - // to know why linter will error such message on code like: - // function log(message) { - // console.log(message) - // } let a = 2; - // - // A possible fix will to just move up all the text after the `}` - // to the next line. - context.report({ - node, - messageId: 'incorrect-end', - data: { - 'name': getNameOfFunction(node), - 'loc': `${endLoc.line}:${endLoc.column}`, - }, - }); - } - } - // Also if it's the last function in some kind of bigger function - // of any kind of syntax that allows it, we should not error about it. - if (shouldSkipNewlineCheck(node)) { - return; - } - // Get the line after the function declaration. - // This one should be empty(new line). - const shouldBeEmptyLine = lines[endLoc.line]; - - // Check if the line is the line is not empty - // and report a error about it. - if (shouldBeEmptyLine) { - const range = node.range; - if (node.type === 'ArrowFunctionExpression') { - // Arrow function declarations must have a `;` - // behind the `}` So to account for it, we make - // the range 1 bit bigger. - range[1]++; - } - context.report({ - node, - messageId: 'no-newline-after-declaration', - data: { - 'name': getNameOfFunction(node), - }, - loc: { - line: endLoc.line + 1, - column: 1, - }, - // Simply add a new line after the function declaration. - fix: (fixer) => { - return fixer.insertTextAfterRange(range, '\n'); - } - }); - } - // Check for the line AFTER the should be empty line. - // This line should not be empty, this indicates that there - // are too many new lines after the function declaration. - // There should be just 1 new line after a decleration of the - // function and not multiple. - // - // Only does this check if lines.length > endLoc.line + 1 - // otherwise it would indicate that we've reached the EOF. - if (lines.length > endLoc.line + 1) { - const lineAfterEmptyline = lines[endLoc.line + 1]; - if (!lineAfterEmptyline) { - /** - * Get the range after the declaration it's first new line - * and the new line after the first line. Rules seems - * to be applied over and over again(after af fix) - * So we don't need to apply any fancy context-aware - * functionality to get all the useless new lines. - * @type {[number, number]} - */ - const range = [node.range[1] + 1, node.range[1] + 2]; - context.report({ - node, - messageId: 'too-many-new-lines', - data: { - 'name': getNameOfFunction(node), - }, - loc: { - line: endLoc.line + 2, - column: 1, - }, - // Simply remove the range, the range - // is the begin index and the end index - // of all the unecassary new lines. - fix: (fixer) => { - return fixer.removeRange(range); - } - }); - } - } - }; - - return { - 'FunctionDeclaration': checkNewLine, - 'FunctionExpression': checkNewLine, - 'ArrowFunctionExpression': checkNewLine, - }; - } - } -}; - -module.exports = { - rules -}; diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 93% rename from .eslintrc.js rename to .eslintrc.cjs index 1d4fff01c96c..343480ec39cb 100644 --- a/.eslintrc.js +++ b/.eslintrc.cjs @@ -1,6 +1,9 @@ +const rulesDirPlugin = require('eslint-plugin-rulesdir'); +rulesDirPlugin.RULES_DIR = 'tasks/lint/plugins'; + module.exports = { parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'local'], + plugins: ['@typescript-eslint', 'rulesdir'], extends: ['plugin:@typescript-eslint/recommended', 'plugin:import/recommended', 'plugin:import/typescript'], rules: { 'array-bracket-spacing': ['error', 'never'], @@ -47,7 +50,7 @@ module.exports = { default: 'array-simple', }], 'yoda': ['error', 'never'], - 'local/consistent-new-lines': 'error', + 'rulesdir/consistent-new-lines': 'error', '@typescript-eslint/brace-style': 'error', '@typescript-eslint/camelcase': 'off', '@typescript-eslint/comma-spacing': ['error', { @@ -136,7 +139,7 @@ module.exports = { overrides: [ { - files: ['tasks/**/*.js', 'tests/**/*.js', '.eslintplugin.js'], + files: ['tasks/**/*.js', 'tests/**/*.js'], rules: { '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-implicit-any-catch': 'off', @@ -146,8 +149,8 @@ module.exports = { { files: ['**/*.tsx'], rules: { - 'local/jsx-uses-m-pragma': 'error', - 'local/jsx-uses-vars': 'error', + 'rulesdir/jsx-uses-m-pragma': 'error', + 'rulesdir/jsx-uses-vars': 'error', }, }, { diff --git a/index.d.ts b/index.d.ts index 332a07370401..2bf2624189ef 100644 --- a/index.d.ts +++ b/index.d.ts @@ -156,7 +156,7 @@ declare namespace DarkReader { * This is a API-Exclusive option, as it can break legitmate websites, * who are using the Dark Reader API. */ - disableStyleSheetsProxy: boolean; + disableStyleSheetsProxy?: boolean; } } diff --git a/package-lock.json b/package-lock.json index b39f2c20e810..bd481a4be3e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "darkreader", - "version": "4.9.42", + "version": "4.9.43", "license": "MIT", "devDependencies": { "@rollup/plugin-node-resolve": "13.0.6", @@ -23,7 +23,7 @@ "chokidar": "3.5.2", "eslint": "8.1.0", "eslint-plugin-import": "2.25.2", - "eslint-plugin-local": "1.0.0", + "eslint-plugin-rulesdir": "0.2.1", "fs-extra": "10.0.0", "globby": "11.0.4", "jasmine-core": "3.10.1", @@ -43,8 +43,6 @@ "rollup-plugin-istanbul2": "2.0.2", "rollup-plugin-typescript2": "0.31.1", "ts-jest": "27.0.7", - "ts-node": "10.4.0", - "tsconfig-paths": "3.11.0", "tslib": "2.3.1", "typescript": "4.4.4", "web-ext": "6.5.0", @@ -619,6 +617,8 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">= 12" } @@ -628,6 +628,8 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@cspotcode/source-map-consumer": "0.8.0" }, @@ -1263,25 +1265,33 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node12": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node14": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@tsconfig/node16": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/@types/babel__core": { "version": "7.1.16", @@ -2436,7 +2446,9 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/argparse": { "version": "2.0.1", @@ -3610,7 +3622,9 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.3", @@ -4191,6 +4205,8 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.3.1" } @@ -5012,11 +5028,14 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "node_modules/eslint-plugin-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-local/-/eslint-plugin-local-1.0.0.tgz", - "integrity": "sha1-8MBwEclf7EK/tNkJ3rtuoDXzsqQ=", - "dev": true + "node_modules/eslint-plugin-rulesdir": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-rulesdir/-/eslint-plugin-rulesdir-0.2.1.tgz", + "integrity": "sha512-t7rQvEyfE4JZJu6dPl4/uVr6Fr0fxopBhzVbtq3isfOHMKdlIe9xW/5CtIaWZI0E1U+wzAk0lEnZC8laCD5YLA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } }, "node_modules/eslint-scope": { "version": "5.1.1", @@ -11755,6 +11774,8 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", "dev": true, + "optional": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "0.7.0", "@tsconfig/node10": "^1.0.7", @@ -11796,6 +11817,8 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=0.4.0" } @@ -12666,6 +12689,8 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -13169,13 +13194,17 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@cspotcode/source-map-support": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", "dev": true, + "optional": true, + "peer": true, "requires": { "@cspotcode/source-map-consumer": "0.8.0" } @@ -13686,25 +13715,33 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node12": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node14": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@tsconfig/node16": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "@types/babel__core": { "version": "7.1.16", @@ -14610,7 +14647,9 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "argparse": { "version": "2.0.1", @@ -15543,7 +15582,9 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "cross-spawn": { "version": "7.0.3", @@ -16015,7 +16056,9 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "diff-sequences": { "version": "27.0.6", @@ -16715,10 +16758,10 @@ } } }, - "eslint-plugin-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-local/-/eslint-plugin-local-1.0.0.tgz", - "integrity": "sha1-8MBwEclf7EK/tNkJ3rtuoDXzsqQ=", + "eslint-plugin-rulesdir": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-rulesdir/-/eslint-plugin-rulesdir-0.2.1.tgz", + "integrity": "sha512-t7rQvEyfE4JZJu6dPl4/uVr6Fr0fxopBhzVbtq3isfOHMKdlIe9xW/5CtIaWZI0E1U+wzAk0lEnZC8laCD5YLA==", "dev": true }, "eslint-scope": { @@ -21902,6 +21945,8 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", "dev": true, + "optional": true, + "peer": true, "requires": { "@cspotcode/source-map-support": "0.7.0", "@tsconfig/node10": "^1.0.7", @@ -21921,7 +21966,9 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true + "dev": true, + "optional": true, + "peer": true } } }, @@ -22593,7 +22640,9 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true + "dev": true, + "optional": true, + "peer": true }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 20f92bb5c9ae..46f692919fc6 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,10 @@ "api": "node tasks/build.js --api", "benchmark-server": "node tests/benchmark-server/index.js", "build": "node tasks/build.js --release", - "code-style": "eslint --ignore-pattern \"!.eslintplugin.js\" --cache --fix \"src/**/*.ts\" \"src/**/*.tsx\" \"tasks/**/*.js\" \"tests/[!coverage]**/*.js\" \"tests/**/*.ts\" \".eslintrc.js\" \"index.d.ts\" \".eslintplugin.js\"", + "code-style": "eslint --cache --fix 'src/**/*.ts' 'src/**/*.tsx' 'tasks/**/*.js' 'tests/**/*.js' 'index.d.ts'", "debug": "node tasks/build.js --debug", "debug:watch": "node tasks/build.js --debug --watch", - "lint": "eslint --ignore-pattern \"!.eslintplugin.js\" \"src/**/*.ts\" \"src/**/*.tsx\" \"tasks/**/*.js\" \"tests/**/*.ts\" \"tests/[!coverage]**/*.js\" \"index.d.ts\" \".eslintplugin.js\"", + "lint": "eslint 'src/**/*.ts' 'src/**/*.tsx' 'tasks/**/*.js' 'tests/**/*.ts' 'index.d.ts'", "prepublishOnly": "npm test && npm run api", "release": "npm test && npm run lint && node tasks/build.js --release", "test": "jest --config=tests/jest.config.js", @@ -26,6 +26,8 @@ "test:utils:debug": "node --inspect-brk ./node_modules/jest/bin/jest --config=tests/utils/jest.config.js --runInBand --no-cache --watch" }, "main": "darkreader.js", + "module": "darkreader.mjs", + "type": "module", "repository": { "type": "git", "url": "https://github.com/darkreader/darkreader.git" @@ -78,7 +80,7 @@ "chokidar": "3.5.2", "eslint": "8.1.0", "eslint-plugin-import": "2.25.2", - "eslint-plugin-local": "1.0.0", + "eslint-plugin-rulesdir": "0.2.1", "fs-extra": "10.0.0", "globby": "11.0.4", "jasmine-core": "3.10.1", @@ -98,8 +100,6 @@ "rollup-plugin-istanbul2": "2.0.2", "rollup-plugin-typescript2": "0.31.1", "ts-jest": "27.0.7", - "ts-node": "10.4.0", - "tsconfig-paths": "3.11.0", "tslib": "2.3.1", "typescript": "4.4.4", "web-ext": "6.5.0", diff --git a/src/definitions.d.ts b/src/definitions.d.ts index 981a97cef3ae..afc20e74394d 100644 --- a/src/definitions.d.ts +++ b/src/definitions.d.ts @@ -135,7 +135,7 @@ export interface DynamicThemeFix { css: string; ignoreInlineStyle: string[]; ignoreImageAnalysis: string[]; - disableStyleSheetsProxy: boolean; + disableStyleSheetsProxy?: boolean; } export interface InversionFix { diff --git a/src/ui/devtools/index.html b/src/ui/devtools/index.html index 372750087bb4..b825c5655e16 100644 --- a/src/ui/devtools/index.html +++ b/src/ui/devtools/index.html @@ -11,6 +11,6 @@ -$BODY + diff --git a/src/ui/popup/index.html b/src/ui/popup/index.html index 511c16962823..f22c43c88623 100644 --- a/src/ui/popup/index.html +++ b/src/ui/popup/index.html @@ -12,6 +12,10 @@ -$BODY + +
+ +
+ diff --git a/src/ui/stylesheet-editor/index.html b/src/ui/stylesheet-editor/index.html index eadceace5aaf..728037da7a36 100644 --- a/src/ui/stylesheet-editor/index.html +++ b/src/ui/stylesheet-editor/index.html @@ -10,6 +10,6 @@ -$BODY + diff --git a/tasks/build.js b/tasks/build.js index 189f0da4c48d..79f79b881a81 100644 --- a/tasks/build.js +++ b/tasks/build.js @@ -1,16 +1,16 @@ // @ts-check -const bundleAPI = require('./bundle-api'); -const bundleCSS = require('./bundle-css'); -const bundleHTML = require('./bundle-html'); -const bundleJS = require('./bundle-js'); -const bundleLocales = require('./bundle-locales'); -const clean = require('./clean'); -const copy = require('./copy'); -const reload = require('./reload'); -const codeStyle = require('./code-style'); -const zip = require('./zip'); -const {runTasks} = require('./task'); -const {log} = require('./utils'); +import bundleAPI from './bundle-api.js'; +import bundleCSS from './bundle-css.js'; +import bundleHTML from './bundle-html.js'; +import bundleJS from './bundle-js.js'; +import bundleLocales from './bundle-locales.js'; +import clean from './clean.js'; +import copy from './copy.js'; +import reload from './reload.js'; +import codeStyle from './code-style.js'; +import zip from './zip.js'; +import {runTasks} from './task.js'; +import {log} from './utils.js'; const standardTask = [ clean, diff --git a/tasks/bundle-api.js b/tasks/bundle-api.js index 23b8e3b2f971..4cbc9a812673 100644 --- a/tasks/bundle-api.js +++ b/tasks/bundle-api.js @@ -1,18 +1,20 @@ -const rollup = require('rollup'); -const rollupPluginNodeResolve = require('@rollup/plugin-node-resolve').default; -const rollupPluginReplace = require('@rollup/plugin-replace'); -const rollupPluginTypescript = require('rollup-plugin-typescript2'); -const typescript = require('typescript'); -const packageJSON = require('../package.json'); -const fs = require('fs-extra'); -const os = require('os'); -const {createTask} = require('./task'); +// @ts-check +import {rollup} from 'rollup'; +import rollupPluginNodeResolve from '@rollup/plugin-node-resolve'; +import rollupPluginReplace from '@rollup/plugin-replace'; +import rollupPluginTypescript from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import fs from 'fs-extra'; +import os from 'os'; +import {createTask} from './task.js'; async function bundleAPI({debug}) { const src = 'src/api/index.ts'; const dest = 'darkreader.js'; - const bundle = await rollup.rollup({ + const packageJSON = await fs.readJSON('package.json'); + + const bundle = await rollup({ input: src, plugins: [ rollupPluginNodeResolve(), @@ -44,7 +46,7 @@ async function bundleAPI({debug}) { }); } -module.exports = createTask( +export default createTask( 'bundle-api', bundleAPI, ); diff --git a/tasks/bundle-css.js b/tasks/bundle-css.js index 45a378a6cf3b..b87278e9c534 100644 --- a/tasks/bundle-css.js +++ b/tasks/bundle-css.js @@ -1,9 +1,10 @@ -const fs = require('fs-extra'); -const less = require('less'); -const path = require('path'); -const {getDestDir, PLATFORM} = require('./paths'); -const reload = require('./reload'); -const {createTask} = require('./task'); +// @ts-check +import fs from 'fs-extra'; +import less from 'less'; +import path from 'path'; +import {getDestDir, PLATFORM} from './paths.js'; +import reload from './reload.js'; +import {createTask} from './task.js'; function getLessFiles({debug}) { const dir = getDestDir({debug, platform: PLATFORM.CHROME}); @@ -41,7 +42,7 @@ async function bundleCSS({debug}) { } } -module.exports = createTask( +export default createTask( 'bundle-css', bundleCSS, ).addWatcher( diff --git a/tasks/bundle-html.js b/tasks/bundle-html.js index 1fcf9af09193..59ada9693c87 100644 --- a/tasks/bundle-html.js +++ b/tasks/bundle-html.js @@ -1,82 +1,17 @@ -const fs = require('fs-extra'); -const {getDestDir, PLATFORM} = require('./paths'); -const reload = require('./reload'); -const {createTask} = require('./task'); - -const enLocale = fs.readFileSync('src/_locales/en.config', {encoding: 'utf8'}).replace(/^#.*?$/gm, ''); -global.chrome = global.chrome || {}; -global.chrome.i18n = global.chrome.i18n || {}; -global.chrome.i18n.getMessage = global.chrome.i18n.getMessage || ((name) => { - const index = enLocale.indexOf(`@${name}`); - if (index < 0) { - throw new Error(`Message @${name} not found`); - } - const start = index + name.length + 1; - let end = enLocale.indexOf('@', start); - if (end < 0) { - end = enLocale.length; - } - const message = enLocale.substring(start, end).trim(); - return message; -}); -global.chrome.i18n.getUILanguage = global.chrome.i18n.getUILanguage || (() => 'en-US'); - -const tsConfig = require('../src/tsconfig.json'); -require('ts-node').register({ - transpileOnly: true, - compilerOptions: { - ...tsConfig.compilerOptions, - module: 'commonjs', - }, -}); -require('tsconfig-paths').register({ - baseUrl: './', - paths: { - 'malevic/*': ['node_modules/malevic/umd/*'], - 'malevic': ['node_modules/malevic/umd/index'], - } -}); -const Malevic = require('malevic/umd/index'); -const MalevicString = require('malevic/umd/string'); -const DevToolsBody = require('../src/ui/devtools/components/body').default; -const PopupBody = require('../src/ui/popup/components/body').default; -const CSSEditorBody = require('../src/ui/stylesheet-editor/components/body').default; -const {getMockData, getMockActiveTabInfo} = require('../src/ui/connect/mock'); +// @ts-check +import fs from 'fs-extra'; +import {getDestDir, PLATFORM} from './paths.js'; +import reload from './reload.js'; +import {createTask} from './task.js'; const pages = [ - { - cwdPath: 'ui/popup/index.html', - rootComponent: PopupBody, - props: { - data: getMockData({isReady: false}), - tab: getMockActiveTabInfo(), - actions: null, - }, - }, - { - cwdPath: 'ui/devtools/index.html', - rootComponent: DevToolsBody, - props: { - data: getMockData({isReady: false}), - tab: getMockActiveTabInfo(), - actions: null, - }, - }, - { - cwdPath: 'ui/stylesheet-editor/index.html', - rootComponent: CSSEditorBody, - props: { - data: getMockData({isReady: false}), - tab: getMockActiveTabInfo(), - actions: null, - }, - }, + 'ui/popup/index.html', + 'ui/devtools/index.html', + 'ui/stylesheet-editor/index.html', ]; -async function bundleHTMLPage({cwdPath, rootComponent, props}, {debug}) { +async function bundleHTMLPage({cwdPath}, {debug}) { let html = await fs.readFile(`src/${cwdPath}`, 'utf8'); - const bodyText = MalevicString.stringify(Malevic.m(rootComponent, props)); - html = html.replace('$BODY', bodyText); const getPath = (dir) => `${dir}/${cwdPath}`; const outPath = getPath(getDestDir({debug, platform: PLATFORM.CHROME})); @@ -91,7 +26,7 @@ async function bundleHTMLPage({cwdPath, rootComponent, props}, {debug}) { async function bundleHTML({debug}) { for (const page of pages) { - await bundleHTMLPage(page, {debug}); + await bundleHTMLPage({cwdPath: page}, {debug}); } } @@ -102,16 +37,16 @@ function getSrcPath(cwdPath) { async function rebuildHTML(changedFiles) { await Promise.all( pages - .filter((page) => changedFiles.some((changed) => changed === getSrcPath(page.cwdPath))) - .map((page) => bundleHTMLPage(page, {debug: true})) + .filter((page) => changedFiles.some((changed) => changed === getSrcPath(page))) + .map((page) => bundleHTMLPage({cwdPath: page}, {debug: true})) ); } -module.exports = createTask( +export default createTask( 'bundle-html', bundleHTML, ).addWatcher( - pages.map((page) => getSrcPath(page.cwdPath)), + pages.map((page) => getSrcPath(page)), async (changedFiles) => { await rebuildHTML(changedFiles); reload({type: reload.UI}); diff --git a/tasks/bundle-js.js b/tasks/bundle-js.js index c8a1fdc1743b..a48ff4d93e55 100644 --- a/tasks/bundle-js.js +++ b/tasks/bundle-js.js @@ -1,14 +1,14 @@ -const fs = require('fs-extra'); -const os = require('os'); -const rollup = require('rollup'); -const rollupPluginNodeResolve = require('@rollup/plugin-node-resolve').default; -const rollupPluginReplace = require('@rollup/plugin-replace'); -const rollupPluginTypescript = require('rollup-plugin-typescript2'); -const typescript = require('typescript'); -const {getDestDir, PLATFORM} = require('./paths'); -const reload = require('./reload'); -const {PORT} = reload; -const {createTask} = require('./task'); +// @ts-check +import fs from 'fs-extra'; +import os from 'os'; +import {rollup} from 'rollup'; +import rollupPluginNodeResolve from '@rollup/plugin-node-resolve'; +import rollupPluginReplace from '@rollup/plugin-replace'; +import rollupPluginTypescript from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; +import {getDestDir, PLATFORM} from './paths.js'; +import reload, {PORT} from './reload.js'; +import {createTask} from './task.js'; async function copyToBrowsers({cwdPath, debug}) { const destPath = `${getDestDir({debug, platform: PLATFORM.CHROME})}/${cwdPath}`; @@ -118,7 +118,7 @@ const jsEntries = [ async function bundleJS(/** @type {JSEntry} */entry, {debug, watch}) { const {src, dest} = entry; - const bundle = await rollup.rollup({ + const bundle = await rollup({ input: src, plugins: [ rollupPluginNodeResolve(), @@ -164,7 +164,7 @@ function getWatchFiles() { /** @type {string[]} */ let watchFiles; -module.exports = createTask( +export default createTask( 'bundle-js', async ({debug, watch}) => await Promise.all( jsEntries.map((entry) => bundleJS(entry, {debug, watch})) diff --git a/tasks/bundle-locales.js b/tasks/bundle-locales.js index c9376df6efe1..3b142cd33409 100644 --- a/tasks/bundle-locales.js +++ b/tasks/bundle-locales.js @@ -1,7 +1,8 @@ -const fs = require('fs-extra'); -const {getDestDir, PLATFORM} = require('./paths'); -const reload = require('./reload'); -const {createTask} = require('./task'); +// @ts-check +import fs from 'fs-extra'; +import {getDestDir, PLATFORM} from './paths.js'; +import reload from './reload.js'; +import {createTask} from './task.js'; async function bundleLocale(/** @type {string} */filePath, {debug}) { let file = await fs.readFile(filePath, 'utf8'); @@ -48,7 +49,7 @@ async function bundleLocales({debug}) { } } -module.exports = createTask( +export default createTask( 'bundle-locales', bundleLocales, ).addWatcher( diff --git a/tasks/clean.js b/tasks/clean.js index ba7cde183982..67b145829574 100644 --- a/tasks/clean.js +++ b/tasks/clean.js @@ -1,6 +1,7 @@ -const fs = require('fs-extra'); -const {getDestDir, PLATFORM} = require('./paths'); -const {createTask} = require('./task'); +// @ts-check +import fs from 'fs-extra'; +import {getDestDir, PLATFORM} from './paths.js'; +import {createTask} from './task.js'; async function clean({debug}) { await fs.remove(getDestDir({debug, platform: PLATFORM.CHROME})); @@ -9,7 +10,7 @@ async function clean({debug}) { await fs.remove(getDestDir({debug, platform: PLATFORM.THUNDERBIRD})); } -module.exports = createTask( +export default createTask( 'clean', clean, ); diff --git a/tasks/code-style.js b/tasks/code-style.js index fe7a7d59b7cb..30938dc9d63a 100644 --- a/tasks/code-style.js +++ b/tasks/code-style.js @@ -1,10 +1,12 @@ -const fs = require('fs-extra'); -const globby = require('globby'); -const prettier = require('prettier'); -const {getDestDir, PLATFORM} = require('./paths'); -const {createTask} = require('./task'); -const {log} = require('./utils'); +// @ts-check +import fs from 'fs-extra'; +import globby from 'globby'; +import prettier from 'prettier'; +import {getDestDir, PLATFORM} from './paths.js'; +import {createTask} from './task.js'; +import {log} from './utils.js'; +/** @type {import('prettier').Options} */ const options = { arrowParens: 'always', bracketSpacing: false, @@ -34,7 +36,7 @@ async function codeStyle({debug}) { } } -module.exports = createTask( +export default createTask( 'code-style', codeStyle, ); diff --git a/tasks/copy.js b/tasks/copy.js index 2b2320d6383d..aee2fe6231ff 100644 --- a/tasks/copy.js +++ b/tasks/copy.js @@ -1,8 +1,9 @@ -const fs = require('fs-extra'); -const globby = require('globby'); -const {getDestDir, PLATFORM} = require('./paths'); -const reload = require('./reload'); -const {createTask} = require('./task'); +// @ts-check +import fs from 'fs-extra'; +import globby from 'globby'; +import {getDestDir, PLATFORM} from './paths.js'; +import reload from './reload.js'; +import {createTask} from './task.js'; const srcDir = 'src'; const cwdPaths = [ @@ -63,14 +64,14 @@ async function copy({debug}) { } } -module.exports = createTask( +export default createTask( 'copy', copy, ).addWatcher( paths, async (changedFiles) => { for (const file of changedFiles) { - if (await fs.exists(file)) { + if (await fs.pathExists(file)) { await copyFile(file, {debug: true, platform: PLATFORM.CHROME}); await copyFile(file, {debug: true, platform: PLATFORM.FIREFOX}); await copyFile(file, {debug: true, platform: PLATFORM.CHROME_MV3}); diff --git a/tasks/lint/plugins/consistent-new-lines.cjs b/tasks/lint/plugins/consistent-new-lines.cjs new file mode 100644 index 000000000000..844ceed1c86d --- /dev/null +++ b/tasks/lint/plugins/consistent-new-lines.cjs @@ -0,0 +1,245 @@ +// @ts-check + +/** + * To handle PrivateIdentifier and Identifier nodes. + * @param { {type: string, name?: string;} } node + * @returns {string} + */ +function handleIdentifier(node) { + if (node.type === 'PrivateIdentifier' || node.type === 'Identifier') { + return node.name; + } + throw new Error('Cannot extract name of function by MethodDefintion'); +} + +/** + * A little helper function to get the correct naming of the function. + * @param {import('eslint').Rule.Node} node + * @returns {string} + */ +function getNameOfFunction(node) { + switch (node.type) { + case 'FunctionDeclaration': + return node.id.name; + case 'FunctionExpression': + // Check if we can node.id + if (node.id) { + return node.id.name; + } + // Okay let's get their parent node and see if we can + // extract the name from them. + return getNameOfFunction(node.parent); + case 'MethodDefinition': + case 'Property': + // @ts-ignore + case 'ClassProperty': + return handleIdentifier(node.key); + case 'ArrowFunctionExpression': + return getNameOfFunction(node.parent); + case 'VariableDeclarator': + // @ts-ignore + return node.id.name; + default: + throw new Error('Incorrect type of node has been passed to getNameOfFunction'); + } +} + +// A little helper function to check if the node should be linted. +/** + * @param {import('eslint').Rule.Node} node + * @returns {boolean} returns if we should skip. + */ +function shouldSkipNewlineCheck(node) { + // Skip if the function declaration is a one-liner. + // Ex: + // const returnType = (node) => node.type; + if (node.type === 'ArrowFunctionExpression') { + if (node.loc.start.line === node.loc.end.line) { + return true; + } + } + const parentNode = node.parent; + const possibleEndLine = node.loc.end.line + 1; + // Most of the time the function is part of a bigger node. + // So when available, we want to compare against the parent's parent. + if (parentNode.parent) { + return possibleEndLine === parentNode.parent.loc.end.line; + } + return possibleEndLine === parentNode.loc.end.line; +} + +// A little helper function that filter select nodes, +// To check if we should handle those nodes for +// the rule. +/** + * @param {import('eslint').Rule.Node} node + * @returns {boolean} returns if we should skip. + */ +function filterNodes(node) { + if (node.type === 'ArrowFunctionExpression') { + switch (node.parent.type) { + case 'CallExpression': + case 'NewExpression': + case 'AssignmentExpression': + case 'AssignmentPattern': + case 'ConditionalExpression': + case 'LogicalExpression': + case 'ReturnStatement': + case 'Property': + // @ts-ignore + case 'TSAsExpression': + // @ts-ignore + case 'JSXExpressionContainer': + return true; + default: + return false; + } + } else { + switch (node.parent.type) { + case 'AssignmentExpression': + case 'ReturnStatement': + case 'Property': + return true; + default: + return false; + } + } +} + +/** + * @type {import('eslint').Rule.RuleModule} + */ +module.exports = { + meta: { + messages: { + 'incorrect-end': 'Function "{{ name }}" doesn\'t end at {{ loc }}.', + 'no-newline-after-declaration': 'Function "{{ name }}" doesn\'t has a new line after it\'s declaration.', + 'too-many-new-lines': 'Function "{{ name }}" has too many new lines after it\'s declaration, it should only have 1 new line.', + }, + fixable: 'whitespace', + type: 'layout', + + }, + create(context) { + // Request the splitted(on \n) lines from eslint. + const lines = context.getSourceCode().getLines(); + /** + * @param {import('eslint').Rule.Node} node + */ + const checkNewLine = (node) => { + // Check if this node should be scanned. + if (filterNodes(node)) { + return; + } + // Get the 2 tokens after this node + const endLoc = node.loc.end; + // Get the line where the function declaration ends. + const endLine = lines[endLoc.line - 1]; + // Just ensure that the `}` is at the correct line + column. + if (endLine.length !== endLoc.column) { + // A special edge-case, + // + // We might have `};`, let's check for that. + if (endLine.length !== endLoc.column + 1 && endLine[endLoc.column] === ';') { + // Cannot really apply any fix here without a expensive + // context-aware system. Developer should be smart enough + // to know why linter will error such message on code like: + // function log(message) { + // console.log(message) + // } let a = 2; + // + // A possible fix will to just move up all the text after the `}` + // to the next line. + context.report({ + node, + messageId: 'incorrect-end', + data: { + 'name': getNameOfFunction(node), + 'loc': `${endLoc.line}:${endLoc.column}`, + }, + }); + } + } + // Also if it's the last function in some kind of bigger function + // of any kind of syntax that allows it, we should not error about it. + if (shouldSkipNewlineCheck(node)) { + return; + } + // Get the line after the function declaration. + // This one should be empty(new line). + const shouldBeEmptyLine = lines[endLoc.line]; + + // Check if the line is the line is not empty + // and report a error about it. + if (shouldBeEmptyLine) { + const range = node.range; + if (node.type === 'ArrowFunctionExpression') { + // Arrow function declarations must have a `;` + // behind the `}` So to account for it, we make + // the range 1 bit bigger. + range[1]++; + } + context.report({ + node, + messageId: 'no-newline-after-declaration', + data: { + 'name': getNameOfFunction(node), + }, + loc: { + line: endLoc.line + 1, + column: 1, + }, + // Simply add a new line after the function declaration. + fix: (fixer) => { + return fixer.insertTextAfterRange(range, '\n'); + } + }); + } + // Check for the line AFTER the should be empty line. + // This line should not be empty, this indicates that there + // are too many new lines after the function declaration. + // There should be just 1 new line after a decleration of the + // function and not multiple. + // + // Only does this check if lines.length > endLoc.line + 1 + // otherwise it would indicate that we've reached the EOF. + if (lines.length > endLoc.line + 1) { + const lineAfterEmptyline = lines[endLoc.line + 1]; + if (!lineAfterEmptyline) { + /** + * Get the range after the declaration it's first new line + * and the new line after the first line. Rules seems + * to be applied over and over again(after af fix) + * So we don't need to apply any fancy context-aware + * functionality to get all the useless new lines. + * @type {[number, number]} + */ + const range = [node.range[1] + 1, node.range[1] + 2]; + context.report({ + node, + messageId: 'too-many-new-lines', + data: { + 'name': getNameOfFunction(node), + }, + loc: { + line: endLoc.line + 2, + column: 1, + }, + // Simply remove the range, the range + // is the begin index and the end index + // of all the unecassary new lines. + fix: (fixer) => { + return fixer.removeRange(range); + } + }); + } + } + }; + + return { + 'FunctionDeclaration': checkNewLine, + 'FunctionExpression': checkNewLine, + 'ArrowFunctionExpression': checkNewLine, + }; + }, +}; diff --git a/tasks/lint/plugins/jsx-uses-m-pragma.cjs b/tasks/lint/plugins/jsx-uses-m-pragma.cjs new file mode 100644 index 000000000000..29146a7106eb --- /dev/null +++ b/tasks/lint/plugins/jsx-uses-m-pragma.cjs @@ -0,0 +1,18 @@ +// @ts-check + +/** + * @type {import('eslint').Rule.RuleModule} + */ +module.exports = { + /** + * @param {{ name: any; }} node + */ + create(context) { + const pragma = 'm'; + const usePragma = () => context.markVariableAsUsed(pragma); + return { + JSXOpeningElement: usePragma, + JSXOpeningFragment: usePragma, + }; + }, +}; diff --git a/tasks/lint/plugins/jsx-uses-vars.cjs b/tasks/lint/plugins/jsx-uses-vars.cjs new file mode 100644 index 000000000000..fc94c89f7994 --- /dev/null +++ b/tasks/lint/plugins/jsx-uses-vars.cjs @@ -0,0 +1,26 @@ +// @ts-check + +/** + * @type {import('eslint').Rule.RuleModule} + */ +module.exports = { + create(context) { + return { + /** + * @param {{ name: any; }} node + */ + JSXOpeningElement(node) { + let variable; + const jsxTag = node.name; + if (jsxTag.type === 'JSXIdentifier') { + variable = jsxTag.name; + } else if (jsxTag.type === 'JSXMemberExpression') { + variable = jsxTag.object.name; + } else { + console.warn('Unsupported JSX identifier', jsxTag); + } + context.markVariableAsUsed(variable); + }, + }; + }, +}; diff --git a/tasks/paths.js b/tasks/paths.js index ec39fd70e6ed..e3eec1a95b2f 100644 --- a/tasks/paths.js +++ b/tasks/paths.js @@ -1,12 +1,12 @@ -module.exports = { - PLATFORM: { - CHROME: 'chrome', - CHROME_MV3: 'chrome-mv3', - FIREFOX: 'firefox', - THUNDERBIRD: 'thunderbird', - }, - getDestDir: function ({debug, platform}) { - const buildTypeDir = `build/${debug ? 'debug' : 'release'}`; - return `${buildTypeDir}/${platform}`; - } +// @ts-check +export const PLATFORM = { + CHROME: 'chrome', + CHROME_MV3: 'chrome-mv3', + FIREFOX: 'firefox', + THUNDERBIRD: 'thunderbird', }; + +export function getDestDir({debug, platform}) { + const buildTypeDir = `build/${debug ? 'debug' : 'release'}`; + return `${buildTypeDir}/${platform}`; +} diff --git a/tasks/reload.js b/tasks/reload.js index 85e5dfea63e6..e2050ca1237a 100644 --- a/tasks/reload.js +++ b/tasks/reload.js @@ -1,7 +1,8 @@ -const WebSocket = require('ws'); -const {log} = require('./utils'); +// @ts-check +import WebSocket from 'ws'; +import {log} from './utils.js'; -const PORT = 8890; +export const PORT = 8890; const WAIT_FOR_CONNECTION = 2000; /** @type {import('ws').Server} */ @@ -25,7 +26,7 @@ function createServer() { sockets.add(ws); times.set(ws, Date.now()); ws.on('message', async (data) => { - const message = JSON.parse(data); + const message = JSON.parse(data.toString()); if (message.type === 'reloading') { log.ok('Extension reloading...'); } @@ -94,8 +95,12 @@ async function reload({type}) { .forEach((ws) => send(ws, {type})); } -module.exports = reload; -module.exports.PORT = PORT; -module.exports.CSS = 'reload:css'; -module.exports.FULL = 'reload:full'; -module.exports.UI = 'reload:ui'; +const CSS = 'reload:css'; +const FULL = 'reload:full'; +const UI = 'reload:ui'; + +export default Object.assign(reload, { + CSS, + FULL, + UI, +}); diff --git a/tasks/task.js b/tasks/task.js index 163f7aeb98be..eaa87c94ae31 100644 --- a/tasks/task.js +++ b/tasks/task.js @@ -1,5 +1,6 @@ -const {log} = require('./utils'); -const watch = require('./watch'); +// @ts-check +import {log} from './utils.js'; +import watch from './watch.js'; /** * @typedef TaskOptions @@ -28,7 +29,7 @@ class Task { } /** - * @param {Promise} promise + * @param {void | Promise} promise */ async _measureTime(promise) { const start = Date.now(); @@ -66,9 +67,9 @@ class Task { /** * @param {string} name - * @param {(options: TaskOptions) => void | Promise} run + * @param {(options: TaskOptions) => void | Promise} run */ -function createTask(name, run) { +export function createTask(name, run) { return new Task(name, run); } @@ -76,7 +77,7 @@ function createTask(name, run) { * @param {Task[]} tasks * @param {TaskOptions} options */ -async function runTasks(tasks, options) { +export async function runTasks(tasks, options) { for (const task of tasks) { try { await task.run(options); @@ -86,8 +87,3 @@ async function runTasks(tasks, options) { } } } - -module.exports = { - createTask, - runTasks, -}; diff --git a/tasks/utils.js b/tasks/utils.js index 0921d59d9a79..2c5e59da90d8 100644 --- a/tasks/utils.js +++ b/tasks/utils.js @@ -1,3 +1,5 @@ +// @ts-check +/** @type {{[color: string]: (text: string) => string}} */ const colors = Object.entries({ gray: '\x1b[90m', green: '\x1b[32m', @@ -14,12 +16,8 @@ function logWithTime(text) { return console.log(`${colors.gray([hours, minutes, seconds].map(leftpad).join(':'))} ${text}`); } -const log = Object.assign((text) => logWithTime(text), { +export const log = Object.assign((text) => logWithTime(text), { ok: (text) => logWithTime(colors.green(text)), warn: (text) => logWithTime(colors.yellow(text)), error: (text) => logWithTime(colors.red(text)), }); - -module.exports = { - log, -}; diff --git a/tasks/watch.js b/tasks/watch.js index 44d513773dd7..8d99e5b817cc 100644 --- a/tasks/watch.js +++ b/tasks/watch.js @@ -1,5 +1,6 @@ -const chokidar = require('chokidar'); -const {log} = require('./utils'); +// @ts-check +import chokidar from 'chokidar'; +import {log} from './utils.js'; const DEBOUNCE = 200; @@ -8,7 +9,7 @@ const DEBOUNCE = 200; * @param {string[]} options.files * @param {(files: string[]) => void | Promise} options.onChange */ -function watch(options) { +export default function watch(options) { const queue = new Set(); let timeoutId = null; @@ -49,5 +50,3 @@ function watch(options) { return watcher; } - -module.exports = watch; diff --git a/tasks/zip.js b/tasks/zip.js index e89e5944f9bb..16174306f1f6 100644 --- a/tasks/zip.js +++ b/tasks/zip.js @@ -1,8 +1,9 @@ -const fs = require('fs'); -const globby = require('globby'); -const yazl = require('yazl'); -const {getDestDir, PLATFORM} = require('./paths'); -const {createTask} = require('./task'); +// @ts-check +import fs from 'fs'; +import globby from 'globby'; +import yazl from 'yazl'; +import {getDestDir, PLATFORM} from './paths.js'; +import {createTask} from './task.js'; function archiveFiles({files, dest, cwd}) { return new Promise((resolve) => { @@ -35,7 +36,7 @@ async function zip({debug}) { await archiveDirectory({dir: thunderBirdDir, dest: thunderbirdDest}); } -module.exports = createTask( +export default createTask( 'zip', zip, ); diff --git a/tests/benchmark-server/index.js b/tests/benchmark-server/index.js index 563d83ba318e..3f9220916484 100644 --- a/tests/benchmark-server/index.js +++ b/tests/benchmark-server/index.js @@ -1,6 +1,6 @@ // @ts-check -const http = require('http'); -const url = require('url'); +import http from 'http'; +import url from 'url'; const port = 1357; http.createServer((request, response) => { diff --git a/tests/browser/coverage.js b/tests/browser/coverage.js index 503cb6fde90f..036c9103f6ec 100644 --- a/tests/browser/coverage.js +++ b/tests/browser/coverage.js @@ -1,6 +1,5 @@ -// @ts-check -const fs = require('fs-extra'); -const path = require('path'); +import fs from 'fs-extra'; +import path from 'path'; /** @typedef {{text: string; covered: boolean}} CodePart */ @@ -52,7 +51,7 @@ function green(/** @type {string} */text) { * @param {string} code * @param {{start: number; end: number}[]} ranges */ -function logCoverage(code, ranges) { +export function logCoverage(code, ranges) { code = code.substring(0, code.indexOf('//# sourceMappingURL=')); const parts = splitCode(code, ranges); const message = parts @@ -157,7 +156,7 @@ async function generateIndexHTMLCoveragePage(dir, info) { * @param {string} dir * @param {import('puppeteer-core').CoverageEntry[]} coverage */ -async function generateHTMLCoverageReports(dir, coverage) { +export async function generateHTMLCoverageReports(dir, coverage) { const info = coverage .filter(({url}) => url.startsWith('chrome-extension://')) .map(({url, text, ranges}) => { @@ -168,8 +167,3 @@ async function generateHTMLCoverageReports(dir, coverage) { await generateIndexHTMLCoveragePage(dir, info); await Promise.all(info.map((i) => generateHTMLCoverageReport(dir, i))); } - -module.exports = { - logCoverage, - generateHTMLCoverageReports, -}; diff --git a/tests/browser/environment.js b/tests/browser/environment.js index 97bf296d4c7b..9119764ccc9b 100644 --- a/tests/browser/environment.js +++ b/tests/browser/environment.js @@ -1,20 +1,19 @@ -// @ts-check -const fs = require('fs-extra'); -const JestNodeEnvironment = require('jest-environment-node'); -const path = require('path'); -const puppeteer = require('puppeteer-core'); -const webExt = require('web-ext'); -const WebSocket = require('ws'); -const {generateHTMLCoverageReports} = require('./coverage'); -const {getChromePath, getFirefoxPath, chromeExtensionDebugDir, firefoxExtensionDebugDir} = require('./paths'); -const {createTestServer} = require('./server'); +import fs from 'fs-extra'; +import JestNodeEnvironment from 'jest-environment-node'; +import path from 'path'; +import puppeteer from 'puppeteer-core'; +import webExt from 'web-ext'; +import WebSocket from 'ws'; +import {generateHTMLCoverageReports} from './coverage.js'; +import {getChromePath, getFirefoxPath, chromeExtensionDebugDir, firefoxExtensionDebugDir} from './paths.js'; +import {createTestServer} from './server.js'; const TEST_SERVER_PORT = 8891; const CORS_SERVER_PORT = 8892; const FIREFOX_DEVTOOLS_PORT = 8893; const POPUP_TEST_PORT = 8894; -class PuppeteerEnvironment extends JestNodeEnvironment { +export default class PuppeteerEnvironment extends JestNodeEnvironment { async setup() { await super.setup(); @@ -248,5 +247,3 @@ class PuppeteerEnvironment extends JestNodeEnvironment { // await this.browser.close(); } } - -module.exports = PuppeteerEnvironment; diff --git a/tests/browser/jest.config.chrome.js b/tests/browser/jest.config.chrome.js index e9f59a3f43cf..16dcb3c688cf 100644 --- a/tests/browser/jest.config.chrome.js +++ b/tests/browser/jest.config.chrome.js @@ -1,6 +1,6 @@ -const jestConfig = require('./jest.config.shared'); +import jestConfig from './jest.config.shared.js'; -module.exports = { +export default { ...jestConfig, globals: { ...jestConfig.globals, diff --git a/tests/browser/jest.config.firefox.js b/tests/browser/jest.config.firefox.js index 2ab318f9d8c8..501826b7e2bf 100644 --- a/tests/browser/jest.config.firefox.js +++ b/tests/browser/jest.config.firefox.js @@ -1,6 +1,6 @@ -const jestConfig = require('./jest.config.shared'); +import jestConfig from './jest.config.shared.js'; -module.exports = { +export default { ...jestConfig, globals: { ...jestConfig.globals, diff --git a/tests/browser/jest.config.js b/tests/browser/jest.config.js index a8cb48a04805..eefecef95a05 100644 --- a/tests/browser/jest.config.js +++ b/tests/browser/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { projects: [ 'tests/browser/jest.config.chrome.js', 'tests/browser/jest.config.firefox.js', diff --git a/tests/browser/jest.config.shared.js b/tests/browser/jest.config.shared.js index b023cef47a19..b7d771f2c464 100644 --- a/tests/browser/jest.config.shared.js +++ b/tests/browser/jest.config.shared.js @@ -1,4 +1,4 @@ -module.exports = { +export default { testEnvironment: './environment.js', verbose: true, transform: { diff --git a/tests/browser/paths.js b/tests/browser/paths.js index a9f895c6ff77..fddf26c828ea 100644 --- a/tests/browser/paths.js +++ b/tests/browser/paths.js @@ -1,7 +1,7 @@ -// @ts-check -const {exec} = require('child_process'); -const fs = require('fs-extra'); -const path = require('path'); +import {exec} from 'child_process'; +import fs from 'fs-extra'; +import path from 'path'; +import url from 'url'; async function winProgramFiles(relPath) { const x64Path = path.join(process.env.PROGRAMFILES, relPath); @@ -26,7 +26,7 @@ function linuxAppPath(app) { /** * @returns {Promise} */ -async function getChromePath() { +export async function getChromePath() { if (process.platform === 'darwin') { return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; } @@ -47,7 +47,7 @@ async function getChromePath() { /** * @returns {Promise} */ -async function getFirefoxPath() { +export async function getFirefoxPath() { if (process.platform === 'darwin') { return '/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin'; } @@ -57,12 +57,6 @@ async function getFirefoxPath() { return await linuxAppPath('firefox-nightly'); } -const chromeExtensionDebugDir = path.join(__dirname, '../../build/debug/chrome'); -const firefoxExtensionDebugDir = path.join(__dirname, '../../build/debug/firefox'); - -module.exports = { - getChromePath, - getFirefoxPath, - chromeExtensionDebugDir, - firefoxExtensionDebugDir, -}; +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); +export const chromeExtensionDebugDir = path.join(__dirname, '../../build/debug/chrome'); +export const firefoxExtensionDebugDir = path.join(__dirname, '../../build/debug/firefox'); diff --git a/tests/browser/server.js b/tests/browser/server.js index a4099a87df80..c765d257f08d 100644 --- a/tests/browser/server.js +++ b/tests/browser/server.js @@ -1,7 +1,7 @@ // @ts-check -const http = require('http'); -const path = require('path'); -const url = require('url'); +import http from 'http'; +import path from 'path'; +import url from 'url'; const mimeTypes = new Map( Object.entries({ @@ -15,7 +15,7 @@ const mimeTypes = new Map( }), ); -async function createTestServer(/** @type {number} */port) { +export async function createTestServer(/** @type {number} */port) { /** @type {import('http').Server} */ let server; /** @type {{[path: string]: string | import('http').RequestListener}} */ @@ -95,7 +95,3 @@ async function createTestServer(/** @type {number} */port) { url: `http://localhost:${port}`, }; } - -module.exports = { - createTestServer, -}; diff --git a/tests/config/jest.config.js b/tests/config/jest.config.js index 18c61a22e688..72f9ac74a1a2 100644 --- a/tests/config/jest.config.js +++ b/tests/config/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { verbose: true, testEnvironment: 'node', transform: { diff --git a/tests/generators/utils/jest.config.js b/tests/generators/utils/jest.config.js index f3427709c17a..852ae0769ce0 100644 --- a/tests/generators/utils/jest.config.js +++ b/tests/generators/utils/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { verbose: true, testEnvironment: 'jsdom', transform: { diff --git a/tests/inject/echo-server.js b/tests/inject/echo-server.js index 26490cd8a0c6..6db782951d8c 100644 --- a/tests/inject/echo-server.js +++ b/tests/inject/echo-server.js @@ -1,9 +1,8 @@ -// @ts-check -const http = require('http'); -const url = require('url'); -const queryString = require('querystring'); +import http from 'http'; +import url from 'url'; +import queryString from 'querystring'; -async function createEchoServer(/** @type {number} */port) { +export async function createEchoServer(/** @type {number} */port) { /** @type {import('http').Server} */ let server; @@ -72,7 +71,3 @@ async function createEchoServer(/** @type {number} */port) { url: `http://localhost:${port}`, }; } - -module.exports = { - createEchoServer, -}; diff --git a/tests/inject/jest.config.js b/tests/inject/jest.config.js index 0569b84b16e4..a8c3b0e6e4a5 100644 --- a/tests/inject/jest.config.js +++ b/tests/inject/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { verbose: true, testEnvironment: 'jsdom', transform: { diff --git a/tests/inject/karma.conf.js b/tests/inject/karma.conf.cjs similarity index 100% rename from tests/inject/karma.conf.js rename to tests/inject/karma.conf.cjs diff --git a/tests/inject/run.js b/tests/inject/run.js index 236f4827e5f0..6b949e18cad3 100644 --- a/tests/inject/run.js +++ b/tests/inject/run.js @@ -1,14 +1,15 @@ -// @ts-check -const karma = require('karma'); -const path = require('path'); -const {createEchoServer} = require('./echo-server'); +import karma from 'karma'; +import path from 'path'; +import url from 'url'; +import {createEchoServer} from './echo-server.js'; const ECHO_SERVER_PORT = 9966; +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); async function run() { const args = process.argv.slice(2); const debug = args.includes('--debug'); - const karmaConfig = karma.config.parseConfig(path.join(__dirname, './karma.conf.js'), /** @type {any} */({debug})); + const karmaConfig = karma.config.parseConfig(path.join(__dirname, './karma.conf.cjs'), /** @type {any} */({debug})); const echoServer = await createEchoServer(ECHO_SERVER_PORT); const karmaServer = new karma.Server(/** @type {any} */(karmaConfig), () => { diff --git a/tests/jest.config.js b/tests/jest.config.js index dbf4ade2d135..0afea8f819d1 100644 --- a/tests/jest.config.js +++ b/tests/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { projects: [ 'tests/config/jest.config.js', 'tests/utils/jest.config.js', diff --git a/tests/utils/jest.config.js b/tests/utils/jest.config.js index ed12a8408e60..79cb2c28f475 100644 --- a/tests/utils/jest.config.js +++ b/tests/utils/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { verbose: true, testEnvironment: 'jsdom', transform: {