Initial commit
This commit is contained in:
commit
fd67e90cbc
62
.eslintrc
Normal file
62
.eslintrc
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"browser": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended", "prettier"],
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaVersion": "latest"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"warn",
|
||||||
|
{
|
||||||
|
"paths": [
|
||||||
|
"array-includes",
|
||||||
|
"array.prototype.flat",
|
||||||
|
"array.prototype.flatmap",
|
||||||
|
"array.prototype.tosorted",
|
||||||
|
"object.entries",
|
||||||
|
"object.fromentries",
|
||||||
|
"object.hasown",
|
||||||
|
"object.values",
|
||||||
|
"string.prototype.matchall",
|
||||||
|
"has"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"arrow-body-style": ["error", "as-needed"],
|
||||||
|
"class-methods-use-this": [
|
||||||
|
"warn",
|
||||||
|
{ "exceptMethods": ["toString", "shouldComponentUpdate"] }
|
||||||
|
],
|
||||||
|
"complexity": ["warn", { "max": 100 }],
|
||||||
|
"curly": ["error", "multi-line", "consistent"],
|
||||||
|
"eqeqeq": ["error", "smart"],
|
||||||
|
"no-async-promise-executor": "off",
|
||||||
|
"no-case-declarations": "off",
|
||||||
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
|
"no-debugger": "off",
|
||||||
|
"no-empty": ["error", { "allowEmptyCatch": true }],
|
||||||
|
"no-inner-declarations": "off",
|
||||||
|
"no-lonely-if": "error",
|
||||||
|
"no-template-curly-in-string": "error",
|
||||||
|
"no-var": "error",
|
||||||
|
"object-shorthand": ["error", "always", { "ignoreConstructors": true }],
|
||||||
|
"one-var": ["error", { "var": "never", "let": "never" }],
|
||||||
|
"prefer-const": ["error", { "destructuring": "all" }],
|
||||||
|
"prefer-destructuring": [
|
||||||
|
"warn",
|
||||||
|
{ "AssignmentExpression": { "array": false, "object": false } }
|
||||||
|
],
|
||||||
|
"prefer-rest-params": "warn",
|
||||||
|
"prefer-spread": "warn",
|
||||||
|
"quote-props": ["error", "as-needed"],
|
||||||
|
"spaced-comment": ["error", "always", { "markers": ["/"] }],
|
||||||
|
"sort-imports": ["warn", { "ignoreDeclarationSort": true }],
|
||||||
|
"yoda": ["error", "never", { "exceptRange": true }]
|
||||||
|
}
|
||||||
|
}
|
139
.gitignore
vendored
Normal file
139
.gitignore
vendored
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
eslint-plugin-import
|
||||||
|
eslint-plugin-jsx-a11y
|
||||||
|
eslint-plugin-react
|
||||||
|
jsx-ast-utils
|
||||||
|
react
|
||||||
|
|
||||||
|
dist/**/*.js
|
||||||
|
dist/**/*.js.map
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
repl.ts
|
2
.npmrc
Normal file
2
.npmrc
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
registry http://raspberrypi.local:4873
|
||||||
|
always-auth=true
|
12
.vscode/settings.json
vendored
Normal file
12
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"eslint.runtime": "node",
|
||||||
|
"search.exclude": {
|
||||||
|
"**/node_modules": true,
|
||||||
|
"**/bower_components": true,
|
||||||
|
"**/*.code-search": true,
|
||||||
|
"**/__mocks__": true,
|
||||||
|
"**/__tests__": true,
|
||||||
|
"**/tests": true
|
||||||
|
}
|
||||||
|
}
|
9
build.sh
Executable file
9
build.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
rm dist/*.js
|
||||||
|
mkdir -p dist
|
||||||
|
./esbuild.ts
|
||||||
|
|
||||||
|
# Test
|
||||||
|
DEST=$HOME/Git/archive/node_modules
|
||||||
|
rm -rf "$DEST/@proteria/eslint-rules"
|
||||||
|
cp -r dist "$DEST/@proteria/eslint-rules"
|
9
dist/README.md
vendored
Normal file
9
dist/README.md
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# eslint-rules
|
||||||
|
|
||||||
|
Custom build of various ESLint rule libraries with little dependency as possible.
|
||||||
|
|
||||||
|
## Includes
|
||||||
|
|
||||||
|
- eslint-plugin-import
|
||||||
|
- eslint-plugin-jsx-a11y
|
||||||
|
- eslint-plugin-react
|
30
dist/package.json
vendored
Normal file
30
dist/package.json
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@aet/eslint-rules",
|
||||||
|
"version": "0.0.1-beta.11",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "http://raspberrypi.local:4873"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.1.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"aria-query": "^5.3.0",
|
||||||
|
"axe-core": "4.7.2",
|
||||||
|
"axobject-query": "^3.2.1",
|
||||||
|
"damerau-levenshtein": "1.0.8",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"doctrine": "^3.0.0",
|
||||||
|
"eslint-import-resolver-node": "^0.3.7",
|
||||||
|
"eslint-module-utils": "^2.8.0",
|
||||||
|
"estraverse": "^5.3.0",
|
||||||
|
"is-core-module": "^2.12.1",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"language-tags": "^1.0.8",
|
||||||
|
"minimatch": "^9.0.3",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"resolve": "^2.0.0-next.4",
|
||||||
|
"semver": "^7.5.4",
|
||||||
|
"tsconfig-paths": "^4.2.0"
|
||||||
|
}
|
||||||
|
}
|
52
esbuild.ts
Executable file
52
esbuild.ts
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env -S node -r esbin
|
||||||
|
import esbuild from 'esbuild';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import { babelPlugin } from './src/babel';
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const ENV = process.env.NODE_ENV || 'development';
|
||||||
|
const PROD = ENV === 'production';
|
||||||
|
|
||||||
|
async function main(entry: string, outfile: string) {
|
||||||
|
const context = await esbuild.context({
|
||||||
|
entryPoints: [entry],
|
||||||
|
outfile,
|
||||||
|
bundle: true,
|
||||||
|
minify: PROD,
|
||||||
|
platform: 'node',
|
||||||
|
packages: 'external',
|
||||||
|
sourcemap: 'linked',
|
||||||
|
plugins: [
|
||||||
|
babelPlugin,
|
||||||
|
{
|
||||||
|
name: 'alias',
|
||||||
|
setup(build) {
|
||||||
|
build.onResolve({ filter: /^jsx-ast-utils$/ }, () => ({
|
||||||
|
path: resolve('./jsx-ast-utils/src/index.js'),
|
||||||
|
}));
|
||||||
|
build.onResolve({ filter: /^jsx-ast-utils\/.+$/ }, ({ path }) => ({
|
||||||
|
path:
|
||||||
|
resolve('./jsx-ast-utils/', path.slice('jsx-ast-utils/'.length)) + '.js',
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
define: {},
|
||||||
|
banner: {
|
||||||
|
js: '/* eslint-disable */',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await context.rebuild();
|
||||||
|
|
||||||
|
if (args.includes('-w') || args.includes('--watch')) {
|
||||||
|
await context.watch();
|
||||||
|
} else {
|
||||||
|
await context.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main('./eslint-plugin-react/index.js', './dist/react/index.js');
|
||||||
|
main('./eslint-plugin-import/src/index.js', './dist/import/index.js');
|
||||||
|
main('./eslint-plugin-jsx-a11y/src/index.js', './dist/jsx-a11y/index.js');
|
||||||
|
main('./src/ensureRedirect.ts', './dist/ensureRedirect.js');
|
1800
eslint-plugin-react-hooks/ExhaustiveDeps.ts
Normal file
1800
eslint-plugin-react-hooks/ExhaustiveDeps.ts
Normal file
File diff suppressed because it is too large
Load Diff
706
eslint-plugin-react-hooks/RulesOfHooks.ts
Normal file
706
eslint-plugin-react-hooks/RulesOfHooks.ts
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global BigInt */
|
||||||
|
/* eslint-disable no-for-of-loops/no-for-of-loops */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch all identifiers that begin with "use" followed by an uppercase Latin
|
||||||
|
* character to exclude identifiers like "user".
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isHookName(s) {
|
||||||
|
if (__EXPERIMENTAL__) {
|
||||||
|
return s === 'use' || /^use[A-Z0-9]/.test(s);
|
||||||
|
}
|
||||||
|
return /^use[A-Z0-9]/.test(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We consider hooks to be a hook name identifier or a member expression
|
||||||
|
* containing a hook name.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isHook(node) {
|
||||||
|
if (node.type === 'Identifier') {
|
||||||
|
return isHookName(node.name);
|
||||||
|
} else if (
|
||||||
|
node.type === 'MemberExpression' &&
|
||||||
|
!node.computed &&
|
||||||
|
isHook(node.property)
|
||||||
|
) {
|
||||||
|
const obj = node.object;
|
||||||
|
const isPascalCaseNameSpace = /^[A-Z].*/;
|
||||||
|
return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the node is a React component name. React component names must
|
||||||
|
* always start with an uppercase letter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isComponentName(node) {
|
||||||
|
return node.type === 'Identifier' && /^[A-Z]/.test(node.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReactFunction(node, functionName) {
|
||||||
|
return (
|
||||||
|
node.name === functionName ||
|
||||||
|
(node.type === 'MemberExpression' &&
|
||||||
|
node.object.name === 'React' &&
|
||||||
|
node.property.name === functionName)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the node is a callback argument of forwardRef. This render function
|
||||||
|
* should follow the rules of hooks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isForwardRefCallback(node) {
|
||||||
|
return !!(
|
||||||
|
node.parent &&
|
||||||
|
node.parent.callee &&
|
||||||
|
isReactFunction(node.parent.callee, 'forwardRef')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the node is a callback argument of React.memo. This anonymous
|
||||||
|
* functional component should follow the rules of hooks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function isMemoCallback(node) {
|
||||||
|
return !!(
|
||||||
|
node.parent &&
|
||||||
|
node.parent.callee &&
|
||||||
|
isReactFunction(node.parent.callee, 'memo')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInsideComponentOrHook(node) {
|
||||||
|
while (node) {
|
||||||
|
const functionName = getFunctionName(node);
|
||||||
|
if (functionName) {
|
||||||
|
if (isComponentName(functionName) || isHook(functionName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isForwardRefCallback(node) || isMemoCallback(node)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
node = node.parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUseEffectEventIdentifier(node) {
|
||||||
|
if (__EXPERIMENTAL__) {
|
||||||
|
return node.type === 'Identifier' && node.name === 'useEffectEvent';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUseIdentifier(node) {
|
||||||
|
if (__EXPERIMENTAL__) {
|
||||||
|
return node.type === 'Identifier' && node.name === 'use';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
meta: {
|
||||||
|
type: 'problem',
|
||||||
|
docs: {
|
||||||
|
description: 'enforces the Rules of Hooks',
|
||||||
|
recommended: true,
|
||||||
|
url: 'https://reactjs.org/docs/hooks-rules.html',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create(context) {
|
||||||
|
let lastEffect = null;
|
||||||
|
const codePathReactHooksMapStack = [];
|
||||||
|
const codePathSegmentStack = [];
|
||||||
|
const useEffectEventFunctions = new WeakSet();
|
||||||
|
|
||||||
|
// For a given scope, iterate through the references and add all useEffectEvent definitions. We can
|
||||||
|
// do this in non-Program nodes because we can rely on the assumption that useEffectEvent functions
|
||||||
|
// can only be declared within a component or hook at its top level.
|
||||||
|
function recordAllUseEffectEventFunctions(scope) {
|
||||||
|
for (const reference of scope.references) {
|
||||||
|
const parent = reference.identifier.parent;
|
||||||
|
if (
|
||||||
|
parent.type === 'VariableDeclarator' &&
|
||||||
|
parent.init &&
|
||||||
|
parent.init.type === 'CallExpression' &&
|
||||||
|
parent.init.callee &&
|
||||||
|
isUseEffectEventIdentifier(parent.init.callee)
|
||||||
|
) {
|
||||||
|
for (const ref of reference.resolved.references) {
|
||||||
|
if (ref !== reference) {
|
||||||
|
useEffectEventFunctions.add(ref.identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Maintain code segment path stack as we traverse.
|
||||||
|
onCodePathSegmentStart: segment => codePathSegmentStack.push(segment),
|
||||||
|
onCodePathSegmentEnd: () => codePathSegmentStack.pop(),
|
||||||
|
|
||||||
|
// Maintain code path stack as we traverse.
|
||||||
|
onCodePathStart: () => codePathReactHooksMapStack.push(new Map()),
|
||||||
|
|
||||||
|
// Process our code path.
|
||||||
|
//
|
||||||
|
// Everything is ok if all React Hooks are both reachable from the initial
|
||||||
|
// segment and reachable from every final segment.
|
||||||
|
onCodePathEnd(codePath, codePathNode) {
|
||||||
|
const reactHooksMap = codePathReactHooksMapStack.pop();
|
||||||
|
if (reactHooksMap.size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All of the segments which are cyclic are recorded in this set.
|
||||||
|
const cyclic = new Set();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of code paths from the start of the function to this
|
||||||
|
* segment. For example:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* function MyComponent() {
|
||||||
|
* if (condition) {
|
||||||
|
* // Segment 1
|
||||||
|
* } else {
|
||||||
|
* // Segment 2
|
||||||
|
* }
|
||||||
|
* // Segment 3
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Segments 1 and 2 have one path to the beginning of `MyComponent` and
|
||||||
|
* segment 3 has two paths to the beginning of `MyComponent` since we
|
||||||
|
* could have either taken the path of segment 1 or segment 2.
|
||||||
|
*
|
||||||
|
* Populates `cyclic` with cyclic segments.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function countPathsFromStart(segment, pathHistory) {
|
||||||
|
const { cache } = countPathsFromStart;
|
||||||
|
let paths = cache.get(segment.id);
|
||||||
|
const pathList = new Set(pathHistory);
|
||||||
|
|
||||||
|
// If `pathList` includes the current segment then we've found a cycle!
|
||||||
|
// We need to fill `cyclic` with all segments inside cycle
|
||||||
|
if (pathList.has(segment.id)) {
|
||||||
|
const pathArray = [...pathList];
|
||||||
|
const cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
|
||||||
|
for (const cyclicSegment of cyclicSegments) {
|
||||||
|
cyclic.add(cyclicSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BigInt('0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the current segment to pathList
|
||||||
|
pathList.add(segment.id);
|
||||||
|
|
||||||
|
// We have a cached `paths`. Return it.
|
||||||
|
if (paths !== undefined) {
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codePath.thrownSegments.includes(segment)) {
|
||||||
|
paths = BigInt('0');
|
||||||
|
} else if (segment.prevSegments.length === 0) {
|
||||||
|
paths = BigInt('1');
|
||||||
|
} else {
|
||||||
|
paths = BigInt('0');
|
||||||
|
for (const prevSegment of segment.prevSegments) {
|
||||||
|
paths += countPathsFromStart(prevSegment, pathList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If our segment is reachable then there should be at least one path
|
||||||
|
// to it from the start of our code path.
|
||||||
|
if (segment.reachable && paths === BigInt('0')) {
|
||||||
|
cache.delete(segment.id);
|
||||||
|
} else {
|
||||||
|
cache.set(segment.id, paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of code paths from this segment to the end of the
|
||||||
|
* function. For example:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* function MyComponent() {
|
||||||
|
* // Segment 1
|
||||||
|
* if (condition) {
|
||||||
|
* // Segment 2
|
||||||
|
* } else {
|
||||||
|
* // Segment 3
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Segments 2 and 3 have one path to the end of `MyComponent` and
|
||||||
|
* segment 1 has two paths to the end of `MyComponent` since we could
|
||||||
|
* either take the path of segment 1 or segment 2.
|
||||||
|
*
|
||||||
|
* Populates `cyclic` with cyclic segments.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function countPathsToEnd(segment, pathHistory) {
|
||||||
|
const { cache } = countPathsToEnd;
|
||||||
|
let paths = cache.get(segment.id);
|
||||||
|
const pathList = new Set(pathHistory);
|
||||||
|
|
||||||
|
// If `pathList` includes the current segment then we've found a cycle!
|
||||||
|
// We need to fill `cyclic` with all segments inside cycle
|
||||||
|
if (pathList.has(segment.id)) {
|
||||||
|
const pathArray = Array.from(pathList);
|
||||||
|
const cyclicSegments = pathArray.slice(pathArray.indexOf(segment.id) + 1);
|
||||||
|
for (const cyclicSegment of cyclicSegments) {
|
||||||
|
cyclic.add(cyclicSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BigInt('0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the current segment to pathList
|
||||||
|
pathList.add(segment.id);
|
||||||
|
|
||||||
|
// We have a cached `paths`. Return it.
|
||||||
|
if (paths !== undefined) {
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codePath.thrownSegments.includes(segment)) {
|
||||||
|
paths = BigInt('0');
|
||||||
|
} else if (segment.nextSegments.length === 0) {
|
||||||
|
paths = BigInt('1');
|
||||||
|
} else {
|
||||||
|
paths = BigInt('0');
|
||||||
|
for (const nextSegment of segment.nextSegments) {
|
||||||
|
paths += countPathsToEnd(nextSegment, pathList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.set(segment.id, paths);
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the shortest path length to the start of a code path.
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* function MyComponent() {
|
||||||
|
* if (condition) {
|
||||||
|
* // Segment 1
|
||||||
|
* }
|
||||||
|
* // Segment 2
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* There is only one path from segment 1 to the code path start. Its
|
||||||
|
* length is one so that is the shortest path.
|
||||||
|
*
|
||||||
|
* There are two paths from segment 2 to the code path start. One
|
||||||
|
* through segment 1 with a length of two and another directly to the
|
||||||
|
* start with a length of one. The shortest path has a length of one
|
||||||
|
* so we would return that.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function shortestPathLengthToStart(segment) {
|
||||||
|
const { cache } = shortestPathLengthToStart;
|
||||||
|
let length = cache.get(segment.id);
|
||||||
|
|
||||||
|
// If `length` is null then we found a cycle! Return infinity since
|
||||||
|
// the shortest path is definitely not the one where we looped.
|
||||||
|
if (length === null) {
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a cached `length`. Return it.
|
||||||
|
if (length !== undefined) {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute `length` and cache it. Guarding against cycles.
|
||||||
|
cache.set(segment.id, null);
|
||||||
|
if (segment.prevSegments.length === 0) {
|
||||||
|
length = 1;
|
||||||
|
} else {
|
||||||
|
length = Infinity;
|
||||||
|
for (const prevSegment of segment.prevSegments) {
|
||||||
|
const prevLength = shortestPathLengthToStart(prevSegment);
|
||||||
|
if (prevLength < length) {
|
||||||
|
length = prevLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
length += 1;
|
||||||
|
}
|
||||||
|
cache.set(segment.id, length);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
countPathsFromStart.cache = new Map();
|
||||||
|
countPathsToEnd.cache = new Map();
|
||||||
|
shortestPathLengthToStart.cache = new Map();
|
||||||
|
|
||||||
|
// Count all code paths to the end of our component/hook. Also primes
|
||||||
|
// the `countPathsToEnd` cache.
|
||||||
|
const allPathsFromStartToEnd = countPathsToEnd(codePath.initialSegment);
|
||||||
|
|
||||||
|
// Gets the function name for our code path. If the function name is
|
||||||
|
// `undefined` then we know either that we have an anonymous function
|
||||||
|
// expression or our code path is not in a function. In both cases we
|
||||||
|
// will want to error since neither are React function components or
|
||||||
|
// hook functions - unless it is an anonymous function argument to
|
||||||
|
// forwardRef or memo.
|
||||||
|
const codePathFunctionName = getFunctionName(codePathNode);
|
||||||
|
|
||||||
|
// This is a valid code path for React hooks if we are directly in a React
|
||||||
|
// function component or we are in a hook function.
|
||||||
|
const isSomewhereInsideComponentOrHook = isInsideComponentOrHook(codePathNode);
|
||||||
|
const isDirectlyInsideComponentOrHook = codePathFunctionName
|
||||||
|
? isComponentName(codePathFunctionName) || isHook(codePathFunctionName)
|
||||||
|
: isForwardRefCallback(codePathNode) || isMemoCallback(codePathNode);
|
||||||
|
|
||||||
|
// Compute the earliest finalizer level using information from the
|
||||||
|
// cache. We expect all reachable final segments to have a cache entry
|
||||||
|
// after calling `visitSegment()`.
|
||||||
|
let shortestFinalPathLength = Infinity;
|
||||||
|
for (const finalSegment of codePath.finalSegments) {
|
||||||
|
if (!finalSegment.reachable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const length = shortestPathLengthToStart(finalSegment);
|
||||||
|
if (length < shortestFinalPathLength) {
|
||||||
|
shortestFinalPathLength = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure all React Hooks pass our lint invariants. Log warnings
|
||||||
|
// if not.
|
||||||
|
for (const [segment, reactHooks] of reactHooksMap) {
|
||||||
|
// NOTE: We could report here that the hook is not reachable, but
|
||||||
|
// that would be redundant with more general "no unreachable"
|
||||||
|
// lint rules.
|
||||||
|
if (!segment.reachable) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are any final segments with a shorter path to start then
|
||||||
|
// we possibly have an early return.
|
||||||
|
//
|
||||||
|
// If our segment is a final segment itself then siblings could
|
||||||
|
// possibly be early returns.
|
||||||
|
const possiblyHasEarlyReturn =
|
||||||
|
segment.nextSegments.length === 0
|
||||||
|
? shortestFinalPathLength <= shortestPathLengthToStart(segment)
|
||||||
|
: shortestFinalPathLength < shortestPathLengthToStart(segment);
|
||||||
|
|
||||||
|
// Count all the paths from the start of our code path to the end of
|
||||||
|
// our code path that go _through_ this segment. The critical piece
|
||||||
|
// of this is _through_. If we just call `countPathsToEnd(segment)`
|
||||||
|
// then we neglect that we may have gone through multiple paths to get
|
||||||
|
// to this point! Consider:
|
||||||
|
//
|
||||||
|
// ```js
|
||||||
|
// function MyComponent() {
|
||||||
|
// if (a) {
|
||||||
|
// // Segment 1
|
||||||
|
// } else {
|
||||||
|
// // Segment 2
|
||||||
|
// }
|
||||||
|
// // Segment 3
|
||||||
|
// if (b) {
|
||||||
|
// // Segment 4
|
||||||
|
// } else {
|
||||||
|
// // Segment 5
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// In this component we have four code paths:
|
||||||
|
//
|
||||||
|
// 1. `a = true; b = true`
|
||||||
|
// 2. `a = true; b = false`
|
||||||
|
// 3. `a = false; b = true`
|
||||||
|
// 4. `a = false; b = false`
|
||||||
|
//
|
||||||
|
// From segment 3 there are two code paths to the end through segment
|
||||||
|
// 4 and segment 5. However, we took two paths to get here through
|
||||||
|
// segment 1 and segment 2.
|
||||||
|
//
|
||||||
|
// If we multiply the paths from start (two) by the paths to end (two)
|
||||||
|
// for segment 3 we get four. Which is our desired count.
|
||||||
|
const pathsFromStartToEnd =
|
||||||
|
countPathsFromStart(segment) * countPathsToEnd(segment);
|
||||||
|
|
||||||
|
// Is this hook a part of a cyclic segment?
|
||||||
|
const cycled = cyclic.has(segment.id);
|
||||||
|
|
||||||
|
for (const hook of reactHooks) {
|
||||||
|
// Report an error if a hook may be called more then once.
|
||||||
|
// `use(...)` can be called in loops.
|
||||||
|
if (cycled && !isUseIdentifier(hook)) {
|
||||||
|
context.report({
|
||||||
|
node: hook,
|
||||||
|
message:
|
||||||
|
`React Hook "${context.getSource(hook)}" may be executed ` +
|
||||||
|
'more than once. Possibly because it is called in a loop. ' +
|
||||||
|
'React Hooks must be called in the exact same order in ' +
|
||||||
|
'every component render.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is not a valid code path for React hooks then we need to
|
||||||
|
// log a warning for every hook in this code path.
|
||||||
|
//
|
||||||
|
// Pick a special message depending on the scope this hook was
|
||||||
|
// called in.
|
||||||
|
if (isDirectlyInsideComponentOrHook) {
|
||||||
|
// Report an error if the hook is called inside an async function.
|
||||||
|
const isAsyncFunction = codePathNode.async;
|
||||||
|
if (isAsyncFunction) {
|
||||||
|
context.report({
|
||||||
|
node: hook,
|
||||||
|
message:
|
||||||
|
`React Hook "${context.getSource(hook)}" cannot be ` +
|
||||||
|
'called in an async function.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report an error if a hook does not reach all finalizing code
|
||||||
|
// path segments.
|
||||||
|
//
|
||||||
|
// Special case when we think there might be an early return.
|
||||||
|
if (
|
||||||
|
!cycled &&
|
||||||
|
pathsFromStartToEnd !== allPathsFromStartToEnd &&
|
||||||
|
!isUseIdentifier(hook) // `use(...)` can be called conditionally.
|
||||||
|
) {
|
||||||
|
const message =
|
||||||
|
`React Hook "${context.getSource(hook)}" is called ` +
|
||||||
|
'conditionally. React Hooks must be called in the exact ' +
|
||||||
|
'same order in every component render.' +
|
||||||
|
(possiblyHasEarlyReturn
|
||||||
|
? ' Did you accidentally call a React Hook after an' +
|
||||||
|
' early return?'
|
||||||
|
: '');
|
||||||
|
context.report({ node: hook, message });
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
codePathNode.parent &&
|
||||||
|
(codePathNode.parent.type === 'MethodDefinition' ||
|
||||||
|
codePathNode.parent.type === 'ClassProperty') &&
|
||||||
|
codePathNode.parent.value === codePathNode
|
||||||
|
) {
|
||||||
|
// Custom message for hooks inside a class
|
||||||
|
const message =
|
||||||
|
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||||
|
'in a class component. React Hooks must be called in a ' +
|
||||||
|
'React function component or a custom React Hook function.';
|
||||||
|
context.report({ node: hook, message });
|
||||||
|
} else if (codePathFunctionName) {
|
||||||
|
// Custom message if we found an invalid function name.
|
||||||
|
const message =
|
||||||
|
`React Hook "${context.getSource(hook)}" is called in ` +
|
||||||
|
`function "${context.getSource(codePathFunctionName)}" ` +
|
||||||
|
'that is neither a React function component nor a custom ' +
|
||||||
|
'React Hook function.' +
|
||||||
|
' React component names must start with an uppercase letter.' +
|
||||||
|
' React Hook names must start with the word "use".';
|
||||||
|
context.report({ node: hook, message });
|
||||||
|
} else if (codePathNode.type === 'Program') {
|
||||||
|
// These are dangerous if you have inline requires enabled.
|
||||||
|
const message =
|
||||||
|
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||||
|
'at the top level. React Hooks must be called in a ' +
|
||||||
|
'React function component or a custom React Hook function.';
|
||||||
|
context.report({ node: hook, message });
|
||||||
|
} else {
|
||||||
|
// Assume in all other cases the user called a hook in some
|
||||||
|
// random function callback. This should usually be true for
|
||||||
|
// anonymous function expressions. Hopefully this is clarifying
|
||||||
|
// enough in the common case that the incorrect message in
|
||||||
|
// uncommon cases doesn't matter.
|
||||||
|
// `use(...)` can be called in callbacks.
|
||||||
|
if (isSomewhereInsideComponentOrHook && !isUseIdentifier(hook)) {
|
||||||
|
const message =
|
||||||
|
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||||
|
'inside a callback. React Hooks must be called in a ' +
|
||||||
|
'React function component or a custom React Hook function.';
|
||||||
|
context.report({ node: hook, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Missed opportunity...We could visit all `Identifier`s instead of all
|
||||||
|
// `CallExpression`s and check that _every use_ of a hook name is valid.
|
||||||
|
// But that gets complicated and enters type-system territory, so we're
|
||||||
|
// only being strict about hook calls for now.
|
||||||
|
CallExpression(node) {
|
||||||
|
if (isHook(node.callee)) {
|
||||||
|
// Add the hook node to a map keyed by the code path segment. We will
|
||||||
|
// do full code path analysis at the end of our code path.
|
||||||
|
const reactHooksMap = last(codePathReactHooksMapStack);
|
||||||
|
const codePathSegment = last(codePathSegmentStack);
|
||||||
|
let reactHooks = reactHooksMap.get(codePathSegment);
|
||||||
|
if (!reactHooks) {
|
||||||
|
reactHooks = [];
|
||||||
|
reactHooksMap.set(codePathSegment, reactHooks);
|
||||||
|
}
|
||||||
|
reactHooks.push(node.callee);
|
||||||
|
}
|
||||||
|
|
||||||
|
// useEffectEvent: useEffectEvent functions can be passed by reference within useEffect as well as in
|
||||||
|
// another useEffectEvent
|
||||||
|
if (
|
||||||
|
node.callee.type === 'Identifier' &&
|
||||||
|
(node.callee.name === 'useEffect' || isUseEffectEventIdentifier(node.callee)) &&
|
||||||
|
node.arguments.length > 0
|
||||||
|
) {
|
||||||
|
// Denote that we have traversed into a useEffect call, and stash the CallExpr for
|
||||||
|
// comparison later when we exit
|
||||||
|
lastEffect = node;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Identifier(node) {
|
||||||
|
// This identifier resolves to a useEffectEvent function, but isn't being referenced in an
|
||||||
|
// effect or another event function. It isn't being called either.
|
||||||
|
if (
|
||||||
|
lastEffect == null &&
|
||||||
|
useEffectEventFunctions.has(node) &&
|
||||||
|
node.parent.type !== 'CallExpression'
|
||||||
|
) {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message:
|
||||||
|
`\`${context.getSource(
|
||||||
|
node,
|
||||||
|
)}\` is a function created with React Hook "useEffectEvent", and can only be called from ` +
|
||||||
|
'the same component. They cannot be assigned to variables or passed down.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'CallExpression:exit'(node) {
|
||||||
|
if (node === lastEffect) {
|
||||||
|
lastEffect = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
FunctionDeclaration(node) {
|
||||||
|
// function MyComponent() { const onClick = useEffectEvent(...) }
|
||||||
|
if (isInsideComponentOrHook(node)) {
|
||||||
|
recordAllUseEffectEventFunctions(context.getScope());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
ArrowFunctionExpression(node) {
|
||||||
|
// const MyComponent = () => { const onClick = useEffectEvent(...) }
|
||||||
|
if (isInsideComponentOrHook(node)) {
|
||||||
|
recordAllUseEffectEventFunctions(context.getScope());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the static name of a function AST node. For function declarations it is
|
||||||
|
* easy. For anonymous function expressions it is much harder. If you search for
|
||||||
|
* `IsAnonymousFunctionDefinition()` in the ECMAScript spec you'll find places
|
||||||
|
* where JS gives anonymous function expressions names. We roughly detect the
|
||||||
|
* same AST nodes with some exceptions to better fit our use case.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getFunctionName(node) {
|
||||||
|
if (
|
||||||
|
node.type === 'FunctionDeclaration' ||
|
||||||
|
(node.type === 'FunctionExpression' && node.id)
|
||||||
|
) {
|
||||||
|
// function useHook() {}
|
||||||
|
// const whatever = function useHook() {};
|
||||||
|
//
|
||||||
|
// Function declaration or function expression names win over any
|
||||||
|
// assignment statements or other renames.
|
||||||
|
return node.id;
|
||||||
|
} else if (
|
||||||
|
node.type === 'FunctionExpression' ||
|
||||||
|
node.type === 'ArrowFunctionExpression'
|
||||||
|
) {
|
||||||
|
if (node.parent.type === 'VariableDeclarator' && node.parent.init === node) {
|
||||||
|
// const useHook = () => {};
|
||||||
|
return node.parent.id;
|
||||||
|
} else if (
|
||||||
|
node.parent.type === 'AssignmentExpression' &&
|
||||||
|
node.parent.right === node &&
|
||||||
|
node.parent.operator === '='
|
||||||
|
) {
|
||||||
|
// useHook = () => {};
|
||||||
|
return node.parent.left;
|
||||||
|
} else if (
|
||||||
|
node.parent.type === 'Property' &&
|
||||||
|
node.parent.value === node &&
|
||||||
|
!node.parent.computed
|
||||||
|
) {
|
||||||
|
// {useHook: () => {}}
|
||||||
|
// {useHook() {}}
|
||||||
|
return node.parent.key;
|
||||||
|
|
||||||
|
// NOTE: We could also support `ClassProperty` and `MethodDefinition`
|
||||||
|
// here to be pedantic. However, hooks in a class are an anti-pattern. So
|
||||||
|
// we don't allow it to error early.
|
||||||
|
//
|
||||||
|
// class {useHook = () => {}}
|
||||||
|
// class {useHook() {}}
|
||||||
|
} else if (
|
||||||
|
node.parent.type === 'AssignmentPattern' &&
|
||||||
|
node.parent.right === node &&
|
||||||
|
!node.parent.computed
|
||||||
|
) {
|
||||||
|
// const {useHook = () => {}} = {};
|
||||||
|
// ({useHook = () => {}} = {});
|
||||||
|
//
|
||||||
|
// Kinda clowny, but we'd said we'd follow spec convention for
|
||||||
|
// `IsAnonymousFunctionDefinition()` usage.
|
||||||
|
return node.parent.left;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function for peeking the last item in a stack.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function last(array) {
|
||||||
|
return array[array.length - 1];
|
||||||
|
}
|
24
eslint-plugin-react-hooks/index.ts
Normal file
24
eslint-plugin-react-hooks/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import RulesOfHooks from './RulesOfHooks';
|
||||||
|
import ExhaustiveDeps from './ExhaustiveDeps';
|
||||||
|
|
||||||
|
export const configs = {
|
||||||
|
recommended: {
|
||||||
|
plugins: ['react-hooks'],
|
||||||
|
rules: {
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rules = {
|
||||||
|
'rules-of-hooks': RulesOfHooks,
|
||||||
|
'exhaustive-deps': ExhaustiveDeps,
|
||||||
|
};
|
33
package.json
Normal file
33
package.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "./esbuild.ts",
|
||||||
|
"check-import": "for js in dist/*.js; do cat $js | grep 'require('; done"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.22.9",
|
||||||
|
"@babel/plugin-transform-flow-strip-types": "^7.22.5",
|
||||||
|
"@babel/preset-env": "^7.22.9",
|
||||||
|
"@types/babel-plugin-macros": "^3.1.0",
|
||||||
|
"@types/babel__core": "^7.20.1",
|
||||||
|
"@types/node": "^20.4.2",
|
||||||
|
"babel-plugin-macros": "^3.1.0",
|
||||||
|
"esbin": "0.0.1-beta.1",
|
||||||
|
"esbuild": "0.18.14",
|
||||||
|
"esbuild-plugin-alias": "^0.2.1",
|
||||||
|
"esbuild-register": "3.4.2",
|
||||||
|
"eslint": "8.45.0",
|
||||||
|
"eslint-config-prettier": "8.8.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"glob": "^10.3.3",
|
||||||
|
"prettier": "^3.0.0"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"printWidth": 90,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
|
}
|
498
patch/eslint-plugin-import.patch
Normal file
498
patch/eslint-plugin-import.patch
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
diff --git a/.babelrc b/.babelrc
|
||||||
|
index 883c03b7..0111d616 100644
|
||||||
|
--- a/.babelrc
|
||||||
|
+++ b/.babelrc
|
||||||
|
@@ -1,18 +1,11 @@
|
||||||
|
{
|
||||||
|
- "presets": ["airbnb"],
|
||||||
|
- "sourceMaps": "inline",
|
||||||
|
- "retainLines": true,
|
||||||
|
+ "presets": [["@babel/preset-env", { "targets": { "node": 16 } }]],
|
||||||
|
"env": {
|
||||||
|
"test": {
|
||||||
|
- "plugins": [
|
||||||
|
- "istanbul",
|
||||||
|
- ["module-resolver", { "root": ["./src/"] }],
|
||||||
|
- ]
|
||||||
|
+ "plugins": ["istanbul", ["module-resolver", { "root": ["./src/"] }]]
|
||||||
|
},
|
||||||
|
"testCompiled": {
|
||||||
|
- "plugins": [
|
||||||
|
- ["module-resolver", { "root": ["./lib/"] }],
|
||||||
|
- ]
|
||||||
|
+ "plugins": [["module-resolver", { "root": ["./lib/"] }]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/.eslintrc b/.eslintrc
|
||||||
|
deleted file mode 100644
|
||||||
|
index 709a4744..00000000
|
||||||
|
--- a/.eslintrc
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,247 +0,0 @@
|
||||||
|
-{
|
||||||
|
- "root": true,
|
||||||
|
- "plugins": [
|
||||||
|
- "eslint-plugin",
|
||||||
|
- "import",
|
||||||
|
- ],
|
||||||
|
- "extends": [
|
||||||
|
- "eslint:recommended",
|
||||||
|
- "plugin:eslint-plugin/recommended",
|
||||||
|
- "plugin:import/recommended",
|
||||||
|
- ],
|
||||||
|
- "env": {
|
||||||
|
- "node": true,
|
||||||
|
- "es6": true,
|
||||||
|
- "es2017": true,
|
||||||
|
- },
|
||||||
|
- "parserOptions": {
|
||||||
|
- "sourceType": "module",
|
||||||
|
- "ecmaVersion": 2020,
|
||||||
|
- },
|
||||||
|
- "rules": {
|
||||||
|
- "arrow-body-style": [2, "as-needed"],
|
||||||
|
- "arrow-parens": [2, "always"],
|
||||||
|
- "arrow-spacing": [2, { "before": true, "after": true }],
|
||||||
|
- "block-spacing": [2, "always"],
|
||||||
|
- "brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
||||||
|
- "comma-dangle": ["error", {
|
||||||
|
- "arrays": "always-multiline",
|
||||||
|
- "objects": "always-multiline",
|
||||||
|
- "imports": "always-multiline",
|
||||||
|
- "exports": "always-multiline",
|
||||||
|
- "functions": "always-multiline",
|
||||||
|
- }],
|
||||||
|
- "comma-spacing": [2, { "before": false, "after": true }],
|
||||||
|
- "comma-style": [2, "last"],
|
||||||
|
- "computed-property-spacing": [2, "never"],
|
||||||
|
- "curly": [2, "all"],
|
||||||
|
- "default-case": [2, { "commentPattern": "(?:)" }],
|
||||||
|
- "default-case-last": [2],
|
||||||
|
- "default-param-last": [2],
|
||||||
|
- "dot-location": [2, "property"],
|
||||||
|
- "dot-notation": [2, { "allowKeywords": true, "allowPattern": "throws" }],
|
||||||
|
- "eol-last": [2, "always"],
|
||||||
|
- "eqeqeq": [2, "allow-null"],
|
||||||
|
- "for-direction": [2],
|
||||||
|
- "function-call-argument-newline": [2, "consistent"],
|
||||||
|
- "func-call-spacing": [2, "never"],
|
||||||
|
- "implicit-arrow-linebreak": [2, "beside"],
|
||||||
|
- "indent": [2, 2, {
|
||||||
|
- "SwitchCase": 1,
|
||||||
|
- "VariableDeclarator": 1,
|
||||||
|
- "outerIIFEBody": 1,
|
||||||
|
- "FunctionDeclaration": {
|
||||||
|
- "parameters": 1,
|
||||||
|
- "body": 1
|
||||||
|
- },
|
||||||
|
- "FunctionExpression": {
|
||||||
|
- "parameters": 1,
|
||||||
|
- "body": 1
|
||||||
|
- },
|
||||||
|
- "CallExpression": {
|
||||||
|
- "arguments": 1
|
||||||
|
- },
|
||||||
|
- "ArrayExpression": 1,
|
||||||
|
- "ObjectExpression": 1,
|
||||||
|
- "ImportDeclaration": 1,
|
||||||
|
- "flatTernaryExpressions": false,
|
||||||
|
- }],
|
||||||
|
- "jsx-quotes": [2, "prefer-double"],
|
||||||
|
- "key-spacing": [2, {
|
||||||
|
- "beforeColon": false,
|
||||||
|
- "afterColon": true,
|
||||||
|
- "mode": "strict",
|
||||||
|
- }],
|
||||||
|
- "keyword-spacing": ["error", {
|
||||||
|
- "before": true,
|
||||||
|
- "after": true,
|
||||||
|
- "overrides": {
|
||||||
|
- "return": { "after": true },
|
||||||
|
- "throw": { "after": true },
|
||||||
|
- "case": { "after": true }
|
||||||
|
- }
|
||||||
|
- }],
|
||||||
|
- "linebreak-style": [2, "unix"],
|
||||||
|
- "lines-around-directive": [2, {
|
||||||
|
- "before": "always",
|
||||||
|
- "after": "always",
|
||||||
|
- }],
|
||||||
|
- "max-len": 0,
|
||||||
|
- "new-parens": 2,
|
||||||
|
- "no-array-constructor": 2,
|
||||||
|
- "no-compare-neg-zero": 2,
|
||||||
|
- "no-cond-assign": [2, "always"],
|
||||||
|
- "no-extra-parens": 2,
|
||||||
|
- "no-multiple-empty-lines": [2, { "max": 1, "maxEOF": 1, "maxBOF": 0 }],
|
||||||
|
- "no-return-assign": [2, "always"],
|
||||||
|
- "no-trailing-spaces": 2,
|
||||||
|
- "no-var": 2,
|
||||||
|
- "object-curly-spacing": [2, "always"],
|
||||||
|
- "object-shorthand": ["error", "always", {
|
||||||
|
- "ignoreConstructors": false,
|
||||||
|
- "avoidQuotes": false,
|
||||||
|
- "avoidExplicitReturnArrows": true,
|
||||||
|
- }],
|
||||||
|
- "one-var": [2, "never"],
|
||||||
|
- "operator-linebreak": [2, "none", {
|
||||||
|
- "overrides": {
|
||||||
|
- "?": "before",
|
||||||
|
- ":": "before",
|
||||||
|
- "&&": "before",
|
||||||
|
- "||": "before",
|
||||||
|
- },
|
||||||
|
- }],
|
||||||
|
- "prefer-const": 2,
|
||||||
|
- "prefer-object-spread": 2,
|
||||||
|
- "prefer-rest-params": 2,
|
||||||
|
- "prefer-template": 2,
|
||||||
|
- "quote-props": [2, "as-needed", { "keywords": false }],
|
||||||
|
- "quotes": [2, "single", {
|
||||||
|
- "allowTemplateLiterals": true,
|
||||||
|
- "avoidEscape": true,
|
||||||
|
- }],
|
||||||
|
- "rest-spread-spacing": [2, "never"],
|
||||||
|
- "semi": [2, "always"],
|
||||||
|
- "semi-spacing": [2, { "before": false, "after": true }],
|
||||||
|
- "semi-style": [2, "last"],
|
||||||
|
- "space-before-blocks": [2, { "functions": "always", "keywords": "always", "classes": "always" }],
|
||||||
|
- "space-before-function-paren": ["error", {
|
||||||
|
- "anonymous": "always",
|
||||||
|
- "named": "never",
|
||||||
|
- "asyncArrow": "always",
|
||||||
|
- }],
|
||||||
|
- "space-in-parens": [2, "never"],
|
||||||
|
- "space-infix-ops": [2],
|
||||||
|
- "space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||||
|
- "switch-colon-spacing": [2, { "after": true, "before": false }],
|
||||||
|
- "template-curly-spacing": [2, "never"],
|
||||||
|
- "template-tag-spacing": [2, "never"],
|
||||||
|
- "unicode-bom": [2, "never"],
|
||||||
|
- "use-isnan": [2, { "enforceForSwitchCase": true }],
|
||||||
|
- "valid-typeof": [2],
|
||||||
|
- "wrap-iife": [2, "outside", { "functionPrototypeMethods": true }],
|
||||||
|
- "wrap-regex": [2],
|
||||||
|
- "yield-star-spacing": [2, { "before": false, "after": true }],
|
||||||
|
- "yoda": [2, "never", { "exceptRange": true, "onlyEquality": false }],
|
||||||
|
-
|
||||||
|
- "eslint-plugin/consistent-output": [
|
||||||
|
- "error",
|
||||||
|
- "always",
|
||||||
|
- ],
|
||||||
|
- "eslint-plugin/meta-property-ordering": "error",
|
||||||
|
- "eslint-plugin/no-deprecated-context-methods": "error",
|
||||||
|
- "eslint-plugin/no-deprecated-report-api": "off",
|
||||||
|
- "eslint-plugin/prefer-replace-text": "error",
|
||||||
|
- "eslint-plugin/report-message-format": "error",
|
||||||
|
- "eslint-plugin/require-meta-docs-description": ["error", { "pattern": "^(Enforce|Ensure|Prefer|Forbid).+\\.$" }],
|
||||||
|
- "eslint-plugin/require-meta-schema": "error",
|
||||||
|
- "eslint-plugin/require-meta-type": "error",
|
||||||
|
-
|
||||||
|
- // dog fooding
|
||||||
|
- "import/no-extraneous-dependencies": ["error", {
|
||||||
|
- "devDependencies": [
|
||||||
|
- "tests/**",
|
||||||
|
- "resolvers/*/test/**",
|
||||||
|
- "scripts/**"
|
||||||
|
- ],
|
||||||
|
- "optionalDependencies": false,
|
||||||
|
- "peerDependencies": true,
|
||||||
|
- "bundledDependencies": false,
|
||||||
|
- }],
|
||||||
|
- "import/unambiguous": "off",
|
||||||
|
- },
|
||||||
|
-
|
||||||
|
- "settings": {
|
||||||
|
- "import/resolver": {
|
||||||
|
- "node": {
|
||||||
|
- "paths": [
|
||||||
|
- "src",
|
||||||
|
- ],
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
-
|
||||||
|
- "overrides": [
|
||||||
|
- {
|
||||||
|
- "files": "scripts/**",
|
||||||
|
- "rules": {
|
||||||
|
- "no-console": "off",
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- {
|
||||||
|
- "files": "resolvers/**",
|
||||||
|
- "env": {
|
||||||
|
- "es6": false,
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- {
|
||||||
|
- "files": "resolvers/webpack/**",
|
||||||
|
- "rules": {
|
||||||
|
- "no-console": 1,
|
||||||
|
- "prefer-template": 0,
|
||||||
|
- "prefer-object-spread": 0,
|
||||||
|
- "prefer-rest-params": 0,
|
||||||
|
- },
|
||||||
|
- "env": {
|
||||||
|
- "es6": true,
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- {
|
||||||
|
- "files": [
|
||||||
|
- "resolvers/*/test/**/*",
|
||||||
|
- ],
|
||||||
|
- "env": {
|
||||||
|
- "mocha": true,
|
||||||
|
- "es6": false
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- {
|
||||||
|
- "files": "utils/**",
|
||||||
|
- "parserOptions": {
|
||||||
|
- "ecmaVersion": 6,
|
||||||
|
- },
|
||||||
|
- "rules": {
|
||||||
|
- "comma-dangle": ["error", {
|
||||||
|
- "arrays": "always-multiline",
|
||||||
|
- "objects": "always-multiline",
|
||||||
|
- "imports": "always-multiline",
|
||||||
|
- "exports": "always-multiline",
|
||||||
|
- "functions": "never"
|
||||||
|
- }],
|
||||||
|
- "prefer-object-spread": "off",
|
||||||
|
- "prefer-template": "off",
|
||||||
|
- "no-console": 1,
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- {
|
||||||
|
- "files": "tests/**",
|
||||||
|
- "env": {
|
||||||
|
- "mocha": true,
|
||||||
|
- },
|
||||||
|
- "rules": {
|
||||||
|
- "max-len": 0,
|
||||||
|
- "import/default": 0,
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- ],
|
||||||
|
-}
|
||||||
|
diff --git a/scripts/resolverDirectories.js b/scripts/resolverDirectories.js
|
||||||
|
index f0c03a3c..a7cadb55 100644
|
||||||
|
--- a/scripts/resolverDirectories.js
|
||||||
|
+++ b/scripts/resolverDirectories.js
|
||||||
|
@@ -1,3 +1,3 @@
|
||||||
|
-import glob from 'glob';
|
||||||
|
+import { globSync } from 'glob';
|
||||||
|
|
||||||
|
-export default glob.sync('./resolvers/*/');
|
||||||
|
+export default globSync('./resolvers/*/');
|
||||||
|
diff --git a/src/docsUrl.js b/src/docsUrl.js
|
||||||
|
index 92b838c0..ccb13ba0 100644
|
||||||
|
--- a/src/docsUrl.js
|
||||||
|
+++ b/src/docsUrl.js
|
||||||
|
@@ -1,7 +1,7 @@
|
||||||
|
-import pkg from '../package.json';
|
||||||
|
+import { version } from '../package.json';
|
||||||
|
|
||||||
|
const repoUrl = 'https://github.com/import-js/eslint-plugin-import';
|
||||||
|
|
||||||
|
-export default function docsUrl(ruleName, commitish = `v${pkg.version}`) {
|
||||||
|
+export default function docsUrl(ruleName, commitish = `v${version}`) {
|
||||||
|
return `${repoUrl}/blob/${commitish}/docs/rules/${ruleName}.md`;
|
||||||
|
}
|
||||||
|
diff --git a/src/index.js b/src/index.js
|
||||||
|
index feafba90..84992bef 100644
|
||||||
|
--- a/src/index.js
|
||||||
|
+++ b/src/index.js
|
||||||
|
@@ -1,71 +1,132 @@
|
||||||
|
-export const rules = {
|
||||||
|
- 'no-unresolved': require('./rules/no-unresolved'),
|
||||||
|
- named: require('./rules/named'),
|
||||||
|
- default: require('./rules/default'),
|
||||||
|
- namespace: require('./rules/namespace'),
|
||||||
|
- 'no-namespace': require('./rules/no-namespace'),
|
||||||
|
- export: require('./rules/export'),
|
||||||
|
- 'no-mutable-exports': require('./rules/no-mutable-exports'),
|
||||||
|
- extensions: require('./rules/extensions'),
|
||||||
|
- 'no-restricted-paths': require('./rules/no-restricted-paths'),
|
||||||
|
- 'no-internal-modules': require('./rules/no-internal-modules'),
|
||||||
|
- 'group-exports': require('./rules/group-exports'),
|
||||||
|
- 'no-relative-packages': require('./rules/no-relative-packages'),
|
||||||
|
- 'no-relative-parent-imports': require('./rules/no-relative-parent-imports'),
|
||||||
|
- 'consistent-type-specifier-style': require('./rules/consistent-type-specifier-style'),
|
||||||
|
+/* eslint-disable spaced-comment */
|
||||||
|
+import noUnresolved from './rules/no-unresolved';
|
||||||
|
+import named from './rules/named';
|
||||||
|
+import defaultRule from './rules/default';
|
||||||
|
+import namespace from './rules/namespace';
|
||||||
|
+import noNamespace from './rules/no-namespace';
|
||||||
|
+import exportRule from './rules/export';
|
||||||
|
+import noMutableExports from './rules/no-mutable-exports';
|
||||||
|
+import extensions from './rules/extensions';
|
||||||
|
+import noRestrictedPaths from './rules/no-restricted-paths';
|
||||||
|
+import noInternalModules from './rules/no-internal-modules';
|
||||||
|
+import groupExports from './rules/group-exports';
|
||||||
|
+import noRelativePackages from './rules/no-relative-packages';
|
||||||
|
+import noRelativeParentImports from './rules/no-relative-parent-imports';
|
||||||
|
+import consistentTypeSpecifierStyle from './rules/consistent-type-specifier-style';
|
||||||
|
+import noSelfImport from './rules/no-self-import';
|
||||||
|
+import noCycle from './rules/no-cycle';
|
||||||
|
+import noNamedDefault from './rules/no-named-default';
|
||||||
|
+import noNamedAsDefault from './rules/no-named-as-default';
|
||||||
|
+import noNamedAsDefaultMember from './rules/no-named-as-default-member';
|
||||||
|
+import noAnonymousDefaultExport from './rules/no-anonymous-default-export';
|
||||||
|
+import noUnusedModules from './rules/no-unused-modules';
|
||||||
|
+import noCommonjs from './rules/no-commonjs';
|
||||||
|
+import noAmd from './rules/no-amd';
|
||||||
|
+import noDuplicates from './rules/no-duplicates';
|
||||||
|
+import first from './rules/first';
|
||||||
|
+import maxDependencies from './rules/max-dependencies';
|
||||||
|
+import noExtraneousDependencies from './rules/no-extraneous-dependencies';
|
||||||
|
+import noAbsolutePath from './rules/no-absolute-path';
|
||||||
|
+import noNodejsModules from './rules/no-nodejs-modules';
|
||||||
|
+import noWebpackLoaderSyntax from './rules/no-webpack-loader-syntax';
|
||||||
|
+import order from './rules/order';
|
||||||
|
+import newlineAfterImport from './rules/newline-after-import';
|
||||||
|
+import preferDefaultExport from './rules/prefer-default-export';
|
||||||
|
+import noDefaultExport from './rules/no-default-export';
|
||||||
|
+import noNamedExport from './rules/no-named-export';
|
||||||
|
+import noDynamicRequire from './rules/no-dynamic-require';
|
||||||
|
+import unambiguous from './rules/unambiguous';
|
||||||
|
+import noUnassignedImport from './rules/no-unassigned-import';
|
||||||
|
+import noUselessPathSegments from './rules/no-useless-path-segments';
|
||||||
|
+import dynamicImportChunkname from './rules/dynamic-import-chunkname';
|
||||||
|
+import noImportModuleExports from './rules/no-import-module-exports';
|
||||||
|
+import noEmptyNamedBlocks from './rules/no-empty-named-blocks';
|
||||||
|
+import exportsLast from './rules/exports-last';
|
||||||
|
+import noDeprecated from './rules/no-deprecated';
|
||||||
|
+import importsFirst from './rules/imports-first';
|
||||||
|
+import recommended from '../config/recommended';
|
||||||
|
+import errors from '../config/errors';
|
||||||
|
+import warnings from '../config/warnings';
|
||||||
|
+import stage0 from '../config/stage-0';
|
||||||
|
+import react from '../config/react';
|
||||||
|
+import reactNative from '../config/react-native';
|
||||||
|
+import electron from '../config/electron';
|
||||||
|
+import typescript from '../config/typescript';
|
||||||
|
|
||||||
|
- 'no-self-import': require('./rules/no-self-import'),
|
||||||
|
- 'no-cycle': require('./rules/no-cycle'),
|
||||||
|
- 'no-named-default': require('./rules/no-named-default'),
|
||||||
|
- 'no-named-as-default': require('./rules/no-named-as-default'),
|
||||||
|
- 'no-named-as-default-member': require('./rules/no-named-as-default-member'),
|
||||||
|
- 'no-anonymous-default-export': require('./rules/no-anonymous-default-export'),
|
||||||
|
- 'no-unused-modules': require('./rules/no-unused-modules'),
|
||||||
|
+export const rules = /*#__PURE__*/ kebabCase({
|
||||||
|
+ noUnresolved,
|
||||||
|
+ named,
|
||||||
|
+ default: defaultRule,
|
||||||
|
+ namespace,
|
||||||
|
+ noNamespace,
|
||||||
|
+ export: exportRule,
|
||||||
|
+ noMutableExports,
|
||||||
|
+ extensions,
|
||||||
|
+ noRestrictedPaths,
|
||||||
|
+ noInternalModules,
|
||||||
|
+ groupExports,
|
||||||
|
+ noRelativePackages,
|
||||||
|
+ noRelativeParentImports,
|
||||||
|
+ consistentTypeSpecifierStyle,
|
||||||
|
|
||||||
|
- 'no-commonjs': require('./rules/no-commonjs'),
|
||||||
|
- 'no-amd': require('./rules/no-amd'),
|
||||||
|
- 'no-duplicates': require('./rules/no-duplicates'),
|
||||||
|
- first: require('./rules/first'),
|
||||||
|
- 'max-dependencies': require('./rules/max-dependencies'),
|
||||||
|
- 'no-extraneous-dependencies': require('./rules/no-extraneous-dependencies'),
|
||||||
|
- 'no-absolute-path': require('./rules/no-absolute-path'),
|
||||||
|
- 'no-nodejs-modules': require('./rules/no-nodejs-modules'),
|
||||||
|
- 'no-webpack-loader-syntax': require('./rules/no-webpack-loader-syntax'),
|
||||||
|
- order: require('./rules/order'),
|
||||||
|
- 'newline-after-import': require('./rules/newline-after-import'),
|
||||||
|
- 'prefer-default-export': require('./rules/prefer-default-export'),
|
||||||
|
- 'no-default-export': require('./rules/no-default-export'),
|
||||||
|
- 'no-named-export': require('./rules/no-named-export'),
|
||||||
|
- 'no-dynamic-require': require('./rules/no-dynamic-require'),
|
||||||
|
- unambiguous: require('./rules/unambiguous'),
|
||||||
|
- 'no-unassigned-import': require('./rules/no-unassigned-import'),
|
||||||
|
- 'no-useless-path-segments': require('./rules/no-useless-path-segments'),
|
||||||
|
- 'dynamic-import-chunkname': require('./rules/dynamic-import-chunkname'),
|
||||||
|
- 'no-import-module-exports': require('./rules/no-import-module-exports'),
|
||||||
|
- 'no-empty-named-blocks': require('./rules/no-empty-named-blocks'),
|
||||||
|
+ noSelfImport,
|
||||||
|
+ noCycle,
|
||||||
|
+ noNamedDefault,
|
||||||
|
+ noNamedAsDefault,
|
||||||
|
+ noNamedAsDefaultMember,
|
||||||
|
+ noAnonymousDefaultExport,
|
||||||
|
+ noUnusedModules,
|
||||||
|
+
|
||||||
|
+ noCommonjs,
|
||||||
|
+ noAmd,
|
||||||
|
+ noDuplicates,
|
||||||
|
+ first,
|
||||||
|
+ maxDependencies,
|
||||||
|
+ noExtraneousDependencies,
|
||||||
|
+ noAbsolutePath,
|
||||||
|
+ noNodejsModules,
|
||||||
|
+ noWebpackLoaderSyntax,
|
||||||
|
+ order,
|
||||||
|
+ newlineAfterImport,
|
||||||
|
+ preferDefaultExport,
|
||||||
|
+ noDefaultExport,
|
||||||
|
+ noNamedExport,
|
||||||
|
+ noDynamicRequire,
|
||||||
|
+ unambiguous,
|
||||||
|
+ noUnassignedImport,
|
||||||
|
+ noUselessPathSegments,
|
||||||
|
+ dynamicImportChunkname,
|
||||||
|
+ noImportModuleExports,
|
||||||
|
+ noEmptyNamedBlocks,
|
||||||
|
|
||||||
|
// export
|
||||||
|
- 'exports-last': require('./rules/exports-last'),
|
||||||
|
+ exportsLast,
|
||||||
|
|
||||||
|
// metadata-based
|
||||||
|
- 'no-deprecated': require('./rules/no-deprecated'),
|
||||||
|
+ noDeprecated,
|
||||||
|
|
||||||
|
// deprecated aliases to rules
|
||||||
|
- 'imports-first': require('./rules/imports-first'),
|
||||||
|
-};
|
||||||
|
+ importsFirst,
|
||||||
|
+});
|
||||||
|
|
||||||
|
-export const configs = {
|
||||||
|
- recommended: require('../config/recommended'),
|
||||||
|
+export const configs = /*#__PURE__*/ kebabCase({
|
||||||
|
+ recommended,
|
||||||
|
|
||||||
|
- errors: require('../config/errors'),
|
||||||
|
- warnings: require('../config/warnings'),
|
||||||
|
+ errors,
|
||||||
|
+ warnings,
|
||||||
|
|
||||||
|
// shhhh... work in progress "secret" rules
|
||||||
|
- 'stage-0': require('../config/stage-0'),
|
||||||
|
+ 'stage-0': stage0,
|
||||||
|
|
||||||
|
// useful stuff for folks using various environments
|
||||||
|
- react: require('../config/react'),
|
||||||
|
- 'react-native': require('../config/react-native'),
|
||||||
|
- electron: require('../config/electron'),
|
||||||
|
- typescript: require('../config/typescript'),
|
||||||
|
-};
|
||||||
|
+ react,
|
||||||
|
+ reactNative,
|
||||||
|
+ electron,
|
||||||
|
+ typescript,
|
||||||
|
+});
|
||||||
|
+
|
||||||
|
+function kebabCase(obj) {
|
||||||
|
+ return Object.fromEntries(
|
||||||
|
+ Object.entries(obj).map(([key, value]) => [key.replace(/([A-Z])/g, '-$1').toLowerCase(), value])
|
||||||
|
+ )
|
||||||
|
+}
|
||||||
|
\ No newline at end of file
|
144
patch/eslint-plugin-jsx-a11y.patch
Normal file
144
patch/eslint-plugin-jsx-a11y.patch
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
diff --git a/src/index.js b/src/index.js
|
||||||
|
index 7b931fe..f7c1f91 100644
|
||||||
|
--- a/src/index.js
|
||||||
|
+++ b/src/index.js
|
||||||
|
@@ -1,47 +1,87 @@
|
||||||
|
/* eslint-disable global-require */
|
||||||
|
+// @ts-check
|
||||||
|
+import accessibleEmoji from './rules/accessible-emoji';
|
||||||
|
+import altText from './rules/alt-text';
|
||||||
|
+import anchorAmbiguousText from './rules/anchor-ambiguous-text';
|
||||||
|
+import anchorHasContent from './rules/anchor-has-content';
|
||||||
|
+import anchorIsValid from './rules/anchor-is-valid';
|
||||||
|
+import ariaActivedescendantHasTabindex from './rules/aria-activedescendant-has-tabindex';
|
||||||
|
+import ariaProps from './rules/aria-props';
|
||||||
|
+import ariaProptypes from './rules/aria-proptypes';
|
||||||
|
+import ariaRole from './rules/aria-role';
|
||||||
|
+import ariaUnsupportedElements from './rules/aria-unsupported-elements';
|
||||||
|
+import autocompleteValid from './rules/autocomplete-valid';
|
||||||
|
+import clickEventsHaveKeyEvents from './rules/click-events-have-key-events';
|
||||||
|
+import controlHasAssociatedLabel from './rules/control-has-associated-label';
|
||||||
|
+import headingHasContent from './rules/heading-has-content';
|
||||||
|
+import htmlHasLang from './rules/html-has-lang';
|
||||||
|
+import iframeHasTitle from './rules/iframe-has-title';
|
||||||
|
+import imgRedundantAlt from './rules/img-redundant-alt';
|
||||||
|
+import interactiveSupportsFocus from './rules/interactive-supports-focus';
|
||||||
|
+import labelHasAssociatedControl from './rules/label-has-associated-control';
|
||||||
|
+import labelHasFor from './rules/label-has-for';
|
||||||
|
+import lang from './rules/lang';
|
||||||
|
+import mediaHasCaption from './rules/media-has-caption';
|
||||||
|
+import mouseEventsHaveKeyEvents from './rules/mouse-events-have-key-events';
|
||||||
|
+import noAriaHiddenOnFocusable from './rules/no-aria-hidden-on-focusable';
|
||||||
|
+import noAccessKey from './rules/no-access-key';
|
||||||
|
+import noAutofocus from './rules/no-autofocus';
|
||||||
|
+import noDistractingElements from './rules/no-distracting-elements';
|
||||||
|
+import noInteractiveElementToNoninteractiveRole from './rules/no-interactive-element-to-noninteractive-role';
|
||||||
|
+import noNoninteractiveElementInteractions from './rules/no-noninteractive-element-interactions';
|
||||||
|
+import noNoninteractiveElementToInteractiveRole from './rules/no-noninteractive-element-to-interactive-role';
|
||||||
|
+import noNoninteractiveTabindex from './rules/no-noninteractive-tabindex';
|
||||||
|
+import noOnChange from './rules/no-onchange';
|
||||||
|
+import noRedundantRoles from './rules/no-redundant-roles';
|
||||||
|
+import noStaticElementInteractions from './rules/no-static-element-interactions';
|
||||||
|
+import preferTagOverRole from './rules/prefer-tag-over-role';
|
||||||
|
+import roleHasRequiredAriaProps from './rules/role-has-required-aria-props';
|
||||||
|
+import roleSupportsAriaProps from './rules/role-supports-aria-props';
|
||||||
|
+import scope from './rules/scope';
|
||||||
|
+import tabindexNoPositive from './rules/tabindex-no-positive';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
- rules: {
|
||||||
|
- 'accessible-emoji': require('./rules/accessible-emoji'),
|
||||||
|
- 'alt-text': require('./rules/alt-text'),
|
||||||
|
- 'anchor-ambiguous-text': require('./rules/anchor-ambiguous-text'),
|
||||||
|
- 'anchor-has-content': require('./rules/anchor-has-content'),
|
||||||
|
- 'anchor-is-valid': require('./rules/anchor-is-valid'),
|
||||||
|
- 'aria-activedescendant-has-tabindex': require('./rules/aria-activedescendant-has-tabindex'),
|
||||||
|
- 'aria-props': require('./rules/aria-props'),
|
||||||
|
- 'aria-proptypes': require('./rules/aria-proptypes'),
|
||||||
|
- 'aria-role': require('./rules/aria-role'),
|
||||||
|
- 'aria-unsupported-elements': require('./rules/aria-unsupported-elements'),
|
||||||
|
- 'autocomplete-valid': require('./rules/autocomplete-valid'),
|
||||||
|
- 'click-events-have-key-events': require('./rules/click-events-have-key-events'),
|
||||||
|
- 'control-has-associated-label': require('./rules/control-has-associated-label'),
|
||||||
|
- 'heading-has-content': require('./rules/heading-has-content'),
|
||||||
|
- 'html-has-lang': require('./rules/html-has-lang'),
|
||||||
|
- 'iframe-has-title': require('./rules/iframe-has-title'),
|
||||||
|
- 'img-redundant-alt': require('./rules/img-redundant-alt'),
|
||||||
|
- 'interactive-supports-focus': require('./rules/interactive-supports-focus'),
|
||||||
|
- 'label-has-associated-control': require('./rules/label-has-associated-control'),
|
||||||
|
- 'label-has-for': require('./rules/label-has-for'),
|
||||||
|
- lang: require('./rules/lang'),
|
||||||
|
- 'media-has-caption': require('./rules/media-has-caption'),
|
||||||
|
- 'mouse-events-have-key-events': require('./rules/mouse-events-have-key-events'),
|
||||||
|
- 'no-access-key': require('./rules/no-access-key'),
|
||||||
|
- 'no-aria-hidden-on-focusable': require('./rules/no-aria-hidden-on-focusable'),
|
||||||
|
- 'no-autofocus': require('./rules/no-autofocus'),
|
||||||
|
- 'no-distracting-elements': require('./rules/no-distracting-elements'),
|
||||||
|
- 'no-interactive-element-to-noninteractive-role': require('./rules/no-interactive-element-to-noninteractive-role'),
|
||||||
|
- 'no-noninteractive-element-interactions': require('./rules/no-noninteractive-element-interactions'),
|
||||||
|
- 'no-noninteractive-element-to-interactive-role': require('./rules/no-noninteractive-element-to-interactive-role'),
|
||||||
|
- 'no-noninteractive-tabindex': require('./rules/no-noninteractive-tabindex'),
|
||||||
|
- 'no-onchange': require('./rules/no-onchange'),
|
||||||
|
- 'no-redundant-roles': require('./rules/no-redundant-roles'),
|
||||||
|
- 'no-static-element-interactions': require('./rules/no-static-element-interactions'),
|
||||||
|
- 'prefer-tag-over-role': require('./rules/prefer-tag-over-role'),
|
||||||
|
- 'role-has-required-aria-props': require('./rules/role-has-required-aria-props'),
|
||||||
|
- 'role-supports-aria-props': require('./rules/role-supports-aria-props'),
|
||||||
|
- scope: require('./rules/scope'),
|
||||||
|
- 'tabindex-no-positive': require('./rules/tabindex-no-positive'),
|
||||||
|
- },
|
||||||
|
+ rules: kebabCase({
|
||||||
|
+ accessibleEmoji,
|
||||||
|
+ altText,
|
||||||
|
+ anchorAmbiguousText,
|
||||||
|
+ anchorHasContent,
|
||||||
|
+ anchorIsValid,
|
||||||
|
+ ariaActivedescendantHasTabindex,
|
||||||
|
+ ariaProps,
|
||||||
|
+ ariaProptypes,
|
||||||
|
+ ariaRole,
|
||||||
|
+ ariaUnsupportedElements,
|
||||||
|
+ autocompleteValid,
|
||||||
|
+ clickEventsHaveKeyEvents,
|
||||||
|
+ controlHasAssociatedLabel,
|
||||||
|
+ headingHasContent,
|
||||||
|
+ htmlHasLang,
|
||||||
|
+ iframeHasTitle,
|
||||||
|
+ imgRedundantAlt,
|
||||||
|
+ interactiveSupportsFocus,
|
||||||
|
+ labelHasAssociatedControl,
|
||||||
|
+ labelHasFor,
|
||||||
|
+ lang,
|
||||||
|
+ mediaHasCaption,
|
||||||
|
+ mouseEventsHaveKeyEvents,
|
||||||
|
+ noAccessKey,
|
||||||
|
+ noAriaHiddenOnFocusable,
|
||||||
|
+ noAutofocus,
|
||||||
|
+ noDistractingElements,
|
||||||
|
+ noInteractiveElementToNoninteractiveRole,
|
||||||
|
+ noNoninteractiveElementInteractions,
|
||||||
|
+ noNoninteractiveElementToInteractiveRole,
|
||||||
|
+ noNoninteractiveTabindex,
|
||||||
|
+ noOnChange,
|
||||||
|
+ noRedundantRoles,
|
||||||
|
+ noStaticElementInteractions,
|
||||||
|
+ preferTagOverRole,
|
||||||
|
+ roleHasRequiredAriaProps,
|
||||||
|
+ roleSupportsAriaProps,
|
||||||
|
+ scope,
|
||||||
|
+ tabindexNoPositive,
|
||||||
|
+ }),
|
||||||
|
configs: {
|
||||||
|
recommended: {
|
||||||
|
plugins: [
|
||||||
|
@@ -294,3 +334,9 @@ module.exports = {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
+
|
||||||
|
+function kebabCase(obj) {
|
||||||
|
+ return Object.fromEntries(
|
||||||
|
+ Object.entries(obj).map(([key, value]) => [key.replace(/([A-Z])/g, '-$1').toLowerCase(), value])
|
||||||
|
+ )
|
||||||
|
+}
|
||||||
|
\ No newline at end of file
|
310
patch/eslint-plugin-react.patch
Normal file
310
patch/eslint-plugin-react.patch
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
diff --git a/.eslintrc b/.eslintrc
|
||||||
|
deleted file mode 100644
|
||||||
|
index 4991f200..00000000
|
||||||
|
--- a/.eslintrc
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,82 +0,0 @@
|
||||||
|
-{
|
||||||
|
- "root": true,
|
||||||
|
- "extends": ["airbnb-base", "plugin:eslint-plugin/recommended"],
|
||||||
|
- "plugins": ["eslint-plugin"],
|
||||||
|
- "env": {
|
||||||
|
- "es6": true,
|
||||||
|
- "node": true
|
||||||
|
- },
|
||||||
|
- "parserOptions": {
|
||||||
|
- "ecmaVersion": 6,
|
||||||
|
- "ecmaFeatures": {
|
||||||
|
- "jsx": true
|
||||||
|
- },
|
||||||
|
- "sourceType": "script",
|
||||||
|
- },
|
||||||
|
- "ignorePatterns": [
|
||||||
|
- "coverage/",
|
||||||
|
- ".nyc_output/",
|
||||||
|
- ],
|
||||||
|
- "rules": {
|
||||||
|
- "comma-dangle": [2, "always-multiline"],
|
||||||
|
- "object-shorthand": [2, "always", {
|
||||||
|
- "ignoreConstructors": false,
|
||||||
|
- "avoidQuotes": false, // this is the override vs airbnb
|
||||||
|
- }],
|
||||||
|
- "max-len": [2, 120, {
|
||||||
|
- "ignoreStrings": true,
|
||||||
|
- "ignoreTemplateLiterals": true,
|
||||||
|
- "ignoreComments": true,
|
||||||
|
- }],
|
||||||
|
- "consistent-return": 0,
|
||||||
|
-
|
||||||
|
- "prefer-destructuring": [2, { "array": false, "object": false }, { "enforceForRenamedProperties": false }],
|
||||||
|
- "prefer-object-spread": 0, // until node 8 is required
|
||||||
|
- "prefer-rest-params": 0, // until node 6 is required
|
||||||
|
- "prefer-spread": 0, // until node 6 is required
|
||||||
|
- "function-call-argument-newline": 1, // TODO: enable
|
||||||
|
- "function-paren-newline": 0,
|
||||||
|
- "no-plusplus": [2, {"allowForLoopAfterthoughts": true}],
|
||||||
|
- "no-param-reassign": 1,
|
||||||
|
- "no-restricted-syntax": [2, {
|
||||||
|
- "selector": "ObjectPattern",
|
||||||
|
- "message": "Object destructuring is not compatible with Node v4"
|
||||||
|
- }],
|
||||||
|
- "strict": [2, "safe"],
|
||||||
|
- "valid-jsdoc": [2, {
|
||||||
|
- "requireReturn": false,
|
||||||
|
- "requireParamDescription": false,
|
||||||
|
- "requireReturnDescription": false,
|
||||||
|
- }],
|
||||||
|
-
|
||||||
|
- "eslint-plugin/consistent-output": 0,
|
||||||
|
- "eslint-plugin/require-meta-docs-description": [2, { "pattern": "^(Enforce|Require|Disallow)" }],
|
||||||
|
- "eslint-plugin/require-meta-schema": 0,
|
||||||
|
- "eslint-plugin/require-meta-type": 0
|
||||||
|
- },
|
||||||
|
- "overrides": [
|
||||||
|
- {
|
||||||
|
- "files": "tests/**",
|
||||||
|
- "rules": {
|
||||||
|
- "no-template-curly-in-string": 1,
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- {
|
||||||
|
- "files": "markdown.config.js",
|
||||||
|
- "rules": {
|
||||||
|
- "no-console": 0,
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- {
|
||||||
|
- "files": ".github/workflows/*.js",
|
||||||
|
- "parserOptions": {
|
||||||
|
- "ecmaVersion": 2019,
|
||||||
|
- },
|
||||||
|
- "rules": {
|
||||||
|
- "camelcase": 0,
|
||||||
|
- "no-console": 0,
|
||||||
|
- "no-restricted-syntax": 0,
|
||||||
|
- },
|
||||||
|
- },
|
||||||
|
- ],
|
||||||
|
-}
|
||||||
|
diff --git a/index.js b/index.js
|
||||||
|
index 4140c6c8..792ceb4f 100644
|
||||||
|
--- a/index.js
|
||||||
|
+++ b/index.js
|
||||||
|
@@ -1,15 +1,13 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const configAll = require('./configs/all');
|
||||||
|
-const configRecommended = require('./configs/recommended');
|
||||||
|
-const configRuntime = require('./configs/jsx-runtime');
|
||||||
|
-
|
||||||
|
-const allRules = require('./lib/rules');
|
||||||
|
+import configAll from './configs/all';
|
||||||
|
+import configRecommended from './configs/recommended';
|
||||||
|
+import configRuntime from './configs/jsx-runtime';
|
||||||
|
+import { name } from './package.json';
|
||||||
|
+import allRules from './lib/rules';
|
||||||
|
|
||||||
|
// for legacy config system
|
||||||
|
-const plugins = [
|
||||||
|
- 'react',
|
||||||
|
-];
|
||||||
|
+const plugins = [name];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
deprecatedRules: configAll.plugins.react.deprecatedRules,
|
||||||
|
diff --git a/lib/rules/button-has-type.js b/lib/rules/button-has-type.js
|
||||||
|
index 204a33c4..01d992c2 100644
|
||||||
|
--- a/lib/rules/button-has-type.js
|
||||||
|
+++ b/lib/rules/button-has-type.js
|
||||||
|
@@ -5,8 +5,7 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const getProp = require('jsx-ast-utils/getProp');
|
||||||
|
-const getLiteralPropValue = require('jsx-ast-utils/getLiteralPropValue');
|
||||||
|
+const { getProp, getLiteralPropValue } = require('jsx-ast-utils');
|
||||||
|
const docsUrl = require('../util/docsUrl');
|
||||||
|
const isCreateElement = require('../util/isCreateElement');
|
||||||
|
const report = require('../util/report');
|
||||||
|
diff --git a/lib/rules/jsx-fragments.js b/lib/rules/jsx-fragments.js
|
||||||
|
index 38b4dd8b..d0575572 100644
|
||||||
|
--- a/lib/rules/jsx-fragments.js
|
||||||
|
+++ b/lib/rules/jsx-fragments.js
|
||||||
|
@@ -5,7 +5,7 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const elementType = require('jsx-ast-utils/elementType');
|
||||||
|
+import { elementType } from 'jsx-ast-utils';
|
||||||
|
const pragmaUtil = require('../util/pragma');
|
||||||
|
const variableUtil = require('../util/variable');
|
||||||
|
const testReactVersion = require('../util/version').testReactVersion;
|
||||||
|
diff --git a/lib/rules/jsx-key.js b/lib/rules/jsx-key.js
|
||||||
|
index 263ed824..e413ef5d 100644
|
||||||
|
--- a/lib/rules/jsx-key.js
|
||||||
|
+++ b/lib/rules/jsx-key.js
|
||||||
|
@@ -5,8 +5,7 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const hasProp = require('jsx-ast-utils/hasProp');
|
||||||
|
-const propName = require('jsx-ast-utils/propName');
|
||||||
|
+import { hasProp, propName } from 'jsx-ast-utils';
|
||||||
|
const values = require('object.values');
|
||||||
|
const docsUrl = require('../util/docsUrl');
|
||||||
|
const pragmaUtil = require('../util/pragma');
|
||||||
|
diff --git a/lib/rules/jsx-no-bind.js b/lib/rules/jsx-no-bind.js
|
||||||
|
index 0a4c38e6..75a57bd9 100644
|
||||||
|
--- a/lib/rules/jsx-no-bind.js
|
||||||
|
+++ b/lib/rules/jsx-no-bind.js
|
||||||
|
@@ -7,7 +7,7 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const propName = require('jsx-ast-utils/propName');
|
||||||
|
+import { propName } from 'jsx-ast-utils';
|
||||||
|
const docsUrl = require('../util/docsUrl');
|
||||||
|
const jsxUtil = require('../util/jsx');
|
||||||
|
const report = require('../util/report');
|
||||||
|
diff --git a/lib/rules/jsx-pascal-case.js b/lib/rules/jsx-pascal-case.js
|
||||||
|
index a1bb4811..db051356 100644
|
||||||
|
--- a/lib/rules/jsx-pascal-case.js
|
||||||
|
+++ b/lib/rules/jsx-pascal-case.js
|
||||||
|
@@ -5,7 +5,7 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const elementType = require('jsx-ast-utils/elementType');
|
||||||
|
+import { elementType } from 'jsx-ast-utils';
|
||||||
|
const minimatch = require('minimatch');
|
||||||
|
const docsUrl = require('../util/docsUrl');
|
||||||
|
const jsxUtil = require('../util/jsx');
|
||||||
|
diff --git a/lib/rules/jsx-sort-props.js b/lib/rules/jsx-sort-props.js
|
||||||
|
index 5de5bee1..8013a8de 100644
|
||||||
|
--- a/lib/rules/jsx-sort-props.js
|
||||||
|
+++ b/lib/rules/jsx-sort-props.js
|
||||||
|
@@ -5,7 +5,7 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const propName = require('jsx-ast-utils/propName');
|
||||||
|
+import { propName } from 'jsx-ast-utils';
|
||||||
|
const includes = require('array-includes');
|
||||||
|
const toSorted = require('array.prototype.tosorted');
|
||||||
|
|
||||||
|
diff --git a/lib/rules/no-namespace.js b/lib/rules/no-namespace.js
|
||||||
|
index 64bbc8d5..b5e9c803 100644
|
||||||
|
--- a/lib/rules/no-namespace.js
|
||||||
|
+++ b/lib/rules/no-namespace.js
|
||||||
|
@@ -5,7 +5,7 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const elementType = require('jsx-ast-utils/elementType');
|
||||||
|
+import { elementType } from 'jsx-ast-utils';
|
||||||
|
const docsUrl = require('../util/docsUrl');
|
||||||
|
const isCreateElement = require('../util/isCreateElement');
|
||||||
|
const report = require('../util/report');
|
||||||
|
diff --git a/lib/util/annotations.js b/lib/util/annotations.js
|
||||||
|
index 60aaef8c..ad8dc0bf 100644
|
||||||
|
--- a/lib/util/annotations.js
|
||||||
|
+++ b/lib/util/annotations.js
|
||||||
|
@@ -27,6 +27,6 @@ function isAnnotatedFunctionPropsDeclaration(node, context) {
|
||||||
|
return (isAnnotated && (isDestructuredProps || isProps));
|
||||||
|
}
|
||||||
|
|
||||||
|
-module.exports = {
|
||||||
|
+export {
|
||||||
|
isAnnotatedFunctionPropsDeclaration,
|
||||||
|
};
|
||||||
|
diff --git a/lib/util/ast.js b/lib/util/ast.js
|
||||||
|
index fd6019a3..3cbc293e 100644
|
||||||
|
--- a/lib/util/ast.js
|
||||||
|
+++ b/lib/util/ast.js
|
||||||
|
@@ -4,7 +4,7 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const estraverse = require('estraverse');
|
||||||
|
+import estraverse from 'estraverse';
|
||||||
|
// const pragmaUtil = require('./pragma');
|
||||||
|
|
||||||
|
/**
|
||||||
|
@@ -428,7 +428,7 @@ function isTSTypeParameterInstantiation(node) {
|
||||||
|
return nodeType === 'TSTypeParameterInstantiation';
|
||||||
|
}
|
||||||
|
|
||||||
|
-module.exports = {
|
||||||
|
+export {
|
||||||
|
traverse,
|
||||||
|
findReturnStatement,
|
||||||
|
getFirstNodeInLine,
|
||||||
|
diff --git a/lib/util/jsx.js b/lib/util/jsx.js
|
||||||
|
index 55073bfe..efc07af1 100644
|
||||||
|
--- a/lib/util/jsx.js
|
||||||
|
+++ b/lib/util/jsx.js
|
||||||
|
@@ -4,7 +4,7 @@
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
-const elementType = require('jsx-ast-utils/elementType');
|
||||||
|
+import { elementType } from 'jsx-ast-utils';
|
||||||
|
|
||||||
|
const astUtil = require('./ast');
|
||||||
|
const isCreateElement = require('./isCreateElement');
|
||||||
|
diff --git a/package.json b/package.json
|
||||||
|
index cb736434..a97113c0 100644
|
||||||
|
--- a/package.json
|
||||||
|
+++ b/package.json
|
||||||
|
@@ -25,21 +25,13 @@
|
||||||
|
"homepage": "https://github.com/jsx-eslint/eslint-plugin-react",
|
||||||
|
"bugs": "https://github.com/jsx-eslint/eslint-plugin-react/issues",
|
||||||
|
"dependencies": {
|
||||||
|
- "array-includes": "^3.1.6",
|
||||||
|
- "array.prototype.flatmap": "^1.3.1",
|
||||||
|
- "array.prototype.tosorted": "^1.1.1",
|
||||||
|
"doctrine": "^2.1.0",
|
||||||
|
"estraverse": "^5.3.0",
|
||||||
|
"jsx-ast-utils": "^2.4.1 || ^3.0.0",
|
||||||
|
"minimatch": "^3.1.2",
|
||||||
|
- "object.entries": "^1.1.6",
|
||||||
|
- "object.fromentries": "^2.0.6",
|
||||||
|
- "object.hasown": "^1.1.2",
|
||||||
|
- "object.values": "^1.1.6",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
|
"resolve": "^2.0.0-next.4",
|
||||||
|
- "semver": "^6.3.0",
|
||||||
|
- "string.prototype.matchall": "^4.0.8"
|
||||||
|
+ "semver": "^6.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.21.0",
|
||||||
|
diff --git a/tsconfig.json b/tsconfig.json
|
||||||
|
deleted file mode 100644
|
||||||
|
index 39187b7f..00000000
|
||||||
|
--- a/tsconfig.json
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,23 +0,0 @@
|
||||||
|
-{
|
||||||
|
- "compilerOptions": {
|
||||||
|
- "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||||
|
- "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||||
|
- // "lib": ["es2015"], /* Specify library files to be included in the compilation. */
|
||||||
|
- "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
- "checkJs": true, /* Report errors in .js files. */
|
||||||
|
- "noEmit": true, /* Do not emit outputs. */
|
||||||
|
- "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
-
|
||||||
|
- /* Strict Type-Checking Options */
|
||||||
|
- // "strict": true, /* Enable all strict type-checking options. */
|
||||||
|
- "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
- // "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
- // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
- "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
- "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
- "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
- "alwaysStrict": false, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
- "resolveJsonModule": true
|
||||||
|
- },
|
||||||
|
- "include": ["lib"],
|
||||||
|
-}
|
459
patch/jsx-ast-utils.patch
Normal file
459
patch/jsx-ast-utils.patch
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
diff --git a/elementType.js b/elementType.js
|
||||||
|
index 96873ac..c7b909a 100644
|
||||||
|
--- a/elementType.js
|
||||||
|
+++ b/elementType.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').elementType; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { elementType as default } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/eventHandlers.js b/eventHandlers.js
|
||||||
|
index b5d7f62..208eaca 100644
|
||||||
|
--- a/eventHandlers.js
|
||||||
|
+++ b/eventHandlers.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').eventHandlers; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { eventHandlers as default } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/eventHandlersByType.js b/eventHandlersByType.js
|
||||||
|
index b7a0c42..e2757e9 100644
|
||||||
|
--- a/eventHandlersByType.js
|
||||||
|
+++ b/eventHandlersByType.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').eventHandlersByType; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { eventHandlersByType as default } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/getLiteralPropValue.js b/getLiteralPropValue.js
|
||||||
|
index efc5c8d..970fc39 100644
|
||||||
|
--- a/getLiteralPropValue.js
|
||||||
|
+++ b/getLiteralPropValue.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').getLiteralPropValue; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { getLiteralPropValue as default } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/getProp.js b/getProp.js
|
||||||
|
index e523c2c..4e270e9 100644
|
||||||
|
--- a/getProp.js
|
||||||
|
+++ b/getProp.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').getProp; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { getProp as default } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/getPropValue.js b/getPropValue.js
|
||||||
|
index 883a37c..86bf585 100644
|
||||||
|
--- a/getPropValue.js
|
||||||
|
+++ b/getPropValue.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').getPropValue; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { getPropValue as default } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/hasAnyProp.js b/hasAnyProp.js
|
||||||
|
index 2a88420..95b3adc 100644
|
||||||
|
--- a/hasAnyProp.js
|
||||||
|
+++ b/hasAnyProp.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').hasAnyProp; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { hasAnyProp as default } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/hasEveryProp.js b/hasEveryProp.js
|
||||||
|
index 338f569..e865100 100644
|
||||||
|
--- a/hasEveryProp.js
|
||||||
|
+++ b/hasEveryProp.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').hasEveryProp; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { hasEveryProp } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/hasProp.js b/hasProp.js
|
||||||
|
index 0f1cd04..bfed363 100644
|
||||||
|
--- a/hasProp.js
|
||||||
|
+++ b/hasProp.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').hasProp; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { hasProp } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/package.json b/package.json
|
||||||
|
index 738d1b3..befb66e 100644
|
||||||
|
--- a/package.json
|
||||||
|
+++ b/package.json
|
||||||
|
@@ -2,7 +2,7 @@
|
||||||
|
"name": "jsx-ast-utils",
|
||||||
|
"version": "3.3.4",
|
||||||
|
"description": "AST utility module for statically analyzing JSX",
|
||||||
|
- "main": "lib/index.js",
|
||||||
|
+ "main": "src/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"prepack": "npmignore --auto --commentLines=autogenerated && npm run build",
|
||||||
|
"prebuild": "rimraf lib",
|
||||||
|
@@ -62,8 +62,6 @@
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
- "array-includes": "^3.1.6",
|
||||||
|
- "array.prototype.flat": "^1.3.1",
|
||||||
|
"object.assign": "^4.1.4",
|
||||||
|
"object.values": "^1.1.6"
|
||||||
|
},
|
||||||
|
diff --git a/propName.js b/propName.js
|
||||||
|
index 361a52b..4424cfe 100644
|
||||||
|
--- a/propName.js
|
||||||
|
+++ b/propName.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-module.exports = require('./lib').propName; // eslint-disable-line import/no-unresolved
|
||||||
|
+export { propName } from './src';
|
||||||
|
\ No newline at end of file
|
||||||
|
diff --git a/src/index.js b/src/index.js
|
||||||
|
index 6814764..565a66e 100644
|
||||||
|
--- a/src/index.js
|
||||||
|
+++ b/src/index.js
|
||||||
|
@@ -5,7 +5,7 @@ import getProp from './getProp';
|
||||||
|
import getPropValue, { getLiteralPropValue } from './getPropValue';
|
||||||
|
import propName from './propName';
|
||||||
|
|
||||||
|
-module.exports = {
|
||||||
|
+export {
|
||||||
|
hasProp,
|
||||||
|
hasAnyProp,
|
||||||
|
hasEveryProp,
|
||||||
|
diff --git a/src/values/JSXElement.js b/src/values/JSXElement.js
|
||||||
|
index e5a049f..029c23d 100644
|
||||||
|
--- a/src/values/JSXElement.js
|
||||||
|
+++ b/src/values/JSXElement.js
|
||||||
|
@@ -1,12 +1,11 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a JSXElement type value node.
|
||||||
|
*
|
||||||
|
* Returns self-closing element with correct name.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromJSXElement(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
-
|
||||||
|
const Tag = value.openingElement.name.name;
|
||||||
|
if (value.openingElement.selfClosing) {
|
||||||
|
return `<${Tag} />`;
|
||||||
|
diff --git a/src/values/JSXFragment.js b/src/values/JSXFragment.js
|
||||||
|
index 379137e..90cbc54 100644
|
||||||
|
--- a/src/values/JSXFragment.js
|
||||||
|
+++ b/src/values/JSXFragment.js
|
||||||
|
@@ -1,12 +1,11 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a JSXFragment type value node.
|
||||||
|
*
|
||||||
|
* Returns self-closing element with correct name.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromJSXFragment(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
-
|
||||||
|
if (value.children.length === 0) {
|
||||||
|
return '<></>';
|
||||||
|
}
|
||||||
|
diff --git a/src/values/expressions/ArrayExpression.js b/src/values/expressions/ArrayExpression.js
|
||||||
|
index f333d58..4462c9d 100644
|
||||||
|
--- a/src/values/expressions/ArrayExpression.js
|
||||||
|
+++ b/src/values/expressions/ArrayExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for an ArrayExpression type value node.
|
||||||
|
* An array expression is an expression with [] syntax.
|
||||||
|
@@ -5,8 +7,6 @@
|
||||||
|
* @returns - An array of the extracted elements.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromArrayExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
return value.elements.map((element) => {
|
||||||
|
if (element === null) return undefined;
|
||||||
|
return getValue(element);
|
||||||
|
diff --git a/src/values/expressions/AssignmentExpression.js b/src/values/expressions/AssignmentExpression.js
|
||||||
|
index 7a14de5..6fce401 100644
|
||||||
|
--- a/src/values/expressions/AssignmentExpression.js
|
||||||
|
+++ b/src/values/expressions/AssignmentExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a AssignmentExpression type value node.
|
||||||
|
* An assignment expression looks like `x = y` or `x += y` in expression position.
|
||||||
|
@@ -7,7 +9,5 @@
|
||||||
|
* @returns - The extracted value converted to correct type.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromAssignmentExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
return `${getValue(value.left)} ${value.operator} ${getValue(value.right)}`;
|
||||||
|
}
|
||||||
|
diff --git a/src/values/expressions/BinaryExpression.js b/src/values/expressions/BinaryExpression.js
|
||||||
|
index 5764f3f..7cf9c07 100644
|
||||||
|
--- a/src/values/expressions/BinaryExpression.js
|
||||||
|
+++ b/src/values/expressions/BinaryExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a BinaryExpression type value node.
|
||||||
|
* A binary expression has a left and right side separated by an operator
|
||||||
|
@@ -7,8 +9,6 @@
|
||||||
|
* @returns - The extracted value converted to correct type.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromBinaryExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
const { operator, left, right } = value;
|
||||||
|
const leftVal = getValue(left);
|
||||||
|
const rightVal = getValue(right);
|
||||||
|
diff --git a/src/values/expressions/BindExpression.js b/src/values/expressions/BindExpression.js
|
||||||
|
index bc0bf0f..6c10fa4 100644
|
||||||
|
--- a/src/values/expressions/BindExpression.js
|
||||||
|
+++ b/src/values/expressions/BindExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a BindExpression type value node.
|
||||||
|
* A bind expression looks like `::this.foo`
|
||||||
|
@@ -8,8 +10,6 @@
|
||||||
|
* @returns - The extracted value converted to correct type.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromBindExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
const callee = getValue(value.callee);
|
||||||
|
|
||||||
|
// If value.object === null, the callee must be a MemberExpression.
|
||||||
|
diff --git a/src/values/expressions/CallExpression.js b/src/values/expressions/CallExpression.js
|
||||||
|
index f225d16..ce7e43d 100644
|
||||||
|
--- a/src/values/expressions/CallExpression.js
|
||||||
|
+++ b/src/values/expressions/CallExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a CallExpression type value node.
|
||||||
|
* A call expression looks like `bar()`
|
||||||
|
@@ -8,8 +10,6 @@
|
||||||
|
* @returns - The extracted value converted to correct type.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromCallExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
const args = Array.isArray(value.arguments) ? value.arguments.map((x) => getValue(x)).join(', ') : '';
|
||||||
|
return `${getValue(value.callee)}${value.optional ? '?.' : ''}(${args})`;
|
||||||
|
}
|
||||||
|
diff --git a/src/values/expressions/ChainExpression.js b/src/values/expressions/ChainExpression.js
|
||||||
|
index 1fb00a7..e8b3b75 100644
|
||||||
|
--- a/src/values/expressions/ChainExpression.js
|
||||||
|
+++ b/src/values/expressions/ChainExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a ChainExpression type value node.
|
||||||
|
* A member expression is accessing a property on an object `obj.property`.
|
||||||
|
@@ -7,7 +9,5 @@
|
||||||
|
* and maintaing `obj?.property` convention.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromChainExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
return getValue(value.expression || value);
|
||||||
|
}
|
||||||
|
diff --git a/src/values/expressions/ConditionalExpression.js b/src/values/expressions/ConditionalExpression.js
|
||||||
|
index 4fd185d..a3308f6 100644
|
||||||
|
--- a/src/values/expressions/ConditionalExpression.js
|
||||||
|
+++ b/src/values/expressions/ConditionalExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a ConditionalExpression type value node.
|
||||||
|
*
|
||||||
|
@@ -5,8 +7,6 @@
|
||||||
|
* @returns - The extracted value converted to correct type.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromConditionalExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
const {
|
||||||
|
test,
|
||||||
|
alternate,
|
||||||
|
diff --git a/src/values/expressions/LogicalExpression.js b/src/values/expressions/LogicalExpression.js
|
||||||
|
index 5b65a4e..b5cc573 100644
|
||||||
|
--- a/src/values/expressions/LogicalExpression.js
|
||||||
|
+++ b/src/values/expressions/LogicalExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a LogicalExpression type value node.
|
||||||
|
* A logical expression is `a && b` or `a || b`, so we evaluate both sides
|
||||||
|
@@ -7,8 +9,6 @@
|
||||||
|
* @returns - The extracted value converted to correct type.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromLogicalExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
const { operator, left, right } = value;
|
||||||
|
const leftVal = getValue(left);
|
||||||
|
const rightVal = getValue(right);
|
||||||
|
diff --git a/src/values/expressions/MemberExpression.js b/src/values/expressions/MemberExpression.js
|
||||||
|
index 5d48fd5..d0cb7af 100644
|
||||||
|
--- a/src/values/expressions/MemberExpression.js
|
||||||
|
+++ b/src/values/expressions/MemberExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a MemberExpression type value node.
|
||||||
|
* A member expression is accessing a property on an object `obj.property`.
|
||||||
|
@@ -7,7 +9,5 @@
|
||||||
|
* and maintaing `obj.property` convention.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromMemberExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
return `${getValue(value.object)}${value.optional ? '?.' : '.'}${getValue(value.property)}`;
|
||||||
|
}
|
||||||
|
diff --git a/src/values/expressions/ObjectExpression.js b/src/values/expressions/ObjectExpression.js
|
||||||
|
index 383b569..c1c08a8 100644
|
||||||
|
--- a/src/values/expressions/ObjectExpression.js
|
||||||
|
+++ b/src/values/expressions/ObjectExpression.js
|
||||||
|
@@ -1,4 +1,5 @@
|
||||||
|
import assign from 'object.assign';
|
||||||
|
+import getValue from './index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extractor function for an ObjectExpression type value node.
|
||||||
|
@@ -7,8 +8,6 @@ import assign from 'object.assign';
|
||||||
|
* @returns - a representation of the object
|
||||||
|
*/
|
||||||
|
export default function extractValueFromObjectExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
return value.properties.reduce((obj, property) => {
|
||||||
|
// Support types: SpreadProperty and ExperimentalSpreadProperty
|
||||||
|
if (/^(?:Experimental)?Spread(?:Property|Element)$/.test(property.type)) {
|
||||||
|
diff --git a/src/values/expressions/OptionalCallExpression.js b/src/values/expressions/OptionalCallExpression.js
|
||||||
|
index bcee760..3a36971 100644
|
||||||
|
--- a/src/values/expressions/OptionalCallExpression.js
|
||||||
|
+++ b/src/values/expressions/OptionalCallExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a OptionalCallExpression type value node.
|
||||||
|
* A member expression is accessing a property on an object `obj.property` and invoking it.
|
||||||
|
@@ -7,7 +9,5 @@
|
||||||
|
* and maintaing `obj.property?.()` convention.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromOptionalCallExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
return `${getValue(value.callee)}?.(${value.arguments.map((x) => getValue(x)).join(', ')})`;
|
||||||
|
}
|
||||||
|
diff --git a/src/values/expressions/OptionalMemberExpression.js b/src/values/expressions/OptionalMemberExpression.js
|
||||||
|
index 8062eae..d236b04 100644
|
||||||
|
--- a/src/values/expressions/OptionalMemberExpression.js
|
||||||
|
+++ b/src/values/expressions/OptionalMemberExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a OptionalMemberExpression type value node.
|
||||||
|
* A member expression is accessing a property on an object `obj.property`.
|
||||||
|
@@ -7,7 +9,5 @@
|
||||||
|
* and maintaing `obj?.property` convention.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromOptionalMemberExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
return `${getValue(value.object)}?.${getValue(value.property)}`;
|
||||||
|
}
|
||||||
|
diff --git a/src/values/expressions/SequenceExpression.js b/src/values/expressions/SequenceExpression.js
|
||||||
|
index 129d992..d644730 100644
|
||||||
|
--- a/src/values/expressions/SequenceExpression.js
|
||||||
|
+++ b/src/values/expressions/SequenceExpression.js
|
||||||
|
@@ -1,3 +1,5 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Extractor function for a SequenceExpression type value node.
|
||||||
|
* A Sequence expression is an object with an attribute named
|
||||||
|
@@ -7,7 +9,5 @@
|
||||||
|
* @returns - An array of the extracted elements.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromSequenceExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
return value.expressions.map((element) => getValue(element));
|
||||||
|
}
|
||||||
|
diff --git a/src/values/expressions/TSNonNullExpression.js b/src/values/expressions/TSNonNullExpression.js
|
||||||
|
index 79dd1b0..7d74c8f 100644
|
||||||
|
--- a/src/values/expressions/TSNonNullExpression.js
|
||||||
|
+++ b/src/values/expressions/TSNonNullExpression.js
|
||||||
|
@@ -1,5 +1,5 @@
|
||||||
|
-const extractValueFromThisExpression = require('./ThisExpression').default;
|
||||||
|
-const extractValueFromCallExpression = require('./CallExpression').default;
|
||||||
|
+import extractValueFromThisExpression from './ThisExpression';
|
||||||
|
+import extractValueFromCallExpression from './CallExpression';
|
||||||
|
|
||||||
|
function navigate(obj, prop, value) {
|
||||||
|
if (value.computed) {
|
||||||
|
diff --git a/src/values/expressions/TypeCastExpression.js b/src/values/expressions/TypeCastExpression.js
|
||||||
|
index 1afd232..aeda1e5 100644
|
||||||
|
--- a/src/values/expressions/TypeCastExpression.js
|
||||||
|
+++ b/src/values/expressions/TypeCastExpression.js
|
||||||
|
@@ -1,3 +1,4 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
/**
|
||||||
|
* Extractor function for a TypeCastExpression type value node.
|
||||||
|
* A type cast expression looks like `(this.handleClick: (event: MouseEvent) => void))`
|
||||||
|
@@ -7,7 +8,5 @@
|
||||||
|
* @returns - The extracted value converted to correct type.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromTypeCastExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
return getValue(value.expression);
|
||||||
|
}
|
||||||
|
diff --git a/src/values/expressions/UnaryExpression.js b/src/values/expressions/UnaryExpression.js
|
||||||
|
index b699722..cc1fa35 100644
|
||||||
|
--- a/src/values/expressions/UnaryExpression.js
|
||||||
|
+++ b/src/values/expressions/UnaryExpression.js
|
||||||
|
@@ -1,3 +1,4 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
/**
|
||||||
|
* Extractor function for a UnaryExpression type value node.
|
||||||
|
* A unary expression is an expression with a unary operator.
|
||||||
|
@@ -7,8 +8,6 @@
|
||||||
|
* @returns - The extracted value converted to correct type.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromUnaryExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
const { operator, argument } = value;
|
||||||
|
|
||||||
|
switch (operator) {
|
||||||
|
diff --git a/src/values/expressions/UpdateExpression.js b/src/values/expressions/UpdateExpression.js
|
||||||
|
index 0e3898c..ca005a2 100644
|
||||||
|
--- a/src/values/expressions/UpdateExpression.js
|
||||||
|
+++ b/src/values/expressions/UpdateExpression.js
|
||||||
|
@@ -1,3 +1,4 @@
|
||||||
|
+import getValue from './index';
|
||||||
|
/**
|
||||||
|
* Extractor function for an UpdateExpression type value node.
|
||||||
|
* An update expression is an expression with an update operator.
|
||||||
|
@@ -7,8 +8,6 @@
|
||||||
|
* @returns - The extracted value converted to correct type.
|
||||||
|
*/
|
||||||
|
export default function extractValueFromUpdateExpression(value) {
|
||||||
|
- // eslint-disable-next-line global-require
|
||||||
|
- const getValue = require('.').default;
|
||||||
|
const { operator, argument, prefix } = value;
|
||||||
|
|
||||||
|
let val = getValue(argument);
|
3380
pnpm-lock.yaml
generated
Normal file
3380
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
save_patch.sh
Executable file
9
save_patch.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
sync() (
|
||||||
|
cd "$1" && git diff > "../patch/$1.patch"
|
||||||
|
)
|
||||||
|
|
||||||
|
sync eslint-plugin-import
|
||||||
|
sync eslint-plugin-jsx-a11y
|
||||||
|
sync eslint-plugin-react
|
||||||
|
sync jsx-ast-utils
|
156
src/babel.ts
Normal file
156
src/babel.ts
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import assert from 'node:assert';
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { extname } from 'node:path';
|
||||||
|
import * as babel from '@babel/core';
|
||||||
|
import type { types as t } from '@babel/core';
|
||||||
|
import type { Loader, Plugin } from 'esbuild';
|
||||||
|
import { createMacro, type MacroHandler } from 'babel-plugin-macros';
|
||||||
|
|
||||||
|
class HandlerMap {
|
||||||
|
map = new Map<string, MacroHandler>();
|
||||||
|
|
||||||
|
set(names: string | string[], handler: MacroHandler) {
|
||||||
|
names = Array.isArray(names) ? names : [names];
|
||||||
|
const macro = createMacro(handler);
|
||||||
|
for (const name of names) {
|
||||||
|
this.map.set(name, macro);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
get keys() {
|
||||||
|
return Array.from(this.map.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvePath = (module: string) => module;
|
||||||
|
require = (module: string) => this.map.get(module);
|
||||||
|
isMacrosName = (module: string) => this.map.has(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = new HandlerMap()
|
||||||
|
.set(
|
||||||
|
'object.assign',
|
||||||
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('assign'))),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
['object-values', 'object.values'],
|
||||||
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('values'))),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'object.fromentries',
|
||||||
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('fromEntries'))),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'object.entries',
|
||||||
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('entries'))),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'has',
|
||||||
|
replace(t => t.memberExpression(t.identifier('Object'), t.identifier('hasOwn'))),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'array-includes',
|
||||||
|
proto(t => t.identifier('includes')),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'array.prototype.flatmap',
|
||||||
|
proto(t => t.identifier('flatMap')),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'array.prototype.flat',
|
||||||
|
proto(t => t.identifier('flat')),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'array.prototype.findlastindex',
|
||||||
|
proto(t => t.identifier('findLastIndex')),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'array.prototype.tosorted',
|
||||||
|
proto(t => t.identifier('toSorted')),
|
||||||
|
)
|
||||||
|
.set(
|
||||||
|
'string.prototype.matchall',
|
||||||
|
proto(t => t.identifier('matchAll')),
|
||||||
|
);
|
||||||
|
|
||||||
|
function replace(getReplacement: (types: typeof t) => t.Expression): MacroHandler {
|
||||||
|
return ({ references, babel: { types: t } }) => {
|
||||||
|
references.default.forEach(referencePath => {
|
||||||
|
referencePath.replaceWith(getReplacement(t));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function proto(getProperty: (types: typeof t) => t.Expression): MacroHandler {
|
||||||
|
return ({ references, babel: { types: t } }) => {
|
||||||
|
references.default.forEach(referencePath => {
|
||||||
|
const { parent, parentPath } = referencePath;
|
||||||
|
assert(t.isCallExpression(parent));
|
||||||
|
const [callee, ...rest] = parent.arguments;
|
||||||
|
parentPath!.replaceWith(
|
||||||
|
t.callExpression(
|
||||||
|
t.memberExpression(callee as t.Expression, getProperty(t)),
|
||||||
|
rest,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const babelPlugin: Plugin = {
|
||||||
|
name: 'babel',
|
||||||
|
setup(build) {
|
||||||
|
const { keys, ...macroOptions } = map;
|
||||||
|
|
||||||
|
build.onLoad({ filter: /\.[jt]sx?$/ }, args => {
|
||||||
|
const { path } = args;
|
||||||
|
if (path.includes('node_modules/')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = readFileSync(path, 'utf-8')
|
||||||
|
.replaceAll("require('object.hasown/polyfill')()", 'Object.hasOwn')
|
||||||
|
.replaceAll("require('object.fromentries/polyfill')()", 'Object.fromEntries');
|
||||||
|
|
||||||
|
if (
|
||||||
|
path.includes('eslint-plugin-import/src/rules/') ||
|
||||||
|
path.includes('eslint-plugin-import/config/')
|
||||||
|
) {
|
||||||
|
source = source.replace('\nmodule.exports = {', '\nexport default {');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFlow = source.includes('@flow');
|
||||||
|
const loader = extname(path).slice(1) as Loader;
|
||||||
|
|
||||||
|
if (!isFlow && !keys.some(key => source.includes(key))) {
|
||||||
|
return { contents: source, loader };
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = babel.transformSync(source, {
|
||||||
|
filename: path,
|
||||||
|
babelrc: false,
|
||||||
|
configFile: false,
|
||||||
|
parserOpts: {
|
||||||
|
plugins: [isFlow ? 'flow' : 'typescript'],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
isFlow && '@babel/plugin-transform-flow-strip-types',
|
||||||
|
['babel-plugin-macros', macroOptions],
|
||||||
|
].filter(Boolean),
|
||||||
|
})!;
|
||||||
|
|
||||||
|
return {
|
||||||
|
contents: res.code!,
|
||||||
|
loader,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Array<T> {
|
||||||
|
filter(
|
||||||
|
predicate: BooleanConstructor,
|
||||||
|
): Exclude<T, null | undefined | false | '' | 0>[];
|
||||||
|
}
|
||||||
|
}
|
39
src/rules/no-import-dot.ts
Normal file
39
src/rules/no-import-dot.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import type { Rule } from "eslint";
|
||||||
|
|
||||||
|
const rule: Rule.RuleModule = {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description:
|
||||||
|
"Bans import from the specifier '.' and '..' and replaces it with '.+/index'",
|
||||||
|
category: "Best Practices",
|
||||||
|
recommended: true,
|
||||||
|
},
|
||||||
|
fixable: "code",
|
||||||
|
},
|
||||||
|
create: context => ({
|
||||||
|
ImportDeclaration(node) {
|
||||||
|
if (node.source.value === ".") {
|
||||||
|
context.report({
|
||||||
|
node: node.source,
|
||||||
|
message:
|
||||||
|
"Importing from the specifier '.' is not allowed. Use './index' instead.",
|
||||||
|
fix(fixer) {
|
||||||
|
return fixer.replaceText(node.source, '"./index"');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (node.source.value === "..") {
|
||||||
|
context.report({
|
||||||
|
node: node.source,
|
||||||
|
message:
|
||||||
|
"Importing from the specifier '..' is not allowed. Use '../index' instead.",
|
||||||
|
fix(fixer) {
|
||||||
|
return fixer.replaceText(node.source, '"../index"');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default rule;
|
32
src/rules/no-new-prisma.ts
Normal file
32
src/rules/no-new-prisma.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import type { Rule } from "eslint";
|
||||||
|
|
||||||
|
const rule: Rule.RuleModule = {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description: "Disallow direct usage of `new PrismaClient()`",
|
||||||
|
category: "Best Practices",
|
||||||
|
recommended: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create(context) {
|
||||||
|
// Check if the file is the target file where the import is allowed
|
||||||
|
if (context.filename.endsWith("src/utils/db.ts")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
NewExpression(node) {
|
||||||
|
if (node.callee.type === "Identifier" && node.callee.name === "PrismaClient") {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message:
|
||||||
|
"Avoid direct usage of `new PrismaClient()`. Import from `src/utils/db.ts` instead.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default rule;
|
33
src/rules/no-webcrypto-import.ts
Normal file
33
src/rules/no-webcrypto-import.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import type { Rule } from "eslint";
|
||||||
|
|
||||||
|
const rule: Rule.RuleModule = {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description: "Disallow importing webcrypto from node:crypto and crypto modules",
|
||||||
|
category: "Best Practices",
|
||||||
|
recommended: true,
|
||||||
|
},
|
||||||
|
schema: [],
|
||||||
|
},
|
||||||
|
create: context => ({
|
||||||
|
ImportDeclaration(node) {
|
||||||
|
const importedSource = node.source.value as string;
|
||||||
|
const importedSpecifier = node.specifiers[0];
|
||||||
|
|
||||||
|
if (
|
||||||
|
(importedSource === "crypto" || importedSource === "node:crypto") &&
|
||||||
|
importedSpecifier.type === "ImportSpecifier" &&
|
||||||
|
importedSpecifier.local.name === "webcrypto"
|
||||||
|
) {
|
||||||
|
context.report({
|
||||||
|
node: importedSpecifier,
|
||||||
|
message:
|
||||||
|
"Do not import 'webcrypto' from 'crypto' or 'node:crypto'. Use the global variable 'crypto' instead.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default rule;
|
36
src/rules/require-node-prefix.ts
Normal file
36
src/rules/require-node-prefix.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { builtinModules } from "node:module";
|
||||||
|
import type { Rule } from "eslint";
|
||||||
|
|
||||||
|
const rule: Rule.RuleModule = {
|
||||||
|
meta: {
|
||||||
|
type: "problem",
|
||||||
|
docs: {
|
||||||
|
description:
|
||||||
|
"Disallow imports of built-in Node.js modules without the `node:` prefix",
|
||||||
|
category: "Best Practices",
|
||||||
|
recommended: true,
|
||||||
|
},
|
||||||
|
fixable: "code",
|
||||||
|
schema: [],
|
||||||
|
},
|
||||||
|
create: context => ({
|
||||||
|
ImportDeclaration(node) {
|
||||||
|
const { source } = node;
|
||||||
|
|
||||||
|
if (source?.type === "Literal" && typeof source.value === "string") {
|
||||||
|
const moduleName = source.value;
|
||||||
|
|
||||||
|
if (builtinModules.includes(moduleName) && !moduleName.startsWith("node:")) {
|
||||||
|
context.report({
|
||||||
|
node: source,
|
||||||
|
message: `Import of built-in Node.js module "${moduleName}" must use the "node:" prefix.`,
|
||||||
|
fix: fixer => fixer.replaceText(source, `"node:${moduleName}"`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default rule;
|
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowArbitraryExtensions": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"strict": true,
|
||||||
|
"stripInternal": true,
|
||||||
|
"target": "esnext",
|
||||||
|
"useUnknownInCatchVariables": false
|
||||||
|
},
|
||||||
|
"ts-node": {
|
||||||
|
"transpileOnly": true,
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user