@sailshq/language-server 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SailsParser.js +125 -33
- package/completions/helper-inputs-completion.js +8 -3
- package/completions/model-attribute-props-completion.js +20 -19
- package/completions/model-attributes-completion.js +356 -0
- package/completions/model-methods-completion.js +24 -1
- package/go-to-definitions/go-to-action.js +82 -29
- package/go-to-definitions/go-to-helper-input.js +112 -0
- package/go-to-definitions/go-to-model-attribute.js +302 -0
- package/go-to-definitions/go-to-model.js +4 -3
- package/go-to-definitions/go-to-page.js +40 -26
- package/go-to-definitions/go-to-view.js +42 -24
- package/index.js +12 -4
- package/package.json +1 -1
- package/validators/validate-action-exist.js +54 -15
- package/validators/validate-data-type.js +88 -25
- package/validators/validate-helper-input-exist.js +98 -32
- package/validators/validate-model-attribute-exist.js +184 -52
- package/validators/validate-model-exist.js +14 -0
- package/validators/validate-required-helper-input.js +100 -39
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const acorn = require('acorn')
|
|
3
|
+
const walk = require('acorn-walk')
|
|
4
|
+
|
|
5
|
+
module.exports = async function goToModelAttribute(
|
|
6
|
+
document,
|
|
7
|
+
position,
|
|
8
|
+
typeMap
|
|
9
|
+
) {
|
|
10
|
+
const text = document.getText()
|
|
11
|
+
const offset = document.offsetAt(position)
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const ast = acorn.parse(text, {
|
|
15
|
+
ecmaVersion: 'latest',
|
|
16
|
+
sourceType: 'module'
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
let result = null
|
|
20
|
+
|
|
21
|
+
const checkPropertyForAttribute = (prop, model, document) => {
|
|
22
|
+
if (prop.type !== 'Property' || !prop.key) return null
|
|
23
|
+
|
|
24
|
+
const attrName =
|
|
25
|
+
prop.key.type === 'Identifier'
|
|
26
|
+
? prop.key.name
|
|
27
|
+
: prop.key.type === 'Literal'
|
|
28
|
+
? prop.key.value
|
|
29
|
+
: null
|
|
30
|
+
|
|
31
|
+
if (!attrName) return null
|
|
32
|
+
|
|
33
|
+
const keyStart = prop.key.start
|
|
34
|
+
const keyEnd = prop.key.end
|
|
35
|
+
|
|
36
|
+
if (offset >= keyStart && offset <= keyEnd) {
|
|
37
|
+
const attrInfo = model.attributes?.[attrName]
|
|
38
|
+
if (attrInfo?.line && attrInfo?.path) {
|
|
39
|
+
const uri = `file://${attrInfo.path}`
|
|
40
|
+
return lsp.LocationLink.create(
|
|
41
|
+
uri,
|
|
42
|
+
lsp.Range.create(attrInfo.line - 1, 0, attrInfo.line - 1, 0),
|
|
43
|
+
lsp.Range.create(attrInfo.line - 1, 0, attrInfo.line - 1, 0),
|
|
44
|
+
lsp.Range.create(
|
|
45
|
+
document.positionAt(keyStart),
|
|
46
|
+
document.positionAt(keyEnd)
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const checkStringLiteralForAttribute = (literal, model, document) => {
|
|
55
|
+
if (literal.type !== 'Literal' || typeof literal.value !== 'string')
|
|
56
|
+
return null
|
|
57
|
+
|
|
58
|
+
const attrName = literal.value
|
|
59
|
+
const literalStart = literal.start
|
|
60
|
+
const literalEnd = literal.end
|
|
61
|
+
|
|
62
|
+
if (offset >= literalStart && offset <= literalEnd) {
|
|
63
|
+
const attrInfo = model.attributes?.[attrName]
|
|
64
|
+
if (attrInfo?.line && attrInfo?.path) {
|
|
65
|
+
const uri = `file://${attrInfo.path}`
|
|
66
|
+
return lsp.LocationLink.create(
|
|
67
|
+
uri,
|
|
68
|
+
lsp.Range.create(attrInfo.line - 1, 0, attrInfo.line - 1, 0),
|
|
69
|
+
lsp.Range.create(attrInfo.line - 1, 0, attrInfo.line - 1, 0),
|
|
70
|
+
lsp.Range.create(
|
|
71
|
+
document.positionAt(literalStart),
|
|
72
|
+
document.positionAt(literalEnd)
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const checkArrayForAttributes = (arrayNode, model, document) => {
|
|
81
|
+
if (!arrayNode || arrayNode.type !== 'ArrayExpression') return null
|
|
82
|
+
|
|
83
|
+
for (const element of arrayNode.elements) {
|
|
84
|
+
if (element?.type === 'Literal' && typeof element.value === 'string') {
|
|
85
|
+
const res = checkStringLiteralForAttribute(element, model, document)
|
|
86
|
+
if (res) return res
|
|
87
|
+
} else if (element?.type === 'ObjectExpression') {
|
|
88
|
+
const res = traverseObjectExpression(element, model, document)
|
|
89
|
+
if (res) return res
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const traverseObjectExpression = (objNode, model, document) => {
|
|
96
|
+
if (!objNode || objNode.type !== 'ObjectExpression') return null
|
|
97
|
+
|
|
98
|
+
for (const prop of objNode.properties) {
|
|
99
|
+
if (prop.type !== 'Property') continue
|
|
100
|
+
|
|
101
|
+
const propName =
|
|
102
|
+
prop.key.type === 'Identifier'
|
|
103
|
+
? prop.key.name
|
|
104
|
+
: prop.key.type === 'Literal'
|
|
105
|
+
? prop.key.value
|
|
106
|
+
: null
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
propName === 'where' ||
|
|
110
|
+
propName === 'or' ||
|
|
111
|
+
propName === 'and' ||
|
|
112
|
+
propName === 'not'
|
|
113
|
+
) {
|
|
114
|
+
if (prop.value.type === 'ObjectExpression') {
|
|
115
|
+
const res = traverseObjectExpression(prop.value, model, document)
|
|
116
|
+
if (res) return res
|
|
117
|
+
} else if (prop.value.type === 'ArrayExpression') {
|
|
118
|
+
for (const element of prop.value.elements) {
|
|
119
|
+
const res = traverseObjectExpression(element, model, document)
|
|
120
|
+
if (res) return res
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else if (propName === 'select' || propName === 'omit') {
|
|
124
|
+
if (prop.value.type === 'ArrayExpression') {
|
|
125
|
+
const res = checkArrayForAttributes(prop.value, model, document)
|
|
126
|
+
if (res) return res
|
|
127
|
+
}
|
|
128
|
+
} else if (propName === 'sort') {
|
|
129
|
+
if (prop.value.type === 'ArrayExpression') {
|
|
130
|
+
const res = checkArrayForAttributes(prop.value, model, document)
|
|
131
|
+
if (res) return res
|
|
132
|
+
} else if (
|
|
133
|
+
prop.value.type === 'Literal' &&
|
|
134
|
+
typeof prop.value.value === 'string'
|
|
135
|
+
) {
|
|
136
|
+
const sortString = prop.value.value
|
|
137
|
+
const attrMatch = sortString.match(/^(\w+)/)
|
|
138
|
+
if (attrMatch) {
|
|
139
|
+
const attrName = attrMatch[1]
|
|
140
|
+
const attrInfo = model.attributes?.[attrName]
|
|
141
|
+
if (attrInfo?.line && attrInfo?.path) {
|
|
142
|
+
const sortStart = prop.value.start
|
|
143
|
+
const sortEnd = prop.value.end
|
|
144
|
+
if (offset >= sortStart && offset <= sortEnd) {
|
|
145
|
+
const uri = `file://${attrInfo.path}`
|
|
146
|
+
return lsp.LocationLink.create(
|
|
147
|
+
uri,
|
|
148
|
+
lsp.Range.create(
|
|
149
|
+
attrInfo.line - 1,
|
|
150
|
+
0,
|
|
151
|
+
attrInfo.line - 1,
|
|
152
|
+
0
|
|
153
|
+
),
|
|
154
|
+
lsp.Range.create(
|
|
155
|
+
attrInfo.line - 1,
|
|
156
|
+
0,
|
|
157
|
+
attrInfo.line - 1,
|
|
158
|
+
0
|
|
159
|
+
),
|
|
160
|
+
lsp.Range.create(
|
|
161
|
+
document.positionAt(sortStart),
|
|
162
|
+
document.positionAt(sortEnd)
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
const res = checkPropertyForAttribute(prop, model, document)
|
|
171
|
+
if (res) return res
|
|
172
|
+
|
|
173
|
+
if (prop.value.type === 'ObjectExpression') {
|
|
174
|
+
const nestedRes = traverseObjectExpression(
|
|
175
|
+
prop.value,
|
|
176
|
+
model,
|
|
177
|
+
document
|
|
178
|
+
)
|
|
179
|
+
if (nestedRes) return nestedRes
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const findModelInChain = (node) => {
|
|
187
|
+
let current = node
|
|
188
|
+
while (current) {
|
|
189
|
+
if (
|
|
190
|
+
current.type === 'CallExpression' &&
|
|
191
|
+
current.callee?.type === 'MemberExpression'
|
|
192
|
+
) {
|
|
193
|
+
if (current.callee.object?.type === 'Identifier') {
|
|
194
|
+
const modelName = current.callee.object.name
|
|
195
|
+
return typeMap.models?.[modelName]
|
|
196
|
+
}
|
|
197
|
+
current = current.callee.object
|
|
198
|
+
} else if (current.type === 'MemberExpression') {
|
|
199
|
+
current = current.object
|
|
200
|
+
} else {
|
|
201
|
+
break
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return null
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
walk.simple(ast, {
|
|
208
|
+
CallExpression(node) {
|
|
209
|
+
if (node.callee.type === 'MemberExpression') {
|
|
210
|
+
const methodName =
|
|
211
|
+
node.callee.property?.type === 'Identifier'
|
|
212
|
+
? node.callee.property.name
|
|
213
|
+
: null
|
|
214
|
+
|
|
215
|
+
if (methodName === 'select' || methodName === 'omit') {
|
|
216
|
+
const model = findModelInChain(node.callee.object)
|
|
217
|
+
if (model) {
|
|
218
|
+
const firstArg = node.arguments?.[0]
|
|
219
|
+
if (firstArg?.type === 'ArrayExpression') {
|
|
220
|
+
for (const element of firstArg.elements) {
|
|
221
|
+
const res = checkStringLiteralForAttribute(
|
|
222
|
+
element,
|
|
223
|
+
model,
|
|
224
|
+
document
|
|
225
|
+
)
|
|
226
|
+
if (res) {
|
|
227
|
+
result = res
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} else if (methodName === 'sort') {
|
|
234
|
+
const model = findModelInChain(node.callee.object)
|
|
235
|
+
if (model) {
|
|
236
|
+
const firstArg = node.arguments?.[0]
|
|
237
|
+
if (firstArg?.type === 'ArrayExpression') {
|
|
238
|
+
const res = checkArrayForAttributes(firstArg, model, document)
|
|
239
|
+
if (res) {
|
|
240
|
+
result = res
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
} else if (
|
|
244
|
+
firstArg?.type === 'Literal' &&
|
|
245
|
+
typeof firstArg.value === 'string'
|
|
246
|
+
) {
|
|
247
|
+
const sortString = firstArg.value
|
|
248
|
+
const attrMatch = sortString.match(/^(\w+)/)
|
|
249
|
+
if (attrMatch) {
|
|
250
|
+
const attrName = attrMatch[1]
|
|
251
|
+
const attrInfo = model.attributes?.[attrName]
|
|
252
|
+
if (attrInfo?.line && attrInfo?.path) {
|
|
253
|
+
const sortStart = firstArg.start
|
|
254
|
+
const sortEnd = firstArg.end
|
|
255
|
+
if (offset >= sortStart && offset <= sortEnd) {
|
|
256
|
+
const uri = `file://${attrInfo.path}`
|
|
257
|
+
result = lsp.LocationLink.create(
|
|
258
|
+
uri,
|
|
259
|
+
lsp.Range.create(
|
|
260
|
+
attrInfo.line - 1,
|
|
261
|
+
0,
|
|
262
|
+
attrInfo.line - 1,
|
|
263
|
+
0
|
|
264
|
+
),
|
|
265
|
+
lsp.Range.create(
|
|
266
|
+
attrInfo.line - 1,
|
|
267
|
+
0,
|
|
268
|
+
attrInfo.line - 1,
|
|
269
|
+
0
|
|
270
|
+
),
|
|
271
|
+
lsp.Range.create(
|
|
272
|
+
document.positionAt(sortStart),
|
|
273
|
+
document.positionAt(sortEnd)
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
} else if (node.callee.object.type === 'Identifier') {
|
|
283
|
+
const modelName = node.callee.object.name
|
|
284
|
+
const model = typeMap.models?.[modelName]
|
|
285
|
+
|
|
286
|
+
if (!model) return
|
|
287
|
+
|
|
288
|
+
const firstArg = node.arguments?.[0]
|
|
289
|
+
if (firstArg?.type === 'ObjectExpression') {
|
|
290
|
+
const res = traverseObjectExpression(firstArg, model, document)
|
|
291
|
+
if (res) result = res
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
return result
|
|
299
|
+
} catch (error) {
|
|
300
|
+
return null
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -25,12 +25,13 @@ module.exports = async function goToModel(document, position, typeMap) {
|
|
|
25
25
|
const model = typeMap.models?.[modelName]
|
|
26
26
|
if (!model?.path) return null
|
|
27
27
|
|
|
28
|
+
const targetLine = model.attributesLine ? model.attributesLine - 1 : 0
|
|
28
29
|
const uri = `file://${model.path}`
|
|
29
30
|
return lsp.LocationLink.create(
|
|
30
31
|
uri,
|
|
31
|
-
lsp.Range.create(
|
|
32
|
-
lsp.Range.create(
|
|
33
|
-
lsp.Range.create(document.positionAt(start), document.positionAt(end))
|
|
32
|
+
lsp.Range.create(targetLine, 0, targetLine, 0),
|
|
33
|
+
lsp.Range.create(targetLine, 0, targetLine, 0),
|
|
34
|
+
lsp.Range.create(document.positionAt(start), document.positionAt(end))
|
|
34
35
|
)
|
|
35
36
|
}
|
|
36
37
|
}
|
|
@@ -1,38 +1,52 @@
|
|
|
1
1
|
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const acorn = require('acorn')
|
|
3
|
+
const walk = require('acorn-walk')
|
|
4
|
+
|
|
2
5
|
module.exports = async function goToPage(document, position, typeMap) {
|
|
3
6
|
const filePath = document.uri
|
|
4
7
|
if (!filePath.includes('/api/controllers/')) return null
|
|
5
8
|
const text = document.getText()
|
|
6
9
|
const offset = document.offsetAt(position)
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
try {
|
|
12
|
+
const ast = acorn.parse(text, {
|
|
13
|
+
ecmaVersion: 'latest',
|
|
14
|
+
sourceType: 'module'
|
|
15
|
+
})
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
const pageName = match.groups.page
|
|
15
|
-
const quote = match.groups.quote
|
|
16
|
-
const fullMatchStart =
|
|
17
|
-
match.index + match[0].indexOf(quote + pageName + quote)
|
|
18
|
-
const fullMatchEnd = fullMatchStart + pageName.length + 2 // +2 for quotes
|
|
17
|
+
let result = null
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
walk.simple(ast, {
|
|
20
|
+
Property(node) {
|
|
21
|
+
if (
|
|
22
|
+
node.key &&
|
|
23
|
+
(node.key.name === 'page' || node.key.value === 'page') &&
|
|
24
|
+
node.value &&
|
|
25
|
+
node.value.type === 'Literal' &&
|
|
26
|
+
typeof node.value.value === 'string'
|
|
27
|
+
) {
|
|
28
|
+
const pageName = node.value.value
|
|
29
|
+
if (offset >= node.value.start && offset <= node.value.end) {
|
|
30
|
+
const pagePath = typeMap.pages?.[pageName]
|
|
31
|
+
if (pagePath) {
|
|
32
|
+
const uri = `file://${pagePath.path}`
|
|
33
|
+
result = lsp.LocationLink.create(
|
|
34
|
+
uri,
|
|
35
|
+
lsp.Range.create(0, 0, 0, 0),
|
|
36
|
+
lsp.Range.create(0, 0, 0, 0),
|
|
37
|
+
lsp.Range.create(
|
|
38
|
+
document.positionAt(node.value.start),
|
|
39
|
+
document.positionAt(node.value.end)
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
33
45
|
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
46
|
+
})
|
|
36
47
|
|
|
37
|
-
|
|
48
|
+
return result
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
38
52
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const lsp = require('vscode-languageserver/node')
|
|
2
2
|
const path = require('path')
|
|
3
|
+
const acorn = require('acorn')
|
|
4
|
+
const walk = require('acorn-walk')
|
|
3
5
|
|
|
4
6
|
module.exports = async function goToView(document, position, typeMap) {
|
|
5
7
|
const fileName = path.basename(document.uri)
|
|
@@ -12,32 +14,48 @@ module.exports = async function goToView(document, position, typeMap) {
|
|
|
12
14
|
|
|
13
15
|
if (!isRoutes && !isController) return null
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
try {
|
|
18
|
+
const ast = acorn.parse(text, {
|
|
19
|
+
ecmaVersion: 'latest',
|
|
20
|
+
sourceType: 'module'
|
|
21
|
+
})
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
while ((match = regex.exec(text)) !== null) {
|
|
20
|
-
const viewName = match.groups.view
|
|
21
|
-
const quote = match.groups.quote
|
|
22
|
-
const fullMatchStart =
|
|
23
|
-
match.index + match[0].indexOf(quote + viewName + quote)
|
|
24
|
-
const fullMatchEnd = fullMatchStart + viewName.length + 2
|
|
23
|
+
let result = null
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
walk.simple(ast, {
|
|
26
|
+
Property(node) {
|
|
27
|
+
if (
|
|
28
|
+
node.key &&
|
|
29
|
+
(node.key.name === 'viewTemplatePath' ||
|
|
30
|
+
node.key.value === 'viewTemplatePath' ||
|
|
31
|
+
node.key.name === 'view' ||
|
|
32
|
+
node.key.value === 'view') &&
|
|
33
|
+
node.value &&
|
|
34
|
+
node.value.type === 'Literal' &&
|
|
35
|
+
typeof node.value.value === 'string'
|
|
36
|
+
) {
|
|
37
|
+
const viewName = node.value.value
|
|
38
|
+
if (offset >= node.value.start && offset <= node.value.end) {
|
|
39
|
+
const viewPath = typeMap.views?.[viewName]
|
|
40
|
+
if (viewPath) {
|
|
41
|
+
const uri = `file://${viewPath.path}`
|
|
42
|
+
result = lsp.LocationLink.create(
|
|
43
|
+
uri,
|
|
44
|
+
lsp.Range.create(0, 0, 0, 0),
|
|
45
|
+
lsp.Range.create(0, 0, 0, 0),
|
|
46
|
+
lsp.Range.create(
|
|
47
|
+
document.positionAt(node.value.start),
|
|
48
|
+
document.positionAt(node.value.end)
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
39
54
|
}
|
|
40
|
-
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return result
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return null
|
|
41
60
|
}
|
|
42
|
-
return null
|
|
43
61
|
}
|
package/index.js
CHANGED
|
@@ -11,7 +11,9 @@ const goToView = require('./go-to-definitions/go-to-view')
|
|
|
11
11
|
const goToPage = require('./go-to-definitions/go-to-page')
|
|
12
12
|
const goToPolicy = require('./go-to-definitions/go-to-policy')
|
|
13
13
|
const goToHelper = require('./go-to-definitions/go-to-helper')
|
|
14
|
+
const goToHelperInput = require('./go-to-definitions/go-to-helper-input')
|
|
14
15
|
const goToModel = require('./go-to-definitions/go-to-model')
|
|
16
|
+
const goToModelAttribute = require('./go-to-definitions/go-to-model-attribute')
|
|
15
17
|
|
|
16
18
|
// Completions
|
|
17
19
|
const actionsCompletion = require('./completions/actions-completion')
|
|
@@ -83,14 +85,18 @@ connection.onDefinition(async (params) => {
|
|
|
83
85
|
pageDefinition,
|
|
84
86
|
policyDefinition,
|
|
85
87
|
helperDefinition,
|
|
86
|
-
|
|
88
|
+
helperInputDefinition,
|
|
89
|
+
modelDefinition,
|
|
90
|
+
modelAttributeDefinition
|
|
87
91
|
] = await Promise.all([
|
|
88
92
|
goToAction(document, params.position, typeMap),
|
|
89
93
|
goToView(document, params.position, typeMap),
|
|
90
94
|
goToPage(document, params.position, typeMap),
|
|
91
95
|
goToPolicy(document, params.position, typeMap),
|
|
92
96
|
goToHelper(document, params.position, typeMap),
|
|
93
|
-
|
|
97
|
+
goToHelperInput(document, params.position, typeMap),
|
|
98
|
+
goToModel(document, params.position, typeMap),
|
|
99
|
+
goToModelAttribute(document, params.position, typeMap)
|
|
94
100
|
])
|
|
95
101
|
|
|
96
102
|
const definitions = [
|
|
@@ -99,8 +105,10 @@ connection.onDefinition(async (params) => {
|
|
|
99
105
|
pageDefinition,
|
|
100
106
|
policyDefinition,
|
|
101
107
|
helperDefinition,
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
helperInputDefinition,
|
|
109
|
+
modelDefinition,
|
|
110
|
+
modelAttributeDefinition
|
|
111
|
+
].filter((def) => def && (Array.isArray(def) ? def.length > 0 : true))
|
|
104
112
|
return definitions
|
|
105
113
|
})
|
|
106
114
|
|
package/package.json
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const acorn = require('acorn')
|
|
3
|
+
const walk = require('acorn-walk')
|
|
2
4
|
|
|
3
5
|
module.exports = function validateActionExist(document, typeMap) {
|
|
4
6
|
const diagnostics = []
|
|
@@ -28,23 +30,60 @@ module.exports = function validateActionExist(document, typeMap) {
|
|
|
28
30
|
|
|
29
31
|
function extractActionInfo(document) {
|
|
30
32
|
const text = document.getText()
|
|
31
|
-
const regex = /(['"])(.+?)\1\s*:\s*(?:{?\s*action\s*:\s*)?(['"])(.+?)\3/g
|
|
32
33
|
const actions = []
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const actionEnd = actionStart + action.length
|
|
39
|
-
|
|
40
|
-
actions.push({
|
|
41
|
-
action,
|
|
42
|
-
range: lsp.Range.create(
|
|
43
|
-
document.positionAt(actionStart),
|
|
44
|
-
document.positionAt(actionEnd)
|
|
45
|
-
)
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const ast = acorn.parse(text, {
|
|
37
|
+
ecmaVersion: 'latest',
|
|
38
|
+
sourceType: 'module'
|
|
46
39
|
})
|
|
47
|
-
|
|
40
|
+
|
|
41
|
+
walk.simple(ast, {
|
|
42
|
+
Property(node) {
|
|
43
|
+
const propertyKey = node.key?.value || node.key?.name
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
propertyKey === 'action' &&
|
|
47
|
+
node.value?.type === 'Literal' &&
|
|
48
|
+
typeof node.value.value === 'string'
|
|
49
|
+
) {
|
|
50
|
+
const actionName = node.value.value
|
|
51
|
+
actions.push({
|
|
52
|
+
action: actionName,
|
|
53
|
+
range: lsp.Range.create(
|
|
54
|
+
document.positionAt(node.value.start),
|
|
55
|
+
document.positionAt(node.value.end)
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
} else if (
|
|
59
|
+
typeof propertyKey === 'string' &&
|
|
60
|
+
(propertyKey.includes('GET') ||
|
|
61
|
+
propertyKey.includes('POST') ||
|
|
62
|
+
propertyKey.includes('PUT') ||
|
|
63
|
+
propertyKey.includes('PATCH') ||
|
|
64
|
+
propertyKey.includes('DELETE') ||
|
|
65
|
+
propertyKey.includes('/')) &&
|
|
66
|
+
node.value?.type === 'Literal' &&
|
|
67
|
+
typeof node.value.value === 'string'
|
|
68
|
+
) {
|
|
69
|
+
const actionName = node.value.value
|
|
70
|
+
if (
|
|
71
|
+
!actionName.startsWith('/') &&
|
|
72
|
+
!actionName.startsWith('http://') &&
|
|
73
|
+
!actionName.startsWith('https://')
|
|
74
|
+
) {
|
|
75
|
+
actions.push({
|
|
76
|
+
action: actionName,
|
|
77
|
+
range: lsp.Range.create(
|
|
78
|
+
document.positionAt(node.value.start),
|
|
79
|
+
document.positionAt(node.value.end)
|
|
80
|
+
)
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
} catch (error) {}
|
|
48
87
|
|
|
49
88
|
return actions
|
|
50
89
|
}
|