mirror of
https://github.com/pezkuwichain/pezkuwi-dev.git
synced 2026-10-22 19:56:18 +00:00
Drop the contextual report of no-unnecessary-type-assertion on as
typescript-eslint 8 reports `x as T` as unnecessary when the receiver
would accept `x` unchanged, and its autofix deletes the assertion. That
is unsound when the assertion also drives inference around it:
const { validators } = found || { validators: {} as ValPoints };
loses the type of `validators`, and every later use becomes an
unresolved type (api-derive's stakerRewards after an autofix). It also
deletes assertions kept on purpose for another environment's typings.
Across api, common, ui and apps it raised 45 reports.
The plugin is now registered through withoutContextualAssertions, which
drops only that report on `as` / `<T>` assertions. Assertions to the
type an expression already has are still reported and fixed, and so
are non-null assertions a receiver makes unnecessary. A spec lints a
fixture through the real type checker with the plugin wrapped and as
published, so it fails if the filter stops filtering.
This commit is contained in:
1 parent
cd810d5ea2
commit
05b2fdafad
3 files changed
+154
-1
No files matched your search
@@ -0,0 +1,86 @@
|
||||
// Copyright 2017-2026 @pezkuwi/dev authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@pezkuwi/dev-test/globals.d.ts" />
|
||||
|
||||
import type { ESLint as ESLintType } from 'eslint';
|
||||
|
||||
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import { ESLint } from 'eslint';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
// Loaded by path: the config directory is plain JavaScript outside this
|
||||
// package's TypeScript root.
|
||||
const WRAPPER = path.join(process.cwd(), 'packages/dev/config/eslint.typeAssertion.js');
|
||||
|
||||
const SOURCE = `interface ValPoints { [id: string]: number }
|
||||
|
||||
declare const found: { validators: ValPoints } | undefined;
|
||||
declare const text: string;
|
||||
declare function take (value: string): void;
|
||||
|
||||
// contextual: the assertion types \`validators\` below
|
||||
const { validators } = found || { validators: {} as ValPoints };
|
||||
|
||||
export const first = validators['a'];
|
||||
|
||||
// unchanged: the expression already has this type
|
||||
export const same = text as string;
|
||||
|
||||
let target: string | undefined;
|
||||
|
||||
// contextual non-null: assigning to it needs no assertion
|
||||
target! = text;
|
||||
|
||||
take(target ?? '');
|
||||
`;
|
||||
|
||||
async function lint (plugin: ESLintType.Plugin): Promise<string[]> {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'type-assertion-'));
|
||||
|
||||
try {
|
||||
fs.writeFileSync(path.join(dir, 'tsconfig.json'), JSON.stringify({ compilerOptions: { strict: true, target: 'es2022' }, include: ['*.ts'] }));
|
||||
fs.writeFileSync(path.join(dir, 'sample.ts'), SOURCE);
|
||||
|
||||
const eslint = new ESLint({
|
||||
cwd: dir,
|
||||
overrideConfig: [{
|
||||
files: ['**/*.ts'],
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
parserOptions: { project: './tsconfig.json', tsconfigRootDir: dir }
|
||||
},
|
||||
plugins: { '@typescript-eslint': plugin },
|
||||
rules: { '@typescript-eslint/no-unnecessary-type-assertion': 'error' }
|
||||
}],
|
||||
overrideConfigFile: true
|
||||
});
|
||||
const [result] = await eslint.lintFiles(['sample.ts']);
|
||||
|
||||
return result.messages.map(({ line, messageId }) => `${line}:${messageId ?? ''}`);
|
||||
} finally {
|
||||
fs.rmSync(dir, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe('withoutContextualAssertions', (): void => {
|
||||
it('drops the contextual report on an assertion, keeps the others', async (): Promise<void> => {
|
||||
const { withoutContextualAssertions } = await import(WRAPPER) as { withoutContextualAssertions: (p: ESLintType.Plugin) => ESLintType.Plugin };
|
||||
|
||||
expect(await lint(withoutContextualAssertions(tsPlugin as unknown as ESLintType.Plugin))).toEqual([
|
||||
'13:unnecessaryAssertion',
|
||||
'18:contextuallyUnnecessary'
|
||||
]);
|
||||
});
|
||||
|
||||
it('is needed: the plugin as published reports the assertion that types validators', async (): Promise<void> => {
|
||||
expect(await lint(tsPlugin as unknown as ESLintType.Plugin)).toEqual([
|
||||
'8:contextuallyUnnecessary',
|
||||
'13:unnecessaryAssertion',
|
||||
'18:contextuallyUnnecessary'
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user