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