diff --git a/lib/utils/parser-config-resolver/parse-by-parser.ts b/lib/utils/parser-config-resolver/parse-by-parser.ts index 83b7f22b..433e70ee 100644 --- a/lib/utils/parser-config-resolver/parse-by-parser.ts +++ b/lib/utils/parser-config-resolver/parse-by-parser.ts @@ -4,19 +4,43 @@ import path from 'path' import { parseForESLint } from 'vue-eslint-parser' import type { ParseResult } from '.' +function stripTypeAwareOptions( + parserOptions: unknown +): Record { + if ( + parserOptions == null || + typeof parserOptions !== 'object' || + Array.isArray(parserOptions) + ) { + return (parserOptions ?? {}) as Record + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { project, programs, projectService, ...rest } = + parserOptions as Record + if ( + rest.parserOptions != null && + typeof rest.parserOptions === 'object' && + !Array.isArray(rest.parserOptions) + ) { + rest.parserOptions = stripTypeAwareOptions(rest.parserOptions) + } + return rest +} + export function parseByParser( filePath: string, parserDefine: Linter.Parser | string | undefined, parserOptions: unknown ): ParseResult { const parser = getParser(parserDefine, filePath) + const safeParserOptions = stripTypeAwareOptions(parserOptions) try { const text = readFileSync(path.resolve(filePath), 'utf8') const parseResult = 'parseForESLint' in parser && typeof parser.parseForESLint === 'function' - ? parser.parseForESLint(text, parserOptions) + ? parser.parseForESLint(text, safeParserOptions) : // eslint-disable-next-line @typescript-eslint/no-explicit-any - { ast: (parser as any).parse(text, parserOptions) } + { ast: (parser as any).parse(text, safeParserOptions) } return parseResult as ParseResult } catch (_e) { return null diff --git a/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/eslint.config.cjs b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/eslint.config.cjs new file mode 100644 index 00000000..e9560aa7 --- /dev/null +++ b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/eslint.config.cjs @@ -0,0 +1,43 @@ +const base = require("../../../../../lib/configs/flat/base"); +module.exports = [ + ...base, + { + files: ['**/*.vue', '*.vue'], + languageOptions: { + parser: require('vue-eslint-parser'), + parserOptions: { + parser: "@typescript-eslint/parser", + project: true, + }, + }, + }, + { + files: ['**/*.ts', '*.ts'], + languageOptions: { + parser: require('@typescript-eslint/parser'), + parserOptions: { + project: true, + }, + }, + }, + { + rules: { + "@intlify/vue-i18n/no-unused-keys": [ + "error", + { + src: "./src", + extensions: [".tsx", ".ts", ".vue"], + enableFix: true, + }, + ], + }, + settings: { + "vue-i18n": { + localeDir: { + pattern: `./locales/*.{json,yaml,yml}`, + localeKey: "file", + }, + }, + }, + }, +]; diff --git a/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/locales/en.json b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/locales/en.json new file mode 100644 index 00000000..da032bfa --- /dev/null +++ b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/locales/en.json @@ -0,0 +1,13 @@ +{ + "hello": "hello world", + "messages": { + "hello": "hi DIO!", + "link": "@:message.hello", + "nested": { + "hello": "hi jojo!" + } + }, + "hello_dio": "hello underscore DIO!", + "hello {name}": "hello {name}!", + "hello-dio": "hello hyphen DIO!" +} diff --git a/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/locales/ja.yaml b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/locales/ja.yaml new file mode 100644 index 00000000..422fec42 --- /dev/null +++ b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/locales/ja.yaml @@ -0,0 +1,9 @@ +hello: "ハローワールド" +messages: + hello: "こんにちは、DIO!" + link: "@:message.hello" + nested: + hello: "こんにちは、ジョジョ!" +hello_dio: "こんにちは、アンダースコア DIO!" +"hello {name}": "こんにちは、{name}!" +hello-dio: "こんにちは、ハイフン DIO!" diff --git a/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/src/App.vue b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/src/App.vue new file mode 100644 index 00000000..39f19675 --- /dev/null +++ b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/src/App.vue @@ -0,0 +1,14 @@ + + + diff --git a/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/src/main.ts b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/src/main.ts new file mode 100644 index 00000000..9beae6b4 --- /dev/null +++ b/tests/fixtures/no-unused-keys/invalid/typescript-with-project-true/src/main.ts @@ -0,0 +1,2 @@ +const $t = (a:any) => {} +$t('hello') diff --git a/tests/lib/rules/no-unused-keys.ts b/tests/lib/rules/no-unused-keys.ts index 5e3c0a1a..9f611d0b 100644 --- a/tests/lib/rules/no-unused-keys.ts +++ b/tests/lib/rules/no-unused-keys.ts @@ -3324,6 +3324,241 @@ messages: nested: {} hello_dio: "こんにちは、アンダースコア DIO!" "hello {name}": "こんにちは、{name}!" +` + } + ] + } + ] + } + } + ), + ...getTestCasesFromFixtures( + { + eslint: '>=8', + cwd: join(cwdRoot, './invalid/typescript-with-project-true'), + localeDir: { + pattern: `./locales/*.{json,yaml,yml}`, + localeKey: 'file' + }, + options: [ + { + src: './src', + extensions: ['.tsx', '.ts', '.vue'], + enableFix: true + } + ], + languageOptions: { + parser: tsParser + } + }, + { + 'src/App.vue': true, + 'locales/en.json': { + output: `{ + "hello": "hello world", + "messages": { + "hello": "hi DIO!", + "nested": { + } + }, + "hello_dio": "hello underscore DIO!", + "hello {name}": "hello {name}!" +} +`, + errors: [ + { + message: "unused 'messages.link' key", + suggestions: [ + { + desc: "Remove the 'messages.link' key.", + output: `{ + "hello": "hello world", + "messages": { + "hello": "hi DIO!", + "nested": { + "hello": "hi jojo!" + } + }, + "hello_dio": "hello underscore DIO!", + "hello {name}": "hello {name}!", + "hello-dio": "hello hyphen DIO!" +} +` + }, + { + desc: 'Remove all unused keys.', + output: `{ + "hello": "hello world", + "messages": { + "hello": "hi DIO!", + "nested": { + } + }, + "hello_dio": "hello underscore DIO!", + "hello {name}": "hello {name}!" +} +` + } + ] + }, + { + message: "unused 'messages.nested.hello' key", + suggestions: [ + { + desc: "Remove the 'messages.nested.hello' key.", + output: `{ + "hello": "hello world", + "messages": { + "hello": "hi DIO!", + "link": "@:message.hello", + "nested": { + } + }, + "hello_dio": "hello underscore DIO!", + "hello {name}": "hello {name}!", + "hello-dio": "hello hyphen DIO!" +} +` + }, + { + desc: 'Remove all unused keys.', + output: `{ + "hello": "hello world", + "messages": { + "hello": "hi DIO!", + "nested": { + } + }, + "hello_dio": "hello underscore DIO!", + "hello {name}": "hello {name}!" +} +` + } + ] + }, + { + message: "unused 'hello-dio' key", + suggestions: [ + { + desc: "Remove the 'hello-dio' key.", + output: `{ + "hello": "hello world", + "messages": { + "hello": "hi DIO!", + "link": "@:message.hello", + "nested": { + "hello": "hi jojo!" + } + }, + "hello_dio": "hello underscore DIO!", + "hello {name}": "hello {name}!" +} +` + }, + { + desc: 'Remove all unused keys.', + output: `{ + "hello": "hello world", + "messages": { + "hello": "hi DIO!", + "nested": { + } + }, + "hello_dio": "hello underscore DIO!", + "hello {name}": "hello {name}!" +} +` + } + ] + } + ] + }, + 'locales/ja.yaml': { + output: `hello: "ハローワールド" +messages: + hello: "こんにちは、DIO!" + nested: {} +hello_dio: "こんにちは、アンダースコア DIO!" +"hello {name}": "こんにちは、{name}!" +`, + errors: [ + { + message: "unused 'messages.link' key", + suggestions: [ + { + desc: "Remove the 'messages.link' key.", + output: `hello: "ハローワールド" +messages: + hello: "こんにちは、DIO!" + nested: + hello: "こんにちは、ジョジョ!" +hello_dio: "こんにちは、アンダースコア DIO!" +"hello {name}": "こんにちは、{name}!" +hello-dio: "こんにちは、ハイフン DIO!" +` + }, + { + desc: 'Remove all unused keys.', + output: `hello: "ハローワールド" +messages: + hello: "こんにちは、DIO!" + nested: {} +hello_dio: "こんにちは、アンダースコア DIO!" +"hello {name}": "こんにちは、{name}!" +` + } + ] + }, + { + message: "unused 'messages.nested.hello' key", + suggestions: [ + { + desc: "Remove the 'messages.nested.hello' key.", + output: `hello: "ハローワールド" +messages: + hello: "こんにちは、DIO!" + link: "@:message.hello" + nested: {} +hello_dio: "こんにちは、アンダースコア DIO!" +"hello {name}": "こんにちは、{name}!" +hello-dio: "こんにちは、ハイフン DIO!" +` + }, + { + desc: 'Remove all unused keys.', + output: `hello: "ハローワールド" +messages: + hello: "こんにちは、DIO!" + nested: {} +hello_dio: "こんにちは、アンダースコア DIO!" +"hello {name}": "こんにちは、{name}!" +` + } + ] + }, + { + message: "unused 'hello-dio' key", + suggestions: [ + { + desc: "Remove the 'hello-dio' key.", + output: `hello: "ハローワールド" +messages: + hello: "こんにちは、DIO!" + link: "@:message.hello" + nested: + hello: "こんにちは、ジョジョ!" +hello_dio: "こんにちは、アンダースコア DIO!" +"hello {name}": "こんにちは、{name}!" +` + }, + { + desc: 'Remove all unused keys.', + output: `hello: "ハローワールド" +messages: + hello: "こんにちは、DIO!" + nested: {} +hello_dio: "こんにちは、アンダースコア DIO!" +"hello {name}": "こんにちは、{name}!" ` } ]