From 677b0461981ee7a93d80236d0fcd764443475294 Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Mon, 9 Dec 2024 15:09:57 +0900 Subject: [PATCH] fix: support v10 --- lib/rules/no-deprecated-modulo-syntax.ts | 4 +- lib/rules/prefer-linked-key-with-paren.ts | 12 +- lib/rules/valid-message-syntax.ts | 12 +- lib/utils/collect-linked-keys.ts | 6 +- lib/utils/message-compiler/parser-v8.ts | 3 +- lib/utils/message-compiler/parser-v9.ts | 59 +++++++ lib/utils/message-compiler/traverser.ts | 4 +- lib/utils/message-compiler/utils.ts | 5 +- package.json | 2 +- pnpm-lock.yaml | 2 +- .../lib/rules/no-deprecated-modulo-syntax.ts | 2 +- tests/lib/rules/valid-message-syntax.ts | 161 +++++++++--------- tests/lib/utils/message-compiler/parser-v8.ts | 2 +- tests/lib/utils/message-compiler/utils.ts | 8 +- 14 files changed, 180 insertions(+), 102 deletions(-) create mode 100644 lib/utils/message-compiler/parser-v9.ts diff --git a/lib/rules/no-deprecated-modulo-syntax.ts b/lib/rules/no-deprecated-modulo-syntax.ts index 59e237ff..3e5e56cc 100644 --- a/lib/rules/no-deprecated-modulo-syntax.ts +++ b/lib/rules/no-deprecated-modulo-syntax.ts @@ -13,7 +13,7 @@ import { getMessageSyntaxVersions, NodeTypes } from '../utils/message-compiler/utils' -import { parse } from '../utils/message-compiler/parser' +import { parse } from '../utils/message-compiler/parser-v9' import { traverseNode } from '../utils/message-compiler/traverser' import { createRule, @@ -75,8 +75,6 @@ function create(context: RuleContext): RuleListener { } if (messageSyntaxVersions.v9) { verifyForV9(message, reportNode, getReportOffset) - } else if (messageSyntaxVersions.v8) { - return } } diff --git a/lib/rules/prefer-linked-key-with-paren.ts b/lib/rules/prefer-linked-key-with-paren.ts index b22685f2..4ca8d7f9 100644 --- a/lib/rules/prefer-linked-key-with-paren.ts +++ b/lib/rules/prefer-linked-key-with-paren.ts @@ -42,7 +42,7 @@ function create(context: RuleContext): RuleListener { const sourceCode = getSourceCode(context) const messageSyntaxVersions = getMessageSyntaxVersions(context) - function verifyForV9( + function verifyForNewSyntax( message: string, reportNode: JSONAST.JSONStringLiteral | YAMLAST.YAMLScalar, getReportOffset: GetReportOffset @@ -134,14 +134,16 @@ function create(context: RuleContext): RuleListener { if (messageSyntaxVersions.reportIfMissingSetting()) { return } - if (messageSyntaxVersions.v9 && messageSyntaxVersions.v8) { + const newSyntax = messageSyntaxVersions.v9 || messageSyntaxVersions.v10 + const v8Syntax = messageSyntaxVersions.v8 + if (newSyntax && v8Syntax) { // This rule cannot support two versions in the same project. return } - if (messageSyntaxVersions.v9) { - verifyForV9(message, reportNode, getReportOffset) - } else if (messageSyntaxVersions.v8) { + if (newSyntax) { + verifyForNewSyntax(message, reportNode, getReportOffset) + } else if (v8Syntax) { verifyForV8(message, reportNode, getReportOffset) } } diff --git a/lib/rules/valid-message-syntax.ts b/lib/rules/valid-message-syntax.ts index f64a5ef9..e7b5f4a9 100644 --- a/lib/rules/valid-message-syntax.ts +++ b/lib/rules/valid-message-syntax.ts @@ -14,6 +14,7 @@ import { getReportIndex } from '../utils/message-compiler/utils' import { parse } from '../utils/message-compiler/parser' +import { parse as parseForV9 } from '../utils/message-compiler/parser-v9' import { parse as parseForV8 } from '../utils/message-compiler/parser-v8' import type { CompileError } from '@intlify/message-compiler' import { createRule } from '../utils/rule' @@ -27,9 +28,16 @@ function create(context: RuleContext): RuleListener { const messageSyntaxVersions = getMessageSyntaxVersions(context) function* extractMessageErrors(message: string) { - if (messageSyntaxVersions.v9) { - yield* parse(message).errors + // v10 and v9 generate nearly identical errors so only one of them will be returned. + const errorsForV10OrV9: CompileError[] = [] + if (messageSyntaxVersions.v10) { + errorsForV10OrV9.push(...parse(message).errors) } + if (messageSyntaxVersions.v9 && !errorsForV10OrV9.length) { + errorsForV10OrV9.push(...parseForV9(message).errors) + } + yield* errorsForV10OrV9 + if (messageSyntaxVersions.v8) { yield* parseForV8(message).errors } diff --git a/lib/utils/collect-linked-keys.ts b/lib/utils/collect-linked-keys.ts index 9221dee2..82fb75ab 100644 --- a/lib/utils/collect-linked-keys.ts +++ b/lib/utils/collect-linked-keys.ts @@ -6,6 +6,7 @@ import type { ResourceNode } from '@intlify/message-compiler' import { traverseNode } from './message-compiler/traverser' import type { I18nLocaleMessageDictionary, RuleContext } from '../types' import { parse } from './message-compiler/parser' +import { parse as parseForV9 } from './message-compiler/parser-v9' import { parse as parseForV8 } from './message-compiler/parser-v8' import type { MessageSyntaxVersions } from './message-compiler/utils' import { NodeTypes } from './message-compiler/utils' @@ -27,9 +28,12 @@ function* extractUsedKeysFromLinks( if (typeof value === 'object') { yield* extractUsedKeysFromLinks(value, messageSyntaxVersions) } else if (typeof value === 'string') { - if (messageSyntaxVersions.v9) { + if (messageSyntaxVersions.v10) { yield* extractUsedKeysFromAST(parse(value).ast) } + if (messageSyntaxVersions.v9) { + yield* extractUsedKeysFromAST(parseForV9(value).ast) + } if (messageSyntaxVersions.v8) { yield* extractUsedKeysFromAST(parseForV8(value).ast) } diff --git a/lib/utils/message-compiler/parser-v8.ts b/lib/utils/message-compiler/parser-v8.ts index 100cc62e..bce8627c 100644 --- a/lib/utils/message-compiler/parser-v8.ts +++ b/lib/utils/message-compiler/parser-v8.ts @@ -17,6 +17,7 @@ import type { } from '@intlify/message-compiler' import { sortedLastIndex } from 'lodash' import { NodeTypes } from './utils' +import type { ModuloNamedNode } from './parser-v9' export function parse(code: string): { ast: ResourceNode @@ -207,7 +208,7 @@ function parseAST(code: string, errors: CompileError[]): ResourceNode { node = listNode } if (!node) { - const namedNode: NamedNode = { + const namedNode: ModuloNamedNode = { type: NodeTypes.Named, key: trimmedKeyValue, ...ctx.getNodeLoc(endOffset - 1, placeholderEndOffset) diff --git a/lib/utils/message-compiler/parser-v9.ts b/lib/utils/message-compiler/parser-v9.ts new file mode 100644 index 00000000..68f1ea6a --- /dev/null +++ b/lib/utils/message-compiler/parser-v9.ts @@ -0,0 +1,59 @@ +/** + * A simplified version of the message parser that handles messages like vue-i18n v8. + * This parser probably has poor performance. + */ +import type { + CompileError, + NamedNode, + ResourceNode +} from '@intlify/message-compiler' +import { NodeTypes } from './utils' +import { parse as baseParse } from './parser' +import type { MessageElementNode } from './traverser' +import { traverseNode } from './traverser' + +// The deprecated Rails i18n format. +export type ModuloNamedNode = NamedNode & { modulo?: boolean } + +export function parse(code: string): { + ast: ResourceNode + errors: CompileError[] +} { + const { ast, errors } = baseParse(code) + + traverseNode(ast, node => { + if (node.type === NodeTypes.Message) { + transformModuloNamedNode(node.items) + } + }) + return { + ast, + errors + } + + function transformModuloNamedNode(nodes: MessageElementNode[]) { + // Converts nodes with a '%' before the brackets into modulo nodes. + for (let index = nodes.length - 1; index >= 1; index--) { + const node = nodes[index] + if ( + node.type !== NodeTypes.Named || + code[node.loc!.start.offset - 1] !== '%' + ) + continue + + const prev = nodes[index - 1] + if (prev.type !== NodeTypes.Text || !prev.value?.endsWith('%')) continue + + node.modulo = true + + prev.loc!.end.offset -= 1 + prev.loc!.end.column -= 1 + prev.end! -= 1 + prev.value = prev.value!.slice(0, -1) + if (prev.start === prev.end) { + nodes.splice(index - 1, 1) + index-- + } + } + } +} diff --git a/lib/utils/message-compiler/traverser.ts b/lib/utils/message-compiler/traverser.ts index 84ee1ab9..360d5583 100644 --- a/lib/utils/message-compiler/traverser.ts +++ b/lib/utils/message-compiler/traverser.ts @@ -11,13 +11,15 @@ import type { TextNode } from '@intlify/message-compiler' import { NodeTypes } from './utils' +import type { ModuloNamedNode } from './parser-v9' -type MessageElementNode = +export type MessageElementNode = | TextNode | NamedNode | ListNode | LiteralNode | LinkedNode + | ModuloNamedNode type MessageASTNode = | ResourceNode | PluralNode diff --git a/lib/utils/message-compiler/utils.ts b/lib/utils/message-compiler/utils.ts index e2bdfa17..24a7e564 100644 --- a/lib/utils/message-compiler/utils.ts +++ b/lib/utils/message-compiler/utils.ts @@ -19,6 +19,7 @@ export const NodeTypes = { export type MessageSyntaxVersions = { v8: boolean v9: boolean + v10: boolean isNotSet: boolean reportIfMissingSetting: () => boolean } @@ -37,6 +38,7 @@ export function getMessageSyntaxVersions( return { v8: true, v9: true, + v10: true, isNotSet: true, reportIfMissingSetting: () => { if (!puttedSettingsError.has(context)) { @@ -54,7 +56,8 @@ export function getMessageSyntaxVersions( const range = new Range(messageSyntaxVersion) return { v8: intersects(range, '^8.0.0 || <=8.0.0'), - v9: intersects(range, '>=9.0.0-0'), + v9: intersects(range, '^9.0.0-0'), + v10: intersects(range, '>=10.0.0-0'), isNotSet: false, reportIfMissingSetting: () => false } diff --git a/package.json b/package.json index 008d9ea1..7bce3244 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "dependencies": { "@eslint/eslintrc": "^3.0.0", "@intlify/core-base": "^10.0.0", - "@intlify/message-compiler": "^10.0.0", + "@intlify/message-compiler": "^10.0.5", "debug": "^4.3.4", "eslint-compat-utils": "^0.6.0", "glob": "^10.3.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf58e6a9..d751c78c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,7 +15,7 @@ importers: specifier: ^10.0.0 version: 10.0.5 '@intlify/message-compiler': - specifier: ^10.0.0 + specifier: ^10.0.5 version: 10.0.5 debug: specifier: ^4.3.4 diff --git a/tests/lib/rules/no-deprecated-modulo-syntax.ts b/tests/lib/rules/no-deprecated-modulo-syntax.ts index 448d0b90..bc4fae5e 100644 --- a/tests/lib/rules/no-deprecated-modulo-syntax.ts +++ b/tests/lib/rules/no-deprecated-modulo-syntax.ts @@ -18,7 +18,7 @@ const tester = new RuleTester({ languageOptions: { parser: vueParser, ecmaVersion: 2015 } }) -tester.run('no-deprecated-module-syntax', rule as never, { +tester.run('no-deprecated-modulo-syntax', rule as never, { valid: [ // text only { diff --git a/tests/lib/rules/valid-message-syntax.ts b/tests/lib/rules/valid-message-syntax.ts index 40c7da59..4b1195ff 100644 --- a/tests/lib/rules/valid-message-syntax.ts +++ b/tests/lib/rules/valid-message-syntax.ts @@ -223,24 +223,25 @@ tester.run('valid-message-syntax', rule as never, { } ] }, - { - code: ` - key: message @:(v8) - `, - ...options.yaml.default, - errors: [ - { - message: `If you want to use '${TEST_RULE_ID_PREFIX}valid-message-syntax' rule, you need to set 'messageSyntaxVersion' at 'settings'. See the 'eslint-plugin-vue-i18n' documentation`, - line: 1, - column: 1 - }, - { - message: 'Unexpected empty linked key', - line: 2, - column: 21 - } - ] - }, + // { + // // The syntax is now allowed. + // code: ` + // key: message @:(v8) + // `, + // ...options.yaml.default, + // errors: [ + // { + // message: `If you want to use '${TEST_RULE_ID_PREFIX}valid-message-syntax' rule, you need to set 'messageSyntaxVersion' at 'settings'. See the 'eslint-plugin-vue-i18n' documentation`, + // line: 1, + // column: 1 + // }, + // { + // message: 'Unexpected empty linked key', + // line: 2, + // column: 21 + // } + // ] + // }, { code: ` key: message { v9 } @@ -272,61 +273,65 @@ tester.run('valid-message-syntax', rule as never, { } ] }, - { - code: ` - key: message @:(v8) - `, - ...options.yaml.v9, - errors: [ - { - message: 'Unexpected empty linked key', - line: 2, - column: 21 - } - ] - }, - { - code: ` - key: message new line - @:(v8) - `, - ...options.yaml.v9, - errors: [ - { - message: 'Unexpected empty linked key', - line: 3, - column: 10 - } - ] - }, - { - code: ` - key: "message new line - @:(v8)" - `, - ...options.yaml.v9, - errors: [ - { - message: 'Unexpected empty linked key', - line: 3, - column: 10 - } - ] - }, - { - code: ` - key: 'message new line - @:(v8)' - `, - ...options.yaml.v9, - errors: [ - { - message: 'Unexpected empty linked key', - line: 3, - column: 10 - } - ] - }, + // { + // // The syntax is now allowed. + // code: ` + // key: message @:(v8) + // `, + // ...options.yaml.v9, + // errors: [ + // { + // message: 'Unexpected empty linked key', + // line: 2, + // column: 21 + // } + // ] + // }, + // { + // // The syntax is now allowed. + // code: ` + // key: message new line + // @:(v8) + // `, + // ...options.yaml.v9, + // errors: [ + // { + // message: 'Unexpected empty linked key', + // line: 3, + // column: 10 + // } + // ] + // }, + // { + // // The syntax is now allowed. + // code: ` + // key: "message new line + // @:(v8)" + // `, + // ...options.yaml.v9, + // errors: [ + // { + // message: 'Unexpected empty linked key', + // line: 3, + // column: 10 + // } + // ] + // }, + // { + // // The syntax is now allowed. + // code: ` + // key: 'message new line + // @:(v8)' + // `, + // ...options.yaml.v9, + // errors: [ + // { + // message: 'Unexpected empty linked key', + // line: 3, + // column: 10 + // } + // ] + // }, { code: ` @@ -476,16 +481,6 @@ tester.run('valid-message-syntax', rule as never, { line: 3, column: 22 }, - { - message: 'Unterminated closing brace', - line: 4, - column: 27 - }, - { - message: 'Unbalanced closing brace', - line: 4, - column: 34 - }, { message: "Unexpected 'null' message", line: 5, diff --git a/tests/lib/utils/message-compiler/parser-v8.ts b/tests/lib/utils/message-compiler/parser-v8.ts index f1170220..5505029e 100644 --- a/tests/lib/utils/message-compiler/parser-v8.ts +++ b/tests/lib/utils/message-compiler/parser-v8.ts @@ -3,7 +3,7 @@ */ import { deepStrictEqual } from 'assert' import { parse } from '../../../../lib/utils/message-compiler/parser-v8' -import { parse as parseForV9 } from '../../../../lib/utils/message-compiler/parser' +import { parse as parseForV9 } from '../../../../lib/utils/message-compiler/parser-v9' import { errorsFixtures } from './parser-v8-data' describe('parser-v8', () => { diff --git a/tests/lib/utils/message-compiler/utils.ts b/tests/lib/utils/message-compiler/utils.ts index 44cefc06..071a4499 100644 --- a/tests/lib/utils/message-compiler/utils.ts +++ b/tests/lib/utils/message-compiler/utils.ts @@ -21,31 +21,37 @@ describe('message-compiler utils', () => { deepStrictEqual(get('^8.0.0'), { v8: true, v9: false, + v10: false, isNotSet: false }) deepStrictEqual(get('^9.0.0'), { v8: false, v9: true, + v10: false, isNotSet: false }) deepStrictEqual(get('^7.0.0'), { v8: true, v9: false, + v10: false, isNotSet: false }) deepStrictEqual(get('^10.0.0'), { v8: false, - v9: true, + v9: false, + v10: true, isNotSet: false }) deepStrictEqual(get('>=5.0.0'), { v8: true, v9: true, + v10: true, isNotSet: false }) deepStrictEqual(get('^9.0.0-beta.8'), { v8: false, v9: true, + v10: false, isNotSet: false }) })