Skip to content

Commit

Permalink
✨ feature(eslint)!: require eslint v9.5+
Browse files Browse the repository at this point in the history
Signed-off-by: Pauline <[email protected]>
  • Loading branch information
pauliesnug committed Aug 29, 2024
1 parent 02089c2 commit 948e472
Show file tree
Hide file tree
Showing 14 changed files with 311 additions and 263 deletions.
1 change: 1 addition & 0 deletions packages/eslint-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- sorted imports, dangling commas
- single quotes, semicolons
- uses eslint [stylistic]
- supports `eslint` v9.5+

## usage

Expand Down
15 changes: 8 additions & 7 deletions packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"@tanstack/eslint-plugin-query": "^5.52.0",
"@unocss/eslint-plugin": ">=0.60.0 < 1",
"astro-eslint-parser": "^1.0.2",
"eslint": ">=8.40.0",
"eslint": "^9.5.0",
"eslint-plugin-astro": "^1.2.3",
"eslint-plugin-format": ">=0.1.0",
"eslint-plugin-json-schema-validator": "^5.1.2",
Expand Down Expand Up @@ -135,14 +135,15 @@
"@antfu/install-pkg": "^0.4.1",
"@clack/prompts": "^0.7.0",
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.0",
"@stylistic/eslint-plugin": "^2.6.4",
"@eslint/compat": "^1.1.1",
"@stylistic/eslint-plugin": "^2.7.1",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
"@vitest/eslint-plugin": "^1.0.5",
"@vitest/eslint-plugin": "^1.1.0",
"eslint-flat-config-utils": "^0.3.1",
"eslint-merge-processors": "^0.1.0",
"eslint-plugin-command": "^0.2.3",
"eslint-plugin-import-x": "^4.1.0",
"eslint-plugin-import-x": "^4.1.1",
"eslint-plugin-jsdoc": "^50.2.2",
"eslint-plugin-jsonc": "^2.16.0",
"eslint-plugin-markdown": "^5.1.0",
Expand All @@ -154,16 +155,15 @@
"eslint-plugin-unicorn": "^55.0.0",
"eslint-plugin-unused-imports": "^4.1.3",
"eslint-plugin-yml": "^1.14.0",
"find-up": "^7.0.0",
"find-up-simple": "^1.0.0",
"globals": "^15.9.0",
"jsonc-eslint-parser": "^2.4.0",
"local-pkg": "^0.5.0",
"parse-gitignore": "^2.0.0",
"toml-eslint-parser": "^0.10.0",
"yaml-eslint-parser": "^1.2.3"
},
"devDependencies": {
"@eslint-react/eslint-plugin": "^1.12.2",
"@eslint-react/eslint-plugin": "^1.12.3",
"@eslint/config-inspector": "^0.5.4",
"@prettier/plugin-xml": "^3.4.1",
"@tanstack/eslint-plugin-query": "^5.52.0",
Expand All @@ -190,6 +190,7 @@
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"jiti": "^1.21.6",
"pathe": "^1.1.2",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-slidev": "^1.0.5",
"svelte": "^4.2.19",
Expand Down
65 changes: 51 additions & 14 deletions packages/eslint-config/src/configs/gitignore.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,73 @@
import fs from 'node:fs';
import { findUpSync } from 'find-up';
// @ts-expect-error no types
import parse from 'parse-gitignore';
import process from 'node:process';
import path from 'pathe';
import { findUpSync } from 'find-up-simple';
import type { OptionsGitignore, TypedFlatConfigItem } from '../types';
import { toArray } from '../utils';
import { convertIgnorePatternToMinimatch } from '@eslint/compat';

export async function gitignore(options: OptionsGitignore = {}): Promise<TypedFlatConfigItem[]> {
// eslint-disable-next-line perfectionist/sort-objects -- root needs to be defined first
const { root = false, strict = true, files = root ? ['.gitignore'] : [findUpSync('.gitignore')!] || [] } = options;
const ignores: string[] = [];
const { cwd = process.cwd(), root = false, strict = true } = options;
const files = toArray(options.files ?? root ? ['.gitignore'] : findUpSync('.gitignore') || []);

files.forEach((f) => {
if (!fs.existsSync(f))
if (strict)
throw new Error(`.gitignore file was not found: ${f}`);
else return;

const readFile = fs.readFileSync(f, 'utf-8');
parse(`${readFile}\n`).globs().forEach((g: any) => {
if (g.type === 'ignore')
ignores.push(...g.patterns);
else if (g.type === 'unignore')
ignores.push(...g.patterns.map((p: string) => `!${p}`));
});
const content = fs.readFileSync(f, 'utf-8');
const relativePath = path.relative(cwd, path.dirname(f));
const globs = content.split(/\r?\n/u)
.filter(l => l && !l.startsWith('#'))
.map(l => convertIgnorePatternToMinimatch(l))
.map(g => relativeMinimatch(g, relativePath, cwd))
.filter(g => g !== null);

ignores.push(...globs);
});

if (strict && files.length === 0)
throw new Error('no .gitignore file found');

return [
{
ignores,
// todo
// name: 'petal/ignores/git',
name: 'petal/ignores/git',
},
];
}

function relativeMinimatch(pattern: string, relativePath: string, cwd = process.cwd()) {
if (['', '.', '/'].includes(relativePath))
return pattern;

const negated = pattern.startsWith('!') ? '!' : ':';
const minimatch = { clearPattern: negated ? pattern.slice(1) : pattern };

if (!relativePath.endsWith('/'))
relativePath = `${relativePath}/`;

const parent = relativePath.startsWith('..');

if (!parent)
return `${negated}${relativePath}${minimatch.clearPattern}`;

if (!relativePath.match(/^(\.\.\/)+$/))
throw new Error('the ignored file location should be a parent or child directory.');

if (minimatch.clearPattern.startsWith('**'))
return pattern;

const parents = path.relative(path.resolve(cwd, relativePath), cwd).split(/[/\\]/);
while (parents.length && minimatch.clearPattern.startsWith(`${parents[0]}/`)) {
minimatch.clearPattern = minimatch.clearPattern.slice(parents[0].length + 1);
parents.shift();
}

if (minimatch.clearPattern.startsWith('**') || parents.length === 0)
return `${negated}${minimatch.clearPattern}`;

return null;
}
10 changes: 6 additions & 4 deletions packages/eslint-config/src/configs/ignores.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { TypedFlatConfigItem } from '../types';
import { GLOB_EXCLUDE } from '../globs';

export async function ignores(): Promise<TypedFlatConfigItem[]> {
export async function ignores(userIgnores: string[] = []): Promise<TypedFlatConfigItem[]> {
return [
{
ignores: GLOB_EXCLUDE,
// todo
// name: 'petal/ignores',
ignores: [
...GLOB_EXCLUDE,
...userIgnores,
],
name: 'petal/ignores',
},
];
}
54 changes: 25 additions & 29 deletions packages/eslint-config/src/configs/unicorn.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,36 @@
import type { TypedFlatConfigItem } from '../types';
import type { OptionsUnicorn, TypedFlatConfigItem } from '../types';
import { interopDefault } from '../utils';

export async function unicorn(): Promise<TypedFlatConfigItem[]> {
export async function unicorn(options: OptionsUnicorn = {}): Promise<TypedFlatConfigItem[]> {
const pluginUnicorn = await interopDefault(import('eslint-plugin-unicorn'));

return [
{
name: 'petal/unicorn/rules',
plugins: {
unicorn: await interopDefault(import('eslint-plugin-unicorn')),
unicorn: pluginUnicorn,
},
rules: {
// Pass error message when throwing errors
'unicorn/error-message': 'error',
// Uppercase regex escapes
'unicorn/escape-case': 'error',
// Array.isArray instead of instanceof
'unicorn/no-instanceof-array': 'error',
// Ban `new Array` as `Array` constructor's params are ambiguous
'unicorn/no-new-array': 'error',
// Prevent deprecated `new Buffer()`
'unicorn/no-new-buffer': 'error',
// Lowercase number formatting for octal, hex, binary (0x1'error' instead of 0X1'error')
'unicorn/number-literal-case': 'error',
// textContent instead of innerText
'unicorn/prefer-dom-node-text-content': 'error',
// includes over indexOf when checking for existence
'unicorn/prefer-includes': 'error',
// Prefer using the node: protocol
'unicorn/prefer-node-protocol': 'error',
// Prefer using number properties like `Number.isNaN` rather than `isNaN`
'unicorn/prefer-number-properties': 'error',
// String methods startsWith/endsWith instead of more complicated stuff
'unicorn/prefer-string-starts-ends-with': 'error',
// Enforce throwing type error when throwing error while checking typeof
'unicorn/prefer-type-error': 'error',
// Use new when throwing error
'unicorn/throw-new-error': 'error',
...(options.useRecommended
? pluginUnicorn.configs['flat/recommended'].rules
: {
'unicorn/consistent-empty-array-spread': 'error',
'unicorn/consistent-function-scoping': 'error',
'unicorn/error-message': 'error',
'unicorn/escape-case': 'error',
'unicorn/new-for-builtins': 'error',
'unicorn/no-instanceof-array': 'error',
'unicorn/no-new-array': 'error',
'unicorn/no-new-buffer': 'error',
'unicorn/number-literal-case': 'error',
'unicorn/prefer-dom-node-text-content': 'error',
'unicorn/prefer-includes': 'error',
'unicorn/prefer-node-protocol': 'error',
'unicorn/prefer-number-properties': 'error',
'unicorn/prefer-string-starts-ends-with': 'error',
'unicorn/prefer-type-error': 'error',
'unicorn/throw-new-error': 'error',
}),
},
},
];
Expand Down
18 changes: 11 additions & 7 deletions packages/eslint-config/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,15 @@ import {
} from './configs';
import { getOverrides, isInEditorEnv, resolveSubOptions } from './utils';

const flatConfigProps: (keyof TypedFlatConfigItem)[] = [
'files',
'ignores',
const flatConfigProps = [
'languageOptions',
'linterOptions',
'name',
'plugins',
'processor',
'rules',
'settings',
];
] satisfies (keyof TypedFlatConfigItem)[];

const VUE_PACKAGES = ['@slidev/cli', 'nuxt', 'vitepress', 'vue'];
const SOLID_PACKAGES = ['solid-js', 'solid-refresh', 'vite-plugin-solid'];
Expand All @@ -72,7 +70,7 @@ export const defaultPluginRenaming: Record<string, string> = {
'yml': 'yaml',
};

type FactoryOptions = OptionsConfig & TypedFlatConfigItem;
type FactoryOptions = OptionsConfig & Omit<TypedFlatConfigItem, 'files'>;
type UserConfig = Awaitable<TypedFlatConfigItem | TypedFlatConfigItem[] | FlatConfigComposer<any, any> | Linter.Config[]>;
type FactoryComposer = FlatConfigComposer<TypedFlatConfigItem, ConfigNames>;

Expand All @@ -92,6 +90,7 @@ export function defineConfig(options: FactoryOptions = {}, ...userConfigs: UserC
solid: enableSolid = SOLID_PACKAGES.some(pkgSort),
svelte: enableSvelte = SVELTE_PACKAGES.some(pkgSort),
typescript: enableTypeScript = TYPESCRIPT_PACKAGES.some(pkgSort),
unicorn: enableUnicorn = true,
vue: enableVue = VUE_PACKAGES.some(pkgSort),
} = options;

Expand Down Expand Up @@ -121,16 +120,18 @@ export function defineConfig(options: FactoryOptions = {}, ...userConfigs: UserC
const typescriptOptions = resolveSubOptions(options, 'typescript');
const tsconfigPath = 'tsconfigPath' in typescriptOptions ? typescriptOptions.tsconfigPath : undefined;

configs.push(ignores());
configs.push(ignores(options.ignores));
configs.push(javascript({ isInEditor, overrides: getOverrides(options, 'javascript') }));
configs.push(comments());
configs.push(node());
configs.push(jsdoc({ stylistic: stylisticOptions }));
configs.push(imports({ stylistic: stylisticOptions }));
configs.push(unicorn());
configs.push(command());
configs.push(perfectionist());

if (enableUnicorn)
configs.push(unicorn(enableUnicorn === true ? {} : enableUnicorn));

if (enableVue)
componentExts.push('vue');

Expand Down Expand Up @@ -246,6 +247,9 @@ export function defineConfig(options: FactoryOptions = {}, ...userConfigs: UserC
typeof stylisticOptions === 'boolean' ? {} : stylisticOptions,
));

if ('files' in options)
throw new Error('[@flowr/eslint-config]: the first argument should not contain the "files" property as options are supposed to be global. place it in a different config block instead.');

const fusedConfig = flatConfigProps.reduce((curr, key) => {
if (key in options)
curr[key] = options[key] as any;
Expand Down
9 changes: 8 additions & 1 deletion packages/eslint-config/src/types/configs/gitignore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export interface OptionsGitignore {
/**
* Path to `.gitignore` files, or files with compatible formats like `.eslintignore`.
*
* @default [`${CWD}.gitignore`]
* @default [`.gitignore`] // or findUpSync('.gitignore')
*/
files?: string[];

Expand All @@ -22,4 +22,11 @@ export interface OptionsGitignore {
* @default false
*/
root?: boolean;

/**
* Current working directory, used to resolve relative paths.
*
* @default process.cwd()
*/
cwd?: string;
}
1 change: 1 addition & 0 deletions packages/eslint-config/src/types/configs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export type * from './markdown';
export type * from './schema';
export type * from './imports';
export type * from './formats';
export type * from './unicorn';
9 changes: 9 additions & 0 deletions packages/eslint-config/src/types/configs/unicorn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface OptionsUnicorn {
/**
* Include all rules recommended by `eslint-plugin-unicorn`.
*
* @see https://github.com/sindresorhus/eslint-plugin-unicorn#rules
* @default false
*/
useRecommended?: boolean;
}
9 changes: 9 additions & 0 deletions packages/eslint-config/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { StylisticConfig } from './configs/stylistic';
import type { OptionsJsonc, OptionsToml, OptionsYaml } from './configs/formats';
import type { OptionsMarkdown } from './configs/markdown';
import type { OptionsSchema } from './configs/schema';
import type { OptionsUnicorn } from './configs/unicorn';

export type * from './prettier';
export type * from './configs';
Expand Down Expand Up @@ -78,6 +79,14 @@ export interface OptionsConfig extends OptionsComponentExts, OptionsProjectType
*/
jsx?: boolean;

/**
* Enable `eslint-plugin-unicorn` support.
*
* @see https://github.com/sindresorhus/eslint-plugin-unicorn
* @default true
*/
unicorn?: boolean | OptionsUnicorn;

/**
* Enable Vitest support.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"stub": "unbuild --stub"
},
"peerDependencies": {
"eslint": "^9.9.1"
"eslint": "^9.5.0"
},
"dependencies": {
"@flowr/utils": "workspace:^"
Expand Down
3 changes: 3 additions & 0 deletions packages/utils/build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export default defineBuildConfig({
entries: [
'src/index',
'src/fetch',
'src/debug',
'src/error',
'src/core',
],
declaration: true,
clean: true,
Expand Down
Loading

0 comments on commit 948e472

Please sign in to comment.