Inline repo
This commit is contained in:
21
packages/eslint-define-config/LICENSE
Normal file
21
packages/eslint-define-config/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-2023 Christopher Quadflieg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
144
packages/eslint-define-config/README.md
Normal file
144
packages/eslint-define-config/README.md
Normal file
@ -0,0 +1,144 @@
|
||||
<p>
|
||||
<a href="https://www.npmjs.com/package/eslint-define-config" target="_blank">
|
||||
<img alt="NPM package" src="https://img.shields.io/npm/v/eslint-define-config.svg">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/eslint-define-config" target="_blank">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dt/eslint-define-config.svg">
|
||||
</a>
|
||||
<a href="https://github.com/eslint-types/eslint-define-config/actions/workflows/ci.yml">
|
||||
<img alt="Build Status" src="https://github.com/eslint-types/eslint-define-config/actions/workflows/ci.yml/badge.svg?branch=main">
|
||||
</a>
|
||||
<a href="https://github.com/eslint-types/eslint-define-config/blob/main/LICENSE">
|
||||
<img alt="License: MIT" src="https://img.shields.io/github/license/eslint-types/eslint-define-config.svg">
|
||||
</a>
|
||||
<a href="https://prettier.io" target="_blank">
|
||||
<img alt="Code Style: Prettier" src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg">
|
||||
</a>
|
||||
<a href="https://www.paypal.com/donate?hosted_button_id=L7GY729FBKTZY" target="_blank">
|
||||
<img alt="Donate: PayPal" src="https://img.shields.io/badge/Donate-PayPal-blue.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# eslint-define-config
|
||||
|
||||
Provide a `defineConfig` function for `.eslintrc.js`, and a `defineFlatConfig` function for `eslint.config.js` files.
|
||||
|
||||
> This project is written by a human and only partially automatically generated!
|
||||
> Some rules are even enhanced by hand!
|
||||
> Unfortunately, this has the disadvantage that not everything is immediately defined. For example, if a rule is not defined, it falls back to a basic definition.
|
||||
> However, the advantage is that you get documentation for pretty much everything in the code and usually get a direct link to the respective plugin or eslint rule. The types are also strictly typed.
|
||||
>
|
||||
> So if you are missing something like a rule or a plugin that should also be supported or a rule definition is e.g. out of date, feel free to open an issue or PR for it.
|
||||
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
# add eslint and eslint-define-config to project’s dev dependencies
|
||||
npm add --save-dev eslint eslint-define-config
|
||||
# or
|
||||
yarn add --dev eslint eslint-define-config
|
||||
# or
|
||||
pnpm add --save-dev eslint eslint-define-config
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
`.eslintrc.js`
|
||||
|
||||
```ts
|
||||
// @ts-check
|
||||
|
||||
// To activate auto-suggestions for Rules of specific plugins, you need to add a `/// <reference types="eslint-plugin-PLUGIN_NAME/define-config-support" />` comment.
|
||||
// ⚠️ This feature is very new and requires the support of the respective plugin owners.
|
||||
|
||||
/// <reference types="@typescript-eslint/eslint-plugin/define-config-support" />
|
||||
|
||||
const { defineConfig } = require('eslint-define-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
root: true,
|
||||
rules: {
|
||||
// rules...
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Flat Config
|
||||
|
||||
`eslint.config.js`
|
||||
|
||||
```ts
|
||||
// @ts-check
|
||||
const { defineFlatConfig } = require('eslint-define-config');
|
||||
|
||||
module.exports = defineFlatConfig([
|
||||
'eslint:recommended',
|
||||
{
|
||||
plugins: {
|
||||
// plugins...
|
||||
},
|
||||
rules: {
|
||||
// rules...
|
||||
},
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
# Why?
|
||||
|
||||
Improve your eslint configuration experience with:
|
||||
|
||||
- auto-suggestions
|
||||
- type checking (Use `// @ts-check` at the first line in your `.eslintrc.js` or `eslint.config.js`)
|
||||
- documentation
|
||||
- deprecation warnings
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/7195563/112484789-8a416480-8d7a-11eb-9337-d8b5bc16de17.png" alt="Image" width="600px"/>
|
||||
|
||||
## Video
|
||||
|
||||
_Click on the thumbnail to play the video_
|
||||
|
||||
<a href="https://user-images.githubusercontent.com/7195563/112726158-4a19e780-8f1c-11eb-8cc6-4ea6c100137f.mp4" target="_blank">
|
||||
<img src="https://user-images.githubusercontent.com/7195563/112726343-30c56b00-8f1d-11eb-9b92-260c530caf1b.png" alt="Video" width="600px"/>
|
||||
</a>
|
||||
|
||||
## Want to support your own plugin?
|
||||
|
||||
:warning: **This feature is very new and requires the support of the respective plugin owners**
|
||||
|
||||
Add a `declare module` to your plugin package like this:
|
||||
|
||||
```ts
|
||||
declare module 'eslint-define-config' {
|
||||
export interface CustomRuleOptions {
|
||||
/**
|
||||
* Require consistently using either `T[]` or `Array<T>` for arrays.
|
||||
*
|
||||
* @see [array-type](https://typescript-eslint.io/rules/array-type)
|
||||
*/
|
||||
'@typescript-eslint/array-type': [
|
||||
{
|
||||
default?: 'array' | 'generic' | 'array-simple';
|
||||
readonly?: 'array' | 'generic' | 'array-simple';
|
||||
},
|
||||
];
|
||||
|
||||
// ... more Rules
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are other interfaces that can be extended.
|
||||
|
||||
- `CustomExtends`
|
||||
- `CustomParserOptions`
|
||||
- `CustomParsers`
|
||||
- `CustomPlugins`
|
||||
- `CustomSettings`
|
||||
|
||||
# Credits
|
||||
|
||||
- [Proposal Idea](https://github.com/eslint/eslint/issues/14249)
|
||||
- [Vite](https://github.com/vitejs/vite) and [Evan You](https://github.com/yyx990803) for the idea
|
||||
- [@antfu](https://github.com/antfu) and his [tweet](https://twitter.com/antfu7/status/1365907188338753536)
|
120
packages/eslint-define-config/package.json
Normal file
120
packages/eslint-define-config/package.json
Normal file
@ -0,0 +1,120 @@
|
||||
{
|
||||
"name": "eslint-define-config",
|
||||
"version": "1.24.1",
|
||||
"description": "Provide a defineConfig function for .eslintrc.js files",
|
||||
"scripts": {
|
||||
"clean": "rimraf coverage .eslintcache dist pnpm-lock.yaml node_modules",
|
||||
"format": "prettier --cache --write .",
|
||||
"lint:run": "eslint --cache --cache-strategy content --report-unused-disable-directives .",
|
||||
"lint": "run-s build lint:run",
|
||||
"typecheck": "vitest typecheck",
|
||||
"ts-check": "tsc",
|
||||
"test": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"generate:rules": "tsx ./scripts/generate-rule-files/cli.ts",
|
||||
"prepublishOnly": "pnpm run clean && pnpm install && pnpm run build",
|
||||
"preflight": "pnpm install && run-s format lint ts-check test typecheck"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
"config",
|
||||
"configuration",
|
||||
"define-config",
|
||||
"eslint-config",
|
||||
"eslint",
|
||||
"eslintconfig",
|
||||
"typed",
|
||||
"typescript"
|
||||
],
|
||||
"author": {
|
||||
"name": "Christopher Quadflieg",
|
||||
"email": "chrissi92@hotmail.de",
|
||||
"url": "https://github.com/Shinigami92"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/eslint-types/eslint-define-config.git"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/Shinigami92"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://www.paypal.com/donate/?hosted_button_id=L7GY729FBKTZY"
|
||||
}
|
||||
],
|
||||
"bugs": "https://github.com/eslint-types/eslint-define-config/issues",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@graphql-eslint/eslint-plugin": "~3.20.1",
|
||||
"@intlify/eslint-plugin-vue-i18n": "~2.0.0",
|
||||
"@poppinss/cliui": "~6.4.1",
|
||||
"@types/dedent": "^0.7.2",
|
||||
"@types/eslint": "~8.44.3",
|
||||
"@types/json-schema": "~7.0.13",
|
||||
"@types/node": "~20.8.3",
|
||||
"@typescript-eslint/eslint-plugin": "~6.7.4",
|
||||
"@typescript-eslint/parser": "~6.7.4",
|
||||
"@vitest/coverage-v8": "~0.34.6",
|
||||
"change-case": "~5.4.4",
|
||||
"dedent": "^1.5.3",
|
||||
"eslint-config-prettier": "~9.0.0",
|
||||
"eslint-gitignore": "~0.1.0",
|
||||
"eslint-plugin-deprecation": "~2.0.0",
|
||||
"eslint-plugin-eslint-comments": "~3.2.0",
|
||||
"eslint-plugin-import": "~2.28.1",
|
||||
"eslint-plugin-inclusive-language": "~2.2.1",
|
||||
"eslint-plugin-jsdoc": "~46.8.2",
|
||||
"eslint-plugin-jsonc": "~2.9.0",
|
||||
"eslint-plugin-jsx-a11y": "~6.7.1",
|
||||
"eslint-plugin-mdx": "~3.1.5",
|
||||
"eslint-plugin-n": "~16.1.0",
|
||||
"eslint-plugin-node": "~11.1.0",
|
||||
"eslint-plugin-prettier": "~5.0.0",
|
||||
"eslint-plugin-promise": "~6.1.1",
|
||||
"eslint-plugin-react": "~7.33.2",
|
||||
"eslint-plugin-react-hooks": "~4.6.0",
|
||||
"eslint-plugin-sonarjs": "~0.21.0",
|
||||
"eslint-plugin-spellcheck": "~0.0.20",
|
||||
"eslint-plugin-testing-library": "~6.0.2",
|
||||
"eslint-plugin-unicorn": "~48.0.1",
|
||||
"eslint-plugin-vitest": "~0.3.2",
|
||||
"eslint-plugin-vue": "~9.17.0",
|
||||
"eslint-plugin-vue-pug": "~0.6.0",
|
||||
"eslint-plugin-yml": "~1.10.0",
|
||||
"expect-type": "~0.17.3",
|
||||
"graphql": "~16.8.1",
|
||||
"json-schema": "~0.4.0",
|
||||
"json-schema-to-ts": "~2.9.2",
|
||||
"json-schema-to-typescript": "~13.1.1",
|
||||
"npm-run-all": "~4.1.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"rimraf": "~5.0.5",
|
||||
"tsup": "~7.2.0",
|
||||
"vue-eslint-parser": "~9.3.2"
|
||||
},
|
||||
"packageManager": "pnpm@8.8.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=9.0.0",
|
||||
"pnpm": ">=8.6.0"
|
||||
}
|
||||
}
|
6894
packages/eslint-define-config/pnpm-lock.yaml
generated
Normal file
6894
packages/eslint-define-config/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
diff --git b/lib/rules/eslint/no-constructor-return.d.ts a/lib/rules/eslint/no-constructor-return.d.ts
|
||||
index fedeca4..3e1fd03 100644
|
||||
--- b/lib/rules/eslint/no-constructor-return.d.ts
|
||||
+++ a/lib/rules/eslint/no-constructor-return.d.ts
|
||||
@@ -1,16 +1,9 @@
|
||||
import type { RuleConfig } from '../rule-config';
|
||||
|
||||
-/**
|
||||
- * Option.
|
||||
- */
|
||||
-export interface NoConstructorReturnOption {
|
||||
- [k: string]: any;
|
||||
-}
|
||||
-
|
||||
/**
|
||||
* Options.
|
||||
*/
|
||||
-export type NoConstructorReturnOptions = NoConstructorReturnOption;
|
||||
+export type NoConstructorReturnOptions = [];
|
||||
|
||||
/**
|
||||
* Disallow returning value from constructor.
|
@ -0,0 +1,14 @@
|
||||
diff --git b/lib/rules/graphql-eslint/naming-convention.d.ts a/lib/rules/graphql-eslint/naming-convention.d.ts
|
||||
index 60b228b..5e01373 100644
|
||||
--- b/lib/rules/graphql-eslint/naming-convention.d.ts
|
||||
+++ a/lib/rules/graphql-eslint/naming-convention.d.ts
|
||||
@@ -78,8 +78,7 @@ export type NamingConventionOption =
|
||||
VariableDefinition?: AsString | AsObject;
|
||||
allowLeadingUnderscore?: boolean;
|
||||
allowTrailingUnderscore?: boolean;
|
||||
- /**
|
||||
- */
|
||||
+ } & {
|
||||
[k: string]: AsString | AsObject;
|
||||
},
|
||||
];
|
@ -0,0 +1,13 @@
|
||||
diff --git b/lib/rules/node/file-extension-in-import.d.ts a/lib/rules/node/file-extension-in-import.d.ts
|
||||
index 652b18d..7261252 100644
|
||||
--- b/lib/rules/node/file-extension-in-import.d.ts
|
||||
+++ a/lib/rules/node/file-extension-in-import.d.ts
|
||||
@@ -5,7 +5,7 @@ import type { RuleConfig } from '../rule-config';
|
||||
*/
|
||||
export interface FileExtensionInImportConfig {
|
||||
tryExtensions?: string[];
|
||||
- [k: string]: 'always' | 'never';
|
||||
+ [ext: `.${string}`]: 'always' | 'never';
|
||||
}
|
||||
|
||||
/**
|
@ -0,0 +1,23 @@
|
||||
diff --git a/lib/rules/react/jsx-no-constructed-context-values.d.ts b/lib/rules/react/jsx-no-constructed-context-values.d.ts
|
||||
index 410f060..e356693 100644
|
||||
--- a/lib/rules/react/jsx-no-constructed-context-values.d.ts
|
||||
+++ b/lib/rules/react/jsx-no-constructed-context-values.d.ts
|
||||
@@ -1,17 +1,9 @@
|
||||
import type { RuleConfig } from '../rule-config';
|
||||
|
||||
-/**
|
||||
- * Option.
|
||||
- */
|
||||
-export interface JsxNoConstructedContextValuesOption {
|
||||
- [k: string]: any;
|
||||
-}
|
||||
-
|
||||
/**
|
||||
* Options.
|
||||
*/
|
||||
-export type JsxNoConstructedContextValuesOptions =
|
||||
- JsxNoConstructedContextValuesOption;
|
||||
+export type JsxNoConstructedContextValuesOptions = [];
|
||||
|
||||
/**
|
||||
* Disallows JSX context provider values from taking values that will cause needless rerenders.
|
@ -0,0 +1,13 @@
|
||||
diff --git a/lib/rules/react/jsx-props-no-spreading.d.ts b/lib/rules/react/jsx-props-no-spreading.d.ts
|
||||
index c1e0069..ba1e1bc 100644
|
||||
--- a/lib/rules/react/jsx-props-no-spreading.d.ts
|
||||
+++ b/lib/rules/react/jsx-props-no-spreading.d.ts
|
||||
@@ -8,8 +8,6 @@ export type JsxPropsNoSpreadingOption = {
|
||||
custom?: 'enforce' | 'ignore';
|
||||
exceptions?: string[];
|
||||
[k: string]: any;
|
||||
-} & {
|
||||
- [k: string]: any;
|
||||
};
|
||||
|
||||
/**
|
@ -0,0 +1,21 @@
|
||||
diff --git a/lib/rules/vitest/valid-title.d.ts b/lib/rules/vitest/valid-title.d.ts
|
||||
index 160be76..834ac9b 100644
|
||||
--- a/lib/rules/vitest/valid-title.d.ts
|
||||
+++ b/lib/rules/vitest/valid-title.d.ts
|
||||
@@ -7,15 +7,7 @@ export interface ValidTitleOption {
|
||||
ignoreTypeOfDescribeName?: boolean;
|
||||
allowArguments?: boolean;
|
||||
disallowedWords?: string[];
|
||||
- /**
|
||||
- */
|
||||
- [k: string]:
|
||||
- | string
|
||||
- | [string]
|
||||
- | [string, string]
|
||||
- | {
|
||||
- [k: string]: string | [string] | [string, string];
|
||||
- };
|
||||
+ [k: string]: any;
|
||||
}
|
||||
|
||||
/**
|
178
packages/eslint-define-config/scripts/index.ts
Executable file
178
packages/eslint-define-config/scripts/index.ts
Executable file
@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bun
|
||||
import { Logger, colors as cliColors } from '@poppinss/cliui';
|
||||
import { pascalCase } from 'change-case';
|
||||
import type { Rule } from 'eslint';
|
||||
import { existsSync, promises as fs } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { format } from 'prettier';
|
||||
import { buildJSDoc, prettierConfig, type Plugin } from './utils';
|
||||
import { PLUGIN_REGISTRY, loadPlugin } from './plugins-map';
|
||||
import { RuleFile } from './rule-file';
|
||||
|
||||
interface FailedRule {
|
||||
ruleName: string;
|
||||
err: unknown;
|
||||
}
|
||||
|
||||
const logger = new Logger();
|
||||
const colors = cliColors.ansi();
|
||||
|
||||
const getRuleName = (name: string) =>
|
||||
name === 'ESLint' ? 'ESLintRule' : `${pascalCase(name)}Rule`;
|
||||
|
||||
const index: [string, string][] = [];
|
||||
|
||||
/**
|
||||
* Generate the `index.d.ts` file for the plugin's rules that will re-export all rules.
|
||||
*/
|
||||
async function generateRuleIndexFile(
|
||||
pluginDirectory: string,
|
||||
{ rules, name, id }: Plugin,
|
||||
failedRules: FailedRule[],
|
||||
): Promise<void> {
|
||||
const generatedRules = Object.keys(rules!).filter(
|
||||
ruleName => !failedRules.some(failedRule => failedRule.ruleName === ruleName),
|
||||
);
|
||||
|
||||
/**
|
||||
* Build all the import statements for the rules.
|
||||
*/
|
||||
const rulesImports = generatedRules
|
||||
.map(name => `import type { ${getRuleName(name)} } from './${name}';`)
|
||||
.join('\n');
|
||||
|
||||
/**
|
||||
* Build the exported type that is an intersection of all the rules.
|
||||
*/
|
||||
const rulesFinalIntersection = generatedRules
|
||||
.map(name => `${getRuleName(name)}`)
|
||||
.sort()
|
||||
.join(' & ');
|
||||
|
||||
const pluginRulesType = `
|
||||
${buildJSDoc([`All ${name} rules.`])}
|
||||
export type ${name}Rules = ${rulesFinalIntersection};
|
||||
`;
|
||||
|
||||
/**
|
||||
* Write the final `index.d.ts` file.
|
||||
*/
|
||||
const fileContent = `
|
||||
${rulesImports}
|
||||
|
||||
${pluginRulesType}
|
||||
`;
|
||||
|
||||
const indexPath = join(pluginDirectory, 'index.d.ts');
|
||||
await fs.writeFile(indexPath, await format(fileContent, prettierConfig));
|
||||
|
||||
index.push([`${name}Rules`, `./${id}/index`]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a report after having generated rules files for a plugin.
|
||||
*/
|
||||
function printGenerationReport(
|
||||
rules: Array<[string, Rule.RuleModule]>,
|
||||
failedRules: FailedRule[],
|
||||
): void {
|
||||
const msg: string = ` ✅ Generated ${rules.length - failedRules.length} rules`;
|
||||
logger.logUpdate(colors.green(msg));
|
||||
logger.logUpdatePersist();
|
||||
|
||||
if (failedRules.length) {
|
||||
logger.log(colors.red(` ❌ Failed ${failedRules.length} rules`));
|
||||
failedRules.forEach(({ ruleName, err }) => {
|
||||
logger.log(colors.red(` - ${ruleName}: ${String(err)}`));
|
||||
});
|
||||
}
|
||||
|
||||
logger.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a `.d.ts` file for each rule in the given plugin.
|
||||
*/
|
||||
async function generateRulesFiles(
|
||||
plugin: Plugin,
|
||||
pluginDirectory: string,
|
||||
): Promise<{ failedRules: FailedRule[] }> {
|
||||
const failedRules: FailedRule[] = [];
|
||||
|
||||
const pluginRules = plugin.rules;
|
||||
if (!pluginRules) {
|
||||
throw new Error(
|
||||
`Plugin ${plugin.name} doesn't have any rules. Did you forget to load them?`,
|
||||
);
|
||||
}
|
||||
|
||||
const rules = Object.entries(pluginRules);
|
||||
for (const [ruleName, rule] of rules) {
|
||||
logger.logUpdate(colors.yellow(` Generating > ${ruleName}`));
|
||||
|
||||
try {
|
||||
await RuleFile(plugin, pluginDirectory, ruleName, rule);
|
||||
} catch (err) {
|
||||
failedRules.push({ ruleName, err });
|
||||
}
|
||||
}
|
||||
|
||||
printGenerationReport(rules, failedRules);
|
||||
|
||||
return { failedRules };
|
||||
}
|
||||
|
||||
const rulesDirectory = resolve(__dirname, '../../../src/types/rules');
|
||||
|
||||
/**
|
||||
* If it doesn't exist, create the directory that will contain the plugin's rule files.
|
||||
*/
|
||||
async function createPluginDirectory(pluginName: string): Promise<string> {
|
||||
const pluginDirectory = join(rulesDirectory, pluginName);
|
||||
|
||||
if (existsSync(pluginDirectory)) {
|
||||
await fs.rm(pluginDirectory, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
await fs.mkdir(pluginDirectory, { mode: 0o755, recursive: true });
|
||||
|
||||
return pluginDirectory;
|
||||
}
|
||||
|
||||
export interface RunOptions {
|
||||
plugins?: string[];
|
||||
targetDirectory?: string;
|
||||
}
|
||||
|
||||
for (const plugin of PLUGIN_REGISTRY) {
|
||||
logger.info(`Generating ${plugin.name} rules.`);
|
||||
logger.logUpdate(colors.yellow(` Loading plugin > ${plugin.name}`));
|
||||
const loadedPlugin = await loadPlugin(plugin);
|
||||
|
||||
const pluginDir = await createPluginDirectory(plugin.id);
|
||||
const { failedRules } = await generateRulesFiles(loadedPlugin, pluginDir);
|
||||
|
||||
await generateRuleIndexFile(pluginDir, loadedPlugin, failedRules);
|
||||
}
|
||||
|
||||
await fs.writeFile(
|
||||
resolve(rulesDirectory, 'index.d.ts'),
|
||||
await format(
|
||||
[
|
||||
'import type { RuleConfig } from "./rule-config";',
|
||||
...index.map(([name, path]) => `import { ${name} } from ${JSON.stringify(path)};`),
|
||||
'',
|
||||
'/**',
|
||||
' * Rules.',
|
||||
' *',
|
||||
' * @see [Rules](https://eslint.org/docs/user-guide/configuring/rules)',
|
||||
' */',
|
||||
'',
|
||||
'export type Rules = Partial<',
|
||||
...index.map(([name]) => ` ${name} &`),
|
||||
' Record<string, RuleConfig>',
|
||||
'>;',
|
||||
].join('\n'),
|
||||
prettierConfig,
|
||||
),
|
||||
);
|
58
packages/eslint-define-config/scripts/json-schema-to-ts.ts
Normal file
58
packages/eslint-define-config/scripts/json-schema-to-ts.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import type { JSONSchema4 } from 'json-schema';
|
||||
import { compile } from 'json-schema-to-typescript';
|
||||
|
||||
/**
|
||||
* Remove unnecessary comments that are generated by `json-schema-to-typescript`.
|
||||
*/
|
||||
function cleanJsDoc(content: string): string {
|
||||
const patterns = [
|
||||
/\* This interface was referenced by .+ JSON-Schema definition/,
|
||||
/\* via the `.+` "/,
|
||||
];
|
||||
|
||||
return content
|
||||
.split('\n')
|
||||
.filter(line => !patterns.some(ignoredLine => ignoredLine.test(line)))
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace some types that are generated by `json-schema-to-typescript`.
|
||||
*/
|
||||
export function patchTypes(content: string): string {
|
||||
const replacements: [RegExp, string][] = [
|
||||
[
|
||||
/\(string & \{\s*\[k: string\]: any\s*\} & \{\s*\[k: string\]: any\s*\}\)\[\]/,
|
||||
'string[]',
|
||||
],
|
||||
];
|
||||
|
||||
for (const [pattern, replacement] of replacements) {
|
||||
content = content.replace(pattern, replacement);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a type from the given JSON schema.
|
||||
*/
|
||||
export async function generateTypeFromSchema(
|
||||
schema: JSONSchema4,
|
||||
typeName: string,
|
||||
): Promise<string> {
|
||||
schema = JSON.parse(
|
||||
JSON.stringify(schema).replace(/#\/items\/0\/\$defs\//g, '#/$defs/'),
|
||||
);
|
||||
const result = await compile(schema, typeName, {
|
||||
format: false,
|
||||
bannerComment: '',
|
||||
style: {
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
},
|
||||
unknownAny: false,
|
||||
});
|
||||
|
||||
return patchTypes(cleanJsDoc(result));
|
||||
}
|
145
packages/eslint-define-config/scripts/plugins-map.ts
Normal file
145
packages/eslint-define-config/scripts/plugins-map.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import type { Plugin, PluginRules } from './utils';
|
||||
|
||||
/**
|
||||
* Map of plugins for which the script will generate rule files.
|
||||
*/
|
||||
export const PLUGIN_REGISTRY: Plugin[] = [
|
||||
{
|
||||
id: 'deprecation',
|
||||
name: 'Deprecation',
|
||||
module: () => import('eslint-plugin-deprecation'),
|
||||
},
|
||||
{
|
||||
id: 'eslint',
|
||||
name: 'ESLint',
|
||||
module: () => import('eslint'),
|
||||
},
|
||||
{
|
||||
id: 'typescript-eslint',
|
||||
name: 'TypeScript',
|
||||
prefix: '@typescript-eslint',
|
||||
module: () => import('@typescript-eslint/eslint-plugin'),
|
||||
},
|
||||
{
|
||||
id: 'import',
|
||||
name: 'Import',
|
||||
module: () => import('eslint-plugin-import'),
|
||||
},
|
||||
{
|
||||
id: 'eslint-comments',
|
||||
name: 'EslintComments',
|
||||
module: () => import('eslint-plugin-eslint-comments'),
|
||||
},
|
||||
{
|
||||
id: 'graphql-eslint',
|
||||
name: 'GraphQL',
|
||||
prefix: '@graphql-eslint',
|
||||
module: () => import('@graphql-eslint/eslint-plugin'),
|
||||
},
|
||||
{
|
||||
id: 'jsdoc',
|
||||
name: 'JSDoc',
|
||||
prefix: 'jsdoc',
|
||||
module: () => import('eslint-plugin-jsdoc'),
|
||||
},
|
||||
{
|
||||
id: 'jsonc',
|
||||
name: 'Jsonc',
|
||||
module: () => import('eslint-plugin-jsonc'),
|
||||
},
|
||||
{
|
||||
id: 'jsx-a11y',
|
||||
name: 'JsxA11y',
|
||||
module: () => import('eslint-plugin-jsx-a11y'),
|
||||
},
|
||||
{
|
||||
id: 'mdx',
|
||||
name: 'Mdx',
|
||||
module: () => import('eslint-plugin-mdx'),
|
||||
},
|
||||
{
|
||||
id: 'n',
|
||||
name: 'N',
|
||||
module: () => import('eslint-plugin-n'),
|
||||
},
|
||||
{
|
||||
id: 'node',
|
||||
name: 'Node',
|
||||
module: () => import('eslint-plugin-node'),
|
||||
},
|
||||
{
|
||||
id: 'promise',
|
||||
name: 'Promise',
|
||||
module: () => import('eslint-plugin-promise'),
|
||||
},
|
||||
{
|
||||
id: 'react',
|
||||
name: 'React',
|
||||
module: () => import('eslint-plugin-react'),
|
||||
},
|
||||
{
|
||||
id: 'react-hooks',
|
||||
name: 'ReactHooks',
|
||||
module: () => import('eslint-plugin-react-hooks'),
|
||||
},
|
||||
{
|
||||
id: 'sonarjs',
|
||||
name: 'SonarJS',
|
||||
prefix: 'sonarjs',
|
||||
module: () => import('eslint-plugin-sonarjs'),
|
||||
},
|
||||
{
|
||||
id: 'spellcheck',
|
||||
name: 'Spellcheck',
|
||||
module: () => import('eslint-plugin-spellcheck'),
|
||||
},
|
||||
{
|
||||
id: 'testing-library',
|
||||
name: 'TestingLibrary',
|
||||
module: () => import('eslint-plugin-testing-library'),
|
||||
},
|
||||
{
|
||||
id: 'unicorn',
|
||||
name: 'Unicorn',
|
||||
module: () => import('eslint-plugin-unicorn'),
|
||||
},
|
||||
{
|
||||
id: 'vitest',
|
||||
name: 'Vitest',
|
||||
module: () => import('eslint-plugin-vitest'),
|
||||
},
|
||||
{
|
||||
id: 'vue',
|
||||
name: 'Vue',
|
||||
module: () => import('eslint-plugin-vue'),
|
||||
},
|
||||
{
|
||||
id: 'vue-i18n',
|
||||
name: 'VueI18n',
|
||||
prefix: '@intlify/vue-i18n',
|
||||
module: () => import('@intlify/eslint-plugin-vue-i18n'),
|
||||
},
|
||||
{
|
||||
id: 'vue-pug',
|
||||
name: 'VuePug',
|
||||
module: () => import('eslint-plugin-vue-pug'),
|
||||
},
|
||||
{
|
||||
id: 'yml',
|
||||
name: 'Yml',
|
||||
module: () => import('eslint-plugin-yml'),
|
||||
},
|
||||
] as const;
|
||||
|
||||
export async function loadPlugin(plugin: Plugin): Promise<Plugin> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mod: any = await plugin.module();
|
||||
const rules: PluginRules =
|
||||
plugin.name === 'ESLint'
|
||||
? Object.fromEntries(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
new mod.Linter().getRules().entries(),
|
||||
)
|
||||
: mod.rules ?? mod.default.rules;
|
||||
return { ...plugin, rules };
|
||||
}
|
168
packages/eslint-define-config/scripts/rule-file.ts
Normal file
168
packages/eslint-define-config/scripts/rule-file.ts
Normal file
@ -0,0 +1,168 @@
|
||||
import { Logger, colors as cliColors } from '@poppinss/cliui';
|
||||
import { pascalCase, kebabCase } from 'change-case';
|
||||
import type { Rule } from 'eslint';
|
||||
import type { JSONSchema4 } from 'json-schema';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { format } from 'prettier';
|
||||
import { generateTypeFromSchema } from './json-schema-to-ts';
|
||||
import { buildJSDoc, prettierConfig, type Plugin } from './utils';
|
||||
|
||||
const logger = new Logger();
|
||||
const colors = cliColors.ansi();
|
||||
|
||||
/**
|
||||
* Build the rule description to append to the JSDoc.
|
||||
*/
|
||||
function buildDescription(description = ''): string {
|
||||
description = description.charAt(0).toUpperCase() + description.slice(1);
|
||||
if (description.length > 0 && !description.endsWith('.')) {
|
||||
description += '.';
|
||||
}
|
||||
|
||||
return description.replace('*/', '');
|
||||
}
|
||||
|
||||
export async function RuleFile(
|
||||
plugin: Plugin,
|
||||
pluginDirectory: string,
|
||||
ruleName: string,
|
||||
rule: Rule.RuleModule,
|
||||
) {
|
||||
let content = '';
|
||||
|
||||
const rulePath = resolve(pluginDirectory, `${ruleName}.d.ts`);
|
||||
const ruleNamePascalCase = pascalCase(ruleName);
|
||||
const schema: JSONSchema4 | JSONSchema4[] | undefined = rule.meta?.schema;
|
||||
const isSchemaArray = Array.isArray(schema);
|
||||
|
||||
const mainSchema = isSchemaArray ? schema[0] : schema;
|
||||
const sideSchema = isSchemaArray && schema.length > 1 ? schema[1] : undefined;
|
||||
const thirdSchema = isSchemaArray && schema.length > 2 ? schema[2] : undefined;
|
||||
|
||||
/**
|
||||
* Generate a JSDoc with the rule description and `@see` url.
|
||||
*/
|
||||
function generateTypeJsDoc(): string {
|
||||
const { meta } = rule;
|
||||
const seeDocLink: string = meta?.docs?.url
|
||||
? `@see [${ruleName}](${meta.docs.url})`
|
||||
: '';
|
||||
|
||||
return buildJSDoc([
|
||||
buildDescription(rule.meta?.docs?.description),
|
||||
' ',
|
||||
rule.meta?.deprecated ? '@deprecated' : '',
|
||||
rule.meta?.deprecated ? ' ' : '',
|
||||
seeDocLink,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a type from a JSON schema and append it to the file content.
|
||||
*/
|
||||
async function appendJsonSchemaType(
|
||||
schema: JSONSchema4,
|
||||
comment: string,
|
||||
): Promise<void> {
|
||||
const type = await generateTypeFromSchema(schema, ruleNamePascalCase + comment);
|
||||
const jsdoc = buildJSDoc([`${comment}.`]);
|
||||
content += `\n${jsdoc}`;
|
||||
content += `\n${type}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoped rule name ESLint config uses.
|
||||
*/
|
||||
function prefixedRuleName(): string {
|
||||
const { prefix, name } = plugin;
|
||||
const rulePrefix = (prefix ?? kebabCase(name)) + '/';
|
||||
return name === 'ESLint' ? ruleName : `${rulePrefix}${ruleName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the `import type { RuleConfig } from '../rule-config'` at the top of the file.
|
||||
*/
|
||||
const nestedDepth = ruleName.split('/').length;
|
||||
content += `import type { RuleConfig } from '${'../'.repeat(nestedDepth)}rule-config'\n\n`;
|
||||
|
||||
/**
|
||||
* Generate and append types for the rule schemas.
|
||||
*/
|
||||
if (thirdSchema) {
|
||||
await appendJsonSchemaType(thirdSchema, 'Setting');
|
||||
}
|
||||
if (sideSchema) {
|
||||
await appendJsonSchemaType(sideSchema, 'Config');
|
||||
}
|
||||
if (mainSchema) {
|
||||
await appendJsonSchemaType(mainSchema, 'Option');
|
||||
}
|
||||
|
||||
if (mainSchema) {
|
||||
/**
|
||||
* Append the rule type options to the file content.
|
||||
*/
|
||||
let type: string = '';
|
||||
if (!isSchemaArray) {
|
||||
type = `${ruleNamePascalCase}Option`;
|
||||
} else if (thirdSchema) {
|
||||
type = `[${ruleNamePascalCase}Option?, ${ruleNamePascalCase}Config?, ${ruleNamePascalCase}Setting?]`;
|
||||
} else if (sideSchema) {
|
||||
type = `[${ruleNamePascalCase}Option?, ${ruleNamePascalCase}Config?]`;
|
||||
} else if (mainSchema) {
|
||||
type = `[${ruleNamePascalCase}Option?]`;
|
||||
}
|
||||
|
||||
content += buildJSDoc(['Options.']) + '\n';
|
||||
content += `export type ${ruleNamePascalCase}Options = ${type}\n\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the rule config type to the file content.
|
||||
*/
|
||||
content += generateTypeJsDoc() + '\n';
|
||||
content += `export type ${ruleNamePascalCase}RuleConfig = RuleConfig<${mainSchema ? `${ruleNamePascalCase}Options` : '[]'}>;\n\n`;
|
||||
|
||||
/**
|
||||
* Append the final rule interface to the file content.
|
||||
*/
|
||||
content += generateTypeJsDoc() + '\n';
|
||||
content += `export interface ${ruleNamePascalCase}Rule {`;
|
||||
content += `${generateTypeJsDoc()}\n`;
|
||||
content += `'${prefixedRuleName()}': ${ruleNamePascalCase}RuleConfig;`;
|
||||
content += '}';
|
||||
|
||||
content = await format(content, prettierConfig);
|
||||
|
||||
/**
|
||||
* Create the directory of the rule file if it doesn't exist.
|
||||
*/
|
||||
const subPath = dirname(rulePath);
|
||||
if (!existsSync(subPath)) {
|
||||
mkdirSync(subPath, { recursive: true });
|
||||
}
|
||||
|
||||
writeFileSync(rulePath, content);
|
||||
|
||||
/**
|
||||
* Apply a patch to the generated content if a diff file exists for it.
|
||||
*
|
||||
* Must be called after `generate()`.
|
||||
*/
|
||||
const pathParts = rulePath.split('/');
|
||||
const diffFile = resolve(
|
||||
__dirname,
|
||||
'./diffs/rules',
|
||||
pathParts.at(-2) ?? '',
|
||||
`${pathParts.at(-1) ?? ''}.diff`,
|
||||
);
|
||||
|
||||
if (existsSync(diffFile)) {
|
||||
logger.logUpdate(colors.yellow(` 🧹 Adjusting ${prefixedRuleName()}`));
|
||||
logger.logUpdatePersist();
|
||||
|
||||
execSync(`git apply ${diffFile}`);
|
||||
}
|
||||
}
|
24
packages/eslint-define-config/scripts/utils.ts
Normal file
24
packages/eslint-define-config/scripts/utils.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { Rule } from 'eslint';
|
||||
import type { Config } from 'prettier';
|
||||
|
||||
export function buildJSDoc(content: string[]) {
|
||||
return ['/**', ...content.filter(Boolean).map(line => ` * ${line}`), ' */'].join('\n');
|
||||
}
|
||||
|
||||
export const prettierConfig: Config = {
|
||||
plugins: ['prettier-plugin-organize-imports'],
|
||||
parser: 'typescript',
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
};
|
||||
|
||||
export type MaybeArray<T> = T | T[];
|
||||
export type PluginRules = Record<string, Rule.RuleModule>;
|
||||
|
||||
export interface Plugin {
|
||||
id: string;
|
||||
name: string;
|
||||
prefix?: string;
|
||||
module: () => Promise<any>;
|
||||
rules?: PluginRules;
|
||||
}
|
Submodule packages/eslint-import-resolver-typescript updated: 7b6bfc3947...7a02ac08b5
Submodule packages/eslint-plugin-import updated: 6b95a02193...f77ceb679d
Submodule packages/eslint-plugin-jsx-a11y updated: fffb05b38c...0d5321a545
Submodule packages/eslint-plugin-n updated: 47cd9a6a0e...eb11b5b35a
Submodule packages/eslint-plugin-react updated: ecadb92609...4467db503e
@ -8,20 +8,22 @@
|
||||
/* eslint-disable no-for-of-loops/no-for-of-loops */
|
||||
import type { Rule, Scope } from 'eslint';
|
||||
import type {
|
||||
FunctionDeclaration,
|
||||
CallExpression,
|
||||
Expression,
|
||||
Super,
|
||||
Node,
|
||||
ArrayExpression,
|
||||
ArrowFunctionExpression,
|
||||
FunctionExpression,
|
||||
SpreadElement,
|
||||
Identifier,
|
||||
VariableDeclarator,
|
||||
MemberExpression,
|
||||
CallExpression,
|
||||
ChainExpression,
|
||||
Pattern,
|
||||
Expression,
|
||||
FunctionDeclaration,
|
||||
FunctionExpression,
|
||||
Identifier,
|
||||
MemberExpression,
|
||||
Node,
|
||||
OptionalMemberExpression,
|
||||
Pattern,
|
||||
SpreadElement,
|
||||
Super,
|
||||
TSAsExpression,
|
||||
VariableDeclarator,
|
||||
} from 'estree';
|
||||
import type { FromSchema } from 'json-schema-to-ts';
|
||||
import { __EXPERIMENTAL__ } from './index';
|
||||
@ -109,7 +111,7 @@ const rule: Rule.RuleModule = {
|
||||
*/
|
||||
function visitFunctionWithDependencies(
|
||||
node: ArrowFunctionExpression | FunctionExpression | FunctionDeclaration,
|
||||
declaredDependenciesNode: SpreadElement | Expression,
|
||||
declaredDependenciesNode: SpreadElement | Expression | undefined,
|
||||
reactiveHook: Super | Expression,
|
||||
reactiveHookName: string,
|
||||
isEffect: boolean,
|
||||
@ -128,7 +130,7 @@ const rule: Rule.RuleModule = {
|
||||
' }\n' +
|
||||
' fetchData();\n' +
|
||||
`}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
|
||||
'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching',
|
||||
'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching',
|
||||
});
|
||||
}
|
||||
|
||||
@ -194,7 +196,7 @@ const rule: Rule.RuleModule = {
|
||||
if (init == null) {
|
||||
return false;
|
||||
}
|
||||
while (init.type === 'TSAsExpression') {
|
||||
while (init.type === 'TSAsExpression' || init.type === 'AsExpression') {
|
||||
init = init.expression;
|
||||
}
|
||||
// Detect primitive constants
|
||||
@ -620,7 +622,11 @@ const rule: Rule.RuleModule = {
|
||||
|
||||
const declaredDependencies: DeclaredDependency[] = [];
|
||||
const externalDependencies = new Set<string>();
|
||||
if (declaredDependenciesNode.type !== 'ArrayExpression') {
|
||||
const isArrayExpression = declaredDependenciesNode.type === 'ArrayExpression';
|
||||
const isTSAsArrayExpression =
|
||||
declaredDependenciesNode.type === 'TSAsExpression' &&
|
||||
declaredDependenciesNode.expression.type === 'ArrayExpression';
|
||||
if (!isArrayExpression && !isTSAsArrayExpression) {
|
||||
// If the declared dependencies are not an array expression then we
|
||||
// can't verify that the user provided the correct dependencies. Tell
|
||||
// the user this in an error.
|
||||
@ -633,7 +639,10 @@ const rule: Rule.RuleModule = {
|
||||
'dependencies.',
|
||||
});
|
||||
} else {
|
||||
declaredDependenciesNode.elements.forEach(declaredDependencyNode => {
|
||||
const arrayExpression = isTSAsArrayExpression
|
||||
? ((declaredDependenciesNode as TSAsExpression).expression as ArrayExpression)
|
||||
: (declaredDependenciesNode as ArrayExpression);
|
||||
arrayExpression.elements.forEach(declaredDependencyNode => {
|
||||
// Skip elided elements.
|
||||
if (declaredDependencyNode === null) {
|
||||
return;
|
||||
@ -1158,7 +1167,11 @@ const rule: Rule.RuleModule = {
|
||||
const reactiveHook = node.callee as Identifier | MemberExpression;
|
||||
const reactiveHookName = (getNodeWithoutReactNamespace(reactiveHook) as Identifier)
|
||||
.name;
|
||||
const declaredDependenciesNode = node.arguments[callbackIndex + 1];
|
||||
const maybeNode = node.arguments[callbackIndex + 1];
|
||||
const declaredDependenciesNode =
|
||||
maybeNode && !(maybeNode.type === 'Identifier' && maybeNode.name === 'undefined')
|
||||
? maybeNode
|
||||
: undefined;
|
||||
const isEffect = /Effect($|[^a-z])/g.test(reactiveHookName);
|
||||
|
||||
// Check whether a callback is supplied. If there is no callback supplied
|
||||
@ -1203,6 +1216,15 @@ const rule: Rule.RuleModule = {
|
||||
isEffect,
|
||||
);
|
||||
return; // Handled
|
||||
case 'TSAsExpression':
|
||||
visitFunctionWithDependencies(
|
||||
callback.expression,
|
||||
declaredDependenciesNode,
|
||||
reactiveHook,
|
||||
reactiveHookName,
|
||||
isEffect,
|
||||
);
|
||||
return; // Handled
|
||||
case 'Identifier':
|
||||
if (!declaredDependenciesNode) {
|
||||
// No deps, no problems.
|
||||
@ -1545,7 +1567,7 @@ function getConstructionExpressionType(node: Node) {
|
||||
}
|
||||
return null;
|
||||
case 'TypeCastExpression':
|
||||
return getConstructionExpressionType(node.expression);
|
||||
case 'AsExpression':
|
||||
case 'TSAsExpression':
|
||||
return getConstructionExpressionType(node.expression);
|
||||
}
|
||||
@ -1749,7 +1771,7 @@ function analyzePropertyChain(
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeWithoutReactNamespace(node: Identifier | MemberExpression) {
|
||||
function getNodeWithoutReactNamespace(node: Expression | Super) {
|
||||
if (
|
||||
node.type === 'MemberExpression' &&
|
||||
node.object.type === 'Identifier' &&
|
||||
|
@ -24,10 +24,7 @@ import { __EXPERIMENTAL__ } from './index';
|
||||
*/
|
||||
|
||||
function isHookName(s: string) {
|
||||
if (__EXPERIMENTAL__) {
|
||||
return s === 'use' || /^use[A-Z0-9]/.test(s);
|
||||
}
|
||||
return /^use[A-Z0-9]/.test(s);
|
||||
return s === 'use' || /^use[A-Z0-9]/.test(s);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,7 +56,7 @@ function isComponentName(node: Node) {
|
||||
return node.type === 'Identifier' && /^[A-Z]/.test(node.name);
|
||||
}
|
||||
|
||||
function isReactFunction(node: Expression | Super, functionName: string) {
|
||||
function isReactFunction(node: Node, functionName: string) {
|
||||
return (
|
||||
(node as Identifier).name === functionName ||
|
||||
(node.type === 'MemberExpression' &&
|
||||
@ -115,10 +112,7 @@ function isUseEffectEventIdentifier(node: Node) {
|
||||
}
|
||||
|
||||
function isUseIdentifier(node: Node) {
|
||||
if (__EXPERIMENTAL__) {
|
||||
return node.type === 'Identifier' && node.name === 'use';
|
||||
}
|
||||
return false;
|
||||
return isReactFunction(node, 'use');
|
||||
}
|
||||
|
||||
const rule: Rule.RuleModule = {
|
||||
|
@ -3,8 +3,8 @@
|
||||
"version": 1,
|
||||
"sources": {
|
||||
"main": {
|
||||
"repository": "git@github.com:facebook/react.git",
|
||||
"commit": "899cb95f52cc83ab5ca1eb1e268c909d3f0961e7",
|
||||
"repository": "https://github.com/facebook/react",
|
||||
"commit": "0e0b69321a6fcfe8a3eaae3b1016beb110437b38",
|
||||
"branch": "main"
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user