Skip to content

Commit

Permalink
update eslint to v9 (#1418)
Browse files Browse the repository at this point in the history
  • Loading branch information
noshiro-pf authored Jan 25, 2025
1 parent de8883a commit 411cac8
Show file tree
Hide file tree
Showing 54 changed files with 6,253 additions and 700 deletions.
17 changes: 17 additions & 0 deletions packages/eslint-configs/configs/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { toThisDir } from '@noshiro/mono-utils';
import * as nodePath from 'node:path';
import { defineConfig } from 'vitest/config';

const thisDir: string = toThisDir(import.meta.url);

// https://github.com/vitest-dev/vitest/blob/v1.5.0/test/import-meta/vite.config.ts
export default defineConfig({
test: {
globals: true,
dir: nodePath.resolve(thisDir, '../src'),
includeSource: [nodePath.resolve(thisDir, '../src/**/*.mts')],
typecheck: {
tsconfig: nodePath.resolve(thisDir, 'tsconfig.test.json'),
},
},
});
1 change: 1 addition & 0 deletions packages/eslint-configs/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const config = [
rules: {
'no-restricted-globals': 'off',
'import/no-internal-modules': 'off',
'@typescript-eslint/prefer-readonly-parameter-types': 'off',
},
},
{
Expand Down
23 changes: 15 additions & 8 deletions packages/eslint-configs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@
"fmt": "yarn zz:prettier .",
"gen-rules-type": "wireit",
"gen-rules-type:command": "node ./scripts/main.mjs",
"gi": "bash ../../scripts/bash/index_ts_generator.sh ./src --min-depth 0 --ext .mts",
"gi": "bash ../../scripts/bash/index_ts_generator.sh ./src --min-depth 0 --ext .mts --ignore plugins",
"lint": "run-p lint:scripts lint:src",
"lint:fix": "run-p lint:fix:scripts lint:fix:src",
"lint:fix:scripts": "yarn zz:eslint:scripts --fix",
"lint:fix:src": "yarn zz:eslint:src --fix",
"lint:scripts": "yarn zz:eslint:scripts",
"lint:src": "yarn zz:eslint:src",
"test": "yarn zz:vitest run",
"testw": "yarn zz:vitest watch",
"tsc": "yarn type-check",
"tscw": "tsc --noEmit --watch",
"type-check": "tsc --noEmit",
Expand All @@ -46,14 +48,16 @@
"zz:eslint:print-config": "yarn zz:eslint --print-config src/index.mts",
"zz:eslint:scripts": "yarn zz:eslint \"./scripts/**/*\"",
"zz:eslint:src": "yarn zz:eslint './src/**/*'",
"zz:prettier": "prettier --ignore-path ../../.prettierignore --ignore-unknown --no-error-on-unmatched-pattern --write"
"zz:prettier": "prettier --ignore-path ../../.prettierignore --ignore-unknown --no-error-on-unmatched-pattern --write",
"zz:vitest": "vitest --config ./configs/vitest.config.ts"
},
"dependencies": {
"@types/eslint": "9.6.1",
"@typescript-eslint/eslint-plugin": "8.20.0",
"@typescript-eslint/parser": "8.20.0",
"@typescript-eslint/utils": "8.20.0",
"eslint": "8.57.0",
"@typescript-eslint/eslint-plugin": "8.21.0",
"@typescript-eslint/parser": "8.21.0",
"@typescript-eslint/type-utils": "8.21.0",
"@typescript-eslint/utils": "8.21.0",
"eslint": "9.18.0",
"eslint-import-resolver-typescript": "3.7.0",
"eslint-plugin-array-func": "5.0.2",
"eslint-plugin-cypress": "4.1.0",
Expand All @@ -62,7 +66,7 @@
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jest": "28.11.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "^2.1.0",
"eslint-plugin-playwright": "2.2.0",
"eslint-plugin-prefer-arrow-functions": "3.6.2",
"eslint-plugin-promise": "7.2.1",
"eslint-plugin-react": "7.37.4",
Expand All @@ -76,10 +80,13 @@
"eslint-plugin-unicorn": "56.0.1",
"eslint-plugin-vitest": "0.5.4",
"globals": "15.14.0",
"typescript-eslint": "8.20.0"
"is-immutable-type": "1.2.9",
"tsutils": "3.21.0",
"typescript-eslint": "8.21.0"
},
"devDependencies": {
"@noshiro/mono-utils": "*",
"@typescript-eslint/rule-tester": "^8.21.0",
"json-schema-to-typescript": "^15.0.4"
},
"wireit": {
Expand Down
32 changes: 16 additions & 16 deletions packages/eslint-configs/scripts/replace.mts
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,22 @@ export const replaceRulesType = (src: string, typeName: string): string =>
endRegexp: closeBraceRegexp,
mapFn: replaceWithNoMatchCheck(
[
'export type Options = {',
' readonly disallowedWords?: readonly string[];',
' readonly ignoreSpaces?: boolean;',
' readonly ignoreTypeOfDescribeName?: boolean;',
' readonly ignoreTypeOfStepName?: boolean;',
' readonly ignoreTypeOfTestName?: boolean;',
' /**',
" * This interface was referenced by `Options`'s JSON-Schema definition via",
' * the `patternProperty` "^must(?:Not)?Match$".',
' */',
' readonly [k: string]:',
' | Record<string, string | readonly [string, string] | readonly [string]>',
' | string',
' | readonly [string, string]',
' | readonly [string];',
'};',
' export type Options = {',
' readonly disallowedWords?: readonly string[];',
' readonly ignoreSpaces?: boolean;',
' readonly ignoreTypeOfDescribeName?: boolean;',
' readonly ignoreTypeOfStepName?: boolean;',
' readonly ignoreTypeOfTestName?: boolean;',
' /**',
" * This interface was referenced by `Options`'s JSON-Schema definition via",
' * the `patternProperty` "^must(?:Not)?Match$".',
' */',
' readonly [k: string]:',
' | Record<string, string | readonly [string, string] | readonly [string]>',
' | string',
' | readonly [string, string]',
' | readonly [string];',
' };',
].join('\n'),

[
Expand Down
12 changes: 4 additions & 8 deletions packages/eslint-configs/scripts/update-dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ cd "${STRICT_TS_LIB_SOURCE_DIR}" || exit
yarn add -D \
@types/eslint@latest \
@typescript-eslint/eslint-plugin@latest \
@typescript-eslint/parser@latest
# TODO: enable this line after eslint v9 is supported in all plugins
# eslint-plugin-functional@latest \
# eslint@latest
@typescript-eslint/parser@latest \
eslint@latest

node "${ESLINT_DIR}/scripts/update-dependencies.mjs" "${STRICT_TS_LIB_SOURCE_DIR}/package.json" "devDependencies"

Expand Down Expand Up @@ -54,10 +52,8 @@ yarn add \
@types/eslint@latest \
globals@latest \
eslint-plugin-playwright@latest \
typescript-eslint@latest
# TODO: enable this line after eslint v9 is supported in all plugins
# eslint-plugin-functional@latest \
# eslint@latest
typescript-eslint@latest \
eslint@latest

node "${ESLINT_DIR}/scripts/update-dependencies.mjs" "${ESLINT_DIR}/package.json" "dependencies"

Expand Down
14 changes: 6 additions & 8 deletions packages/eslint-configs/src/configs/plugins.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import eslintPluginArrayFunc from 'eslint-plugin-array-func';

import eslintPluginFunctional from 'eslint-plugin-functional';

// @ts-expect-error no type definition
import eslintPluginTotalFunctions from 'eslint-plugin-total-functions';

// @ts-expect-error no type definition
import eslintPluginSecurity from 'eslint-plugin-security';

Expand Down Expand Up @@ -45,12 +42,15 @@ import eslintPluginReactRefresh from 'eslint-plugin-react-refresh';
// @ts-expect-error no type definition
import eslintPluginJsxA11y from 'eslint-plugin-jsx-a11y';

// @ts-expect-error no type definition
import eslintPluginTreeShakable from 'eslint-plugin-tree-shakable';

// @ts-expect-error no type definition
import eslintPluginEslintPlugin from 'eslint-plugin-eslint-plugin';

// import eslintPluginTotalFunctions from 'eslint-plugin-total-functions';
import { eslintPluginTotalFunctions } from '../plugins/total-functions/index.mjs';

// import eslintPluginTreeShakable from 'eslint-plugin-tree-shakable';
import { eslintPluginTreeShakable } from '../plugins/tree-shakable/index.mjs';

import { type FlatConfig, type Plugin } from '../types/index.mjs';

export const plugins: Record<
Expand Down Expand Up @@ -104,10 +104,8 @@ export const plugins: Record<
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'strict-dependencies': eslintPluginStrictDependencies,
'testing-library': eslintPluginTestingLibrary,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'total-functions': eslintPluginTotalFunctions,
unicorn: eslintPluginUnicorn,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'tree-shakable': eslintPluginTreeShakable,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'eslint-plugin': eslintPluginEslintPlugin,
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-configs/src/plugins/index.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './total-functions/index.mjs';
export * from './tree-shakable/index.mjs';
8 changes: 8 additions & 0 deletions packages/eslint-configs/src/plugins/total-functions/index.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { type Plugin } from '../../types/index.mjs';
import { rules } from './rules/index.mjs';

// forked from https://github.com/danielnixon/eslint-plugin-total-functions v7.1.0

export const eslintPluginTotalFunctions: Omit<Plugin, 'configs'> = {
rules,
} as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
AST_NODE_TYPES,
ESLintUtils,
type TSESTree,
} from '@typescript-eslint/utils';
import { unionTypeParts } from 'tsutils';
import { type Type, type TypeChecker } from 'typescript';

export const createRule = ESLintUtils.RuleCreator(
() => 'https://github.com/danielnixon/eslint-plugin-total-functions',
);

export const typeSymbolName = (type: Type): string | undefined => {
try {
// HACK despite what the type suggests, symbol can in fact be undefined
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return type?.symbol?.name;
} catch {
// Accessing symbol can throw for reasons I don't fully understand.
return undefined;
}
};

export type TypePair = Readonly<{
destinationType: Type;
sourceType: Type;
}>;

/**
* Breaks the supplied types into their union type parts and returns an array of
* pairs of constituent types that are assignable.
*/
export const assignableTypePairs = (
rawDestinationType: Type,
rawSourceType: Type,
checker: TypeChecker,
): readonly TypePair[] => {
const destinationTypeParts = unionTypeParts(rawDestinationType);

const sourceTypeParts = unionTypeParts(rawSourceType);

return sourceTypeParts.flatMap((sourceTypePart) =>
destinationTypeParts
.filter((destinationTypePart) =>
checker.isTypeAssignableTo(sourceTypePart, destinationTypePart),
)
.map(
(destinationTypePart) =>
({
sourceType: sourceTypePart,
destinationType: destinationTypePart,
}) as const,
),
);
};

/** True if this expression is a literal, false otherwise. */
export const isLiteral = (
sourceNode: TSESTree.Expression | undefined,
): boolean => {
if (sourceNode === undefined) {
return false;
}

if (sourceNode.type === AST_NODE_TYPES.ObjectExpression) {
// empty object literal: {}
return sourceNode.properties.length === 0;
}

if (sourceNode.type === AST_NODE_TYPES.ArrayExpression) {
// empty object literal: []
return sourceNode.elements.length === 0;
}

// TODO: handle recursive case for both arrays and objects and
// permit literals such as string and numbers as properties

return false;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { type Type } from 'typescript';
import { typeSymbolName } from './common.mjs';

// Note: `Lazy` deliberately excluded even though it has the same signature as `IO`, its semantics
// don't imply impurity like IO.
export const effects: readonly string[] = [
'IO',
'IOEither',
'IOOption',
'ReaderTask',
'ReaderTaskEither',
'StateReaderTaskEither',
'Task',
'TaskEither',
'TaskOption',
'TaskThese',
] as const;

export type FpTsEffectType = Readonly<{
effectType: Type;
effectName: string;
effectTypeParameter: Type | undefined;
}>;

const fpTsEffectTypeParameter = (
effectName: string,
effectType: Type,
): Type | undefined => {
if (effectName === 'IO') {
const signatures = effectType.getCallSignatures();
const signature = signatures[0];

if (signatures.length !== 1 || signature === undefined) {
return undefined;
}

return signature.getReturnType();
}

// TODO extract the type param from other effect types.
return undefined;
};

export const fpTsEffectType = (type: Type): FpTsEffectType | undefined => {
const symbolName = typeSymbolName(type);

if (symbolName === undefined || !effects.includes(symbolName)) {
return undefined;
}

return {
effectType: type,
effectName: symbolName,
effectTypeParameter: fpTsEffectTypeParameter(symbolName, type),
} as const;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { type Plugin } from '../../../types/index.mjs';
import { noEnums } from './no-enums.mjs';
import { noHiddenTypeAssertions } from './no-hidden-type-assertions.mjs';
import { noNestedFpTsEffects } from './no-nested-fp-ts-effects.mjs';
import { noPartialArrayReduce } from './no-partial-array-reduce.mjs';
import { noPartialDivision } from './no-partial-division.mjs';
import { noPartialStringNormalize } from './no-partial-string-normalize.mjs';
import { noPartialUrlConstructor } from './no-partial-url-constructor.mjs';
import { noPrematureFpTsEffects } from './no-premature-fp-ts-effects.mjs';
import { noUnsafeMutableReadonlyAssignment } from './no-unsafe-mutable-readonly-assignment.mjs';
import { noUnsafeReadonlyMutableAssignment } from './no-unsafe-readonly-mutable-assignment.mjs';
import { noUnsafeTypeAssertion } from './no-unsafe-type-assertion.mjs';
import { requireStrictMode } from './require-strict-mode.mjs';

export const rules = {
'require-strict-mode': requireStrictMode,
'no-unsafe-type-assertion': noUnsafeTypeAssertion,
'no-unsafe-readonly-mutable-assignment': noUnsafeReadonlyMutableAssignment,
'no-unsafe-mutable-readonly-assignment': noUnsafeMutableReadonlyAssignment,
'no-enums': noEnums,
'no-partial-url-constructor': noPartialUrlConstructor,
'no-partial-division': noPartialDivision,
'no-partial-string-normalize': noPartialStringNormalize,
'no-premature-fp-ts-effects': noPrematureFpTsEffects,
'no-nested-fp-ts-effects': noNestedFpTsEffects,
'no-partial-array-reduce': noPartialArrayReduce,
'no-hidden-type-assertions': noHiddenTypeAssertions,
} as const satisfies Plugin['rules'];
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createRule } from './common.mjs';

/** An ESLint rule to ban enums. */

export const noEnums = createRule({
name: 'no-enums',
meta: {
type: 'problem',
docs: {
description: 'Bans enums.',
},
messages: {
errorStringGeneric: "Don't declare enums.",
},
schema: [],
},
create: (context) => ({
TSEnumDeclaration: (node) => {
context.report({
node,
messageId: 'errorStringGeneric',
} as const);
},
}),
defaultOptions: [],
} as const);
Loading

0 comments on commit 411cac8

Please sign in to comment.