Skip to content

Commit c360df5

Browse files
authored
Upgrade vue-eslint-parser and use new API (#139)
* Upgrade vue-eslint-parser and use new API * Fix typing
1 parent f566960 commit c360df5

File tree

12 files changed

+688
-1249
lines changed

12 files changed

+688
-1249
lines changed

lib/rules/key-format-style.ts

Lines changed: 125 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,67 @@
11
/**
22
* @author Yosuke Ota
33
*/
4-
import type { AST as VAST } from 'vue-eslint-parser'
54
import type { AST as JSONAST } from 'jsonc-eslint-parser'
65
import type { AST as YAMLAST } from 'yaml-eslint-parser'
76
import { extname } from 'path'
8-
import { getLocaleMessages } from '../utils/index'
7+
import { defineCustomBlocksVisitor, getLocaleMessages } from '../utils/index'
98
import debugBuilder from 'debug'
109
import type { RuleContext, RuleListener } from '../types'
1110
import { getCasingChecker } from '../utils/casing'
12-
import { LocaleMessage } from '../utils/locale-messages'
11+
import type { LocaleMessage } from '../utils/locale-messages'
1312
const debug = debugBuilder('eslint-plugin-vue-i18n:key-format-style')
1413

1514
const allowedCaseOptions = ['camelCase', 'kebab-case', 'snake_case'] as const
1615
type CaseOption = typeof allowedCaseOptions[number]
17-
const unknownKey = Symbol('unknown key')
18-
type UnknownKey = typeof unknownKey
1916

2017
function create(context: RuleContext): RuleListener {
2118
const filename = context.getFilename()
2219
const expectCasing: CaseOption = context.options[0] ?? 'camelCase'
2320
const checker = getCasingChecker(expectCasing)
2421
const allowArray: boolean = context.options[1]?.allowArray
2522

23+
function reportUnknown(reportNode: YAMLAST.YAMLNode) {
24+
context.report({
25+
message: `Unexpected object key. Use ${expectCasing} string key instead`,
26+
loc: reportNode.loc
27+
})
28+
}
29+
function verifyKey(
30+
key: string | number,
31+
reportNode: JSONAST.JSONNode | YAMLAST.YAMLNode
32+
) {
33+
if (typeof key === 'number') {
34+
if (!allowArray) {
35+
context.report({
36+
message: `Unexpected array element`,
37+
loc: reportNode.loc
38+
})
39+
}
40+
} else {
41+
if (!checker(key)) {
42+
context.report({
43+
message: `"${key}" is not ${expectCasing}`,
44+
loc: reportNode.loc
45+
})
46+
}
47+
}
48+
}
2649
/**
27-
* Create node visitor
50+
* Create node visitor for JSON
2851
*/
29-
function createVisitor<N extends JSONAST.JSONNode | YAMLAST.YAMLNode>(
30-
targetLocaleMessage: LocaleMessage,
31-
{
32-
skipNode,
33-
resolveKey,
34-
resolveReportNode
35-
}: {
36-
skipNode: (node: N) => boolean
37-
resolveKey: (node: N) => string | number | UnknownKey | null
38-
resolveReportNode: (node: N) => N
39-
}
40-
) {
52+
function createVisitorForJson(
53+
targetLocaleMessage: LocaleMessage
54+
): RuleListener {
4155
type KeyStack = {
4256
inLocale: boolean
43-
node?: N
57+
node?: JSONAST.JSONNode
4458
upper?: KeyStack
4559
}
4660
let keyStack: KeyStack = {
4761
inLocale: targetLocaleMessage.localeKey === 'file'
4862
}
4963
return {
50-
enterNode(node: N) {
51-
if (skipNode(node)) {
52-
return
53-
}
54-
const key = resolveKey(node)
55-
if (key == null) {
56-
return
57-
}
64+
JSONProperty(node: JSONAST.JSONProperty) {
5865
const { inLocale } = keyStack
5966
keyStack = {
6067
node,
@@ -64,182 +71,122 @@ function create(context: RuleContext): RuleListener {
6471
if (!inLocale) {
6572
return
6673
}
67-
if (key === unknownKey) {
68-
context.report({
69-
message: `Unexpected object key. Use ${expectCasing} string key instead`,
70-
loc: resolveReportNode(node).loc
71-
})
72-
} else if (typeof key === 'number') {
73-
if (!allowArray) {
74-
context.report({
75-
message: `Unexpected array element`,
76-
loc: resolveReportNode(node).loc
77-
})
78-
}
79-
} else {
80-
if (!checker(key)) {
81-
context.report({
82-
message: `"${key}" is not ${expectCasing}`,
83-
loc: resolveReportNode(node).loc
84-
})
85-
}
86-
}
74+
75+
const key =
76+
node.key.type === 'JSONLiteral' ? `${node.key.value}` : node.key.name
77+
78+
verifyKey(key, node.key)
8779
},
88-
leaveNode(node: N) {
89-
if (keyStack.node === node) {
90-
keyStack = keyStack.upper!
91-
}
92-
}
93-
}
94-
}
95-
/**
96-
* Create node visitor for JSON
97-
*/
98-
function createVisitorForJson(targetLocaleMessage: LocaleMessage) {
99-
return createVisitor<JSONAST.JSONNode>(targetLocaleMessage, {
100-
skipNode(node) {
101-
if (
102-
node.type === 'Program' ||
103-
node.type === 'JSONExpressionStatement' ||
104-
node.type === 'JSONProperty'
105-
) {
106-
return true
107-
}
108-
const parent = node.parent!
109-
if (parent.type === 'JSONProperty' && parent.key === node) {
110-
return true
111-
}
112-
return false
80+
'JSONProperty:exit'() {
81+
keyStack = keyStack.upper!
11382
},
114-
resolveKey(node) {
115-
const parent = node.parent!
116-
if (parent.type === 'JSONProperty') {
117-
return parent.key.type === 'JSONLiteral'
118-
? `${parent.key.value}`
119-
: parent.key.name
120-
} else if (parent.type === 'JSONArrayExpression') {
121-
return parent.elements.indexOf(node as never)
83+
'JSONArrayExpression > *'(
84+
node: JSONAST.JSONArrayExpression['elements'][number] & {
85+
parent: JSONAST.JSONArrayExpression
12286
}
123-
return null
124-
},
125-
resolveReportNode(node) {
126-
const parent = node.parent!
127-
return parent.type === 'JSONProperty' ? parent.key : node
87+
) {
88+
const key = node.parent.elements.indexOf(node)
89+
verifyKey(key, node)
12890
}
129-
})
91+
}
13092
}
13193

13294
/**
13395
* Create node visitor for YAML
13496
*/
135-
function createVisitorForYaml(targetLocaleMessage: LocaleMessage) {
136-
const yamlKeyNodes = new Set()
137-
return createVisitor<YAMLAST.YAMLNode>(targetLocaleMessage, {
138-
skipNode(node) {
97+
function createVisitorForYaml(
98+
targetLocaleMessage: LocaleMessage
99+
): RuleListener {
100+
const yamlKeyNodes = new Set<YAMLAST.YAMLContent | YAMLAST.YAMLWithMeta>()
101+
102+
type KeyStack = {
103+
inLocale: boolean
104+
node?: YAMLAST.YAMLNode
105+
upper?: KeyStack
106+
}
107+
let keyStack: KeyStack = {
108+
inLocale: targetLocaleMessage.localeKey === 'file'
109+
}
110+
function withinKey(node: YAMLAST.YAMLNode) {
111+
for (const keyNode of yamlKeyNodes) {
139112
if (
140-
node.type === 'Program' ||
141-
node.type === 'YAMLDocument' ||
142-
node.type === 'YAMLDirective' ||
143-
node.type === 'YAMLAnchor' ||
144-
node.type === 'YAMLTag'
113+
keyNode.range[0] <= node.range[0] &&
114+
node.range[0] < keyNode.range[1]
145115
) {
146116
return true
147117
}
148-
149-
if (yamlKeyNodes.has(node)) {
150-
// within key node
151-
return true
152-
}
153-
const parent = node.parent
154-
if (yamlKeyNodes.has(parent)) {
155-
// within key node
156-
yamlKeyNodes.add(node)
157-
return true
118+
}
119+
return false
120+
}
121+
return {
122+
YAMLPair(node: YAMLAST.YAMLPair) {
123+
const { inLocale } = keyStack
124+
keyStack = {
125+
node,
126+
inLocale: true,
127+
upper: keyStack
158128
}
159-
if (node.type === 'YAMLPair') {
160-
yamlKeyNodes.add(node.key)
161-
return true
129+
if (!inLocale) {
130+
return
162131
}
163-
return false
164-
},
165-
resolveKey(node) {
166-
const parent = node.parent!
167-
if (parent.type === 'YAMLPair') {
168-
if (parent.key == null) {
169-
return unknownKey
170-
}
171-
if (parent.key.type === 'YAMLScalar') {
172-
const key = parent.key.value
173-
return typeof key === 'string' ? key : String(key)
132+
if (node.key != null) {
133+
if (withinKey(node)) {
134+
return
174135
}
175-
return unknownKey
176-
} else if (parent.type === 'YAMLSequence') {
177-
return parent.entries.indexOf(node as never)
136+
yamlKeyNodes.add(node.key)
178137
}
179138

180-
return null
139+
if (node.key == null) {
140+
reportUnknown(node)
141+
} else if (node.key.type === 'YAMLScalar') {
142+
const keyValue = node.key.value
143+
const key = typeof keyValue === 'string' ? keyValue : String(keyValue)
144+
verifyKey(key, node.key)
145+
} else {
146+
reportUnknown(node)
147+
}
148+
},
149+
'YAMLPair:exit'() {
150+
keyStack = keyStack.upper!
181151
},
182-
resolveReportNode(node) {
183-
const parent = node.parent!
184-
return parent.type === 'YAMLPair' ? parent.key || parent : node
152+
'YAMLSequence > *'(
153+
node: YAMLAST.YAMLSequence['entries'][number] & {
154+
parent: YAMLAST.YAMLSequence
155+
}
156+
) {
157+
if (withinKey(node)) {
158+
return
159+
}
160+
const key = node.parent.entries.indexOf(node)
161+
verifyKey(key, node)
185162
}
186-
})
163+
}
187164
}
188165

189166
if (extname(filename) === '.vue') {
190-
return {
191-
Program() {
192-
const documentFragment =
193-
context.parserServices.getDocumentFragment &&
194-
context.parserServices.getDocumentFragment()
195-
/** @type {VElement[]} */
196-
const i18nBlocks =
197-
(documentFragment &&
198-
documentFragment.children.filter(
199-
(node): node is VAST.VElement =>
200-
node.type === 'VElement' && node.name === 'i18n'
201-
)) ||
202-
[]
203-
if (!i18nBlocks.length) {
204-
return
167+
return defineCustomBlocksVisitor(
168+
context,
169+
ctx => {
170+
const localeMessages = getLocaleMessages(context)
171+
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(
172+
ctx.parserServices.customBlock
173+
)
174+
if (!targetLocaleMessage) {
175+
return {}
205176
}
177+
return createVisitorForJson(targetLocaleMessage)
178+
},
179+
ctx => {
206180
const localeMessages = getLocaleMessages(context)
207-
208-
for (const block of i18nBlocks) {
209-
if (
210-
block.startTag.attributes.some(
211-
attr => !attr.directive && attr.key.name === 'src'
212-
)
213-
) {
214-
continue
215-
}
216-
217-
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(
218-
block
219-
)
220-
if (!targetLocaleMessage) {
221-
continue
222-
}
223-
const parserLang = targetLocaleMessage.getParserLang()
224-
225-
let visitor
226-
if (parserLang === 'json') {
227-
visitor = createVisitorForJson(targetLocaleMessage)
228-
} else if (parserLang === 'yaml') {
229-
visitor = createVisitorForYaml(targetLocaleMessage)
230-
}
231-
232-
if (visitor == null) {
233-
return
234-
}
235-
236-
targetLocaleMessage.traverseNodes({
237-
enterNode: visitor.enterNode,
238-
leaveNode: visitor.leaveNode
239-
})
181+
const targetLocaleMessage = localeMessages.findBlockLocaleMessage(
182+
ctx.parserServices.customBlock
183+
)
184+
if (!targetLocaleMessage) {
185+
return {}
240186
}
187+
return createVisitorForYaml(targetLocaleMessage)
241188
}
242-
}
189+
)
243190
} else if (context.parserServices.isJSON || context.parserServices.isYAML) {
244191
const localeMessages = getLocaleMessages(context)
245192
const targetLocaleMessage = localeMessages.findExistLocaleMessage(filename)
@@ -249,19 +196,9 @@ function create(context: RuleContext): RuleListener {
249196
}
250197

251198
if (context.parserServices.isJSON) {
252-
const { enterNode, leaveNode } = createVisitorForJson(targetLocaleMessage)
253-
254-
return {
255-
'[type=/^JSON/]': enterNode,
256-
'[type=/^JSON/]:exit': leaveNode
257-
}
199+
return createVisitorForJson(targetLocaleMessage)
258200
} else if (context.parserServices.isYAML) {
259-
const { enterNode, leaveNode } = createVisitorForYaml(targetLocaleMessage)
260-
261-
return {
262-
'[type=/^YAML/]': enterNode,
263-
'[type=/^YAML/]:exit': leaveNode
264-
}
201+
return createVisitorForYaml(targetLocaleMessage)
265202
}
266203
return {}
267204
} else {

0 commit comments

Comments
 (0)