Inline repo
This commit is contained in:
@ -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;
|
||||
}
|
Reference in New Issue
Block a user