@sailshq/language-server 0.1.0 → 0.2.1

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.
@@ -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
  /**
4
6
  * Validate if a Waterline model attribute exists when used in criteria or chainable methods.
@@ -10,119 +12,286 @@ module.exports = function validateModelAttributeExist(document, typeMap) {
10
12
  const diagnostics = []
11
13
  const text = document.getText()
12
14
 
13
- // Criteria methods: Model.find({ attribute: ... }) etc.
14
- const criteriaRegex =
15
- /([A-Za-z0-9_]+)\s*\.\s*(find|findOne|update|destroy|where)\s*\(\s*\{\s*([A-Za-z0-9_]+)\s*:/g
16
-
17
- // Chainable: .select(['attr1', 'attr2']) or .omit(['attr1', ...])
18
- const arrayChainRegex = /\.(select|omit)\s*\(\s*\[([^\]]*)\]/g
19
-
20
- // Chainable: .populate('attr')
21
- const populateRegex = /\.populate\s*\(\s*['"]([A-Za-z0-9_]+)['"]\s*\)/g
22
-
23
- let match
24
-
25
- // Criteria methods
26
- while ((match = criteriaRegex.exec(text)) !== null) {
27
- const modelName = match[1]
28
- const attribute = match[3]
29
- const attrStart = match.index + match[0].lastIndexOf(attribute)
30
- const attrEnd = attrStart + attribute.length
31
-
32
- const model = typeMap.models && typeMap.models[modelName]
33
- if (!model) continue
34
-
35
- if (
36
- !model.attributes ||
37
- !Object.prototype.hasOwnProperty.call(model.attributes, attribute)
38
- ) {
39
- diagnostics.push(
40
- lsp.Diagnostic.create(
41
- lsp.Range.create(
42
- document.positionAt(attrStart),
43
- document.positionAt(attrEnd)
44
- ),
45
- `'${attribute}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
46
- lsp.DiagnosticSeverity.Error,
47
- 'sails-lsp'
48
- )
49
- )
15
+ // Build a lowercased model map for robust case-insensitive lookup
16
+ const modelMap = {}
17
+ if (typeMap.models) {
18
+ for (const key of Object.keys(typeMap.models)) {
19
+ modelMap[key.toLowerCase()] = typeMap.models[key]
50
20
  }
51
21
  }
52
-
53
- // .select(['attr1', ...]) and .omit(['attr1', ...])
54
- while ((match = arrayChainRegex.exec(text)) !== null) {
55
- const method = match[1]
56
- const attrsString = match[2]
57
- // Try to find the model name by searching backwards for ModelName.
58
- // This is a heuristic and may not be perfect.
59
- const before = text.slice(0, match.index)
60
- const modelMatch = /([A-Za-z0-9_]+)\s*\.\s*$/.exec(
61
- before.split('\n').pop() || ''
62
- )
63
- const modelName = modelMatch && modelMatch[1]
64
- if (!modelName) continue
65
- const model = typeMap.models && typeMap.models[modelName]
66
- if (!model) continue
67
-
68
- // Extract attribute names from the array string
69
- const attrRegex = /['"]([A-Za-z0-9_]+)['"]/g
70
- let attrMatch
71
- while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
72
- const attribute = attrMatch[1]
73
- const attrStart = match.index + match[0].indexOf(attribute)
74
- const attrEnd = attrStart + attribute.length
75
- if (
76
- !model.attributes ||
77
- !Object.prototype.hasOwnProperty.call(model.attributes, attribute)
78
- ) {
79
- diagnostics.push(
80
- lsp.Diagnostic.create(
81
- lsp.Range.create(
82
- document.positionAt(attrStart),
83
- document.positionAt(attrEnd)
84
- ),
85
- `'${attribute}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
86
- lsp.DiagnosticSeverity.Error,
87
- 'sails-lsp'
88
- )
89
- )
90
- }
91
- }
22
+ // Helper function to get model by name, case-insensitive
23
+ function getModelByName(name) {
24
+ if (!name) return undefined
25
+ const upper = name.charAt(0).toUpperCase() + name.slice(1)
26
+ return typeMap.models[upper]
92
27
  }
93
28
 
94
- // .populate('attr')
95
- while ((match = populateRegex.exec(text)) !== null) {
96
- const attribute = match[1]
97
- // Try to find the model name by searching backwards for ModelName.
98
- const before = text.slice(0, match.index)
99
- const modelMatch = /([A-Za-z0-9_]+)\s*\.\s*$/.exec(
100
- before.split('\n').pop() || ''
101
- )
102
- const modelName = modelMatch && modelMatch[1]
103
- if (!modelName) continue
104
- const model = typeMap.models && typeMap.models[modelName]
105
- if (!model) continue
106
-
107
- const attrStart = match.index + match[0].indexOf(attribute)
108
- const attrEnd = attrStart + attribute.length
109
- if (
110
- !model.attributes ||
111
- !Object.prototype.hasOwnProperty.call(model.attributes, attribute)
112
- ) {
113
- diagnostics.push(
114
- lsp.Diagnostic.create(
115
- lsp.Range.create(
116
- document.positionAt(attrStart),
117
- document.positionAt(attrEnd)
118
- ),
119
- `'${attribute}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
120
- lsp.DiagnosticSeverity.Error,
121
- 'sails-lsp'
122
- )
123
- )
124
- }
29
+ // AST-based: Validate Model.create({ ... }) and similar
30
+ try {
31
+ const ast = acorn.parse(text, {
32
+ ecmaVersion: 'latest',
33
+ sourceType: 'module'
34
+ })
35
+ walk.simple(ast, {
36
+ CallExpression(node) {
37
+ if (
38
+ node.callee &&
39
+ node.callee.type === 'MemberExpression' &&
40
+ node.arguments &&
41
+ node.arguments.length > 0 &&
42
+ node.arguments[0].type === 'ObjectExpression'
43
+ ) {
44
+ const method = node.callee.property.name
45
+ const modelName = node.callee.object.name
46
+ // Only check for Waterline methods
47
+ if (
48
+ [
49
+ 'create',
50
+ 'createEach',
51
+ 'count',
52
+ 'find',
53
+ 'findOne',
54
+ 'update',
55
+ 'destroy',
56
+ 'where',
57
+ 'findOrCreate',
58
+ 'sum'
59
+ ].includes(method)
60
+ ) {
61
+ const model = getModelByName(modelName)
62
+ if (!model) return
63
+ for (const prop of node.arguments[0].properties) {
64
+ const attribute = prop.key && (prop.key.name || prop.key.value)
65
+ const queryOptionKeys = [
66
+ 'where',
67
+ 'select',
68
+ 'omit',
69
+ 'sort',
70
+ 'limit',
71
+ 'skip',
72
+ 'page',
73
+ 'populate',
74
+ 'groupBy',
75
+ 'having',
76
+ 'sum',
77
+ 'average',
78
+ 'min',
79
+ 'max',
80
+ 'distinct',
81
+ 'meta'
82
+ ]
83
+ // --- FINAL FIX: treat 'where' exactly like 'sort', 'select', 'omit' for non-create methods ---
84
+ if (
85
+ method !== 'create' &&
86
+ method !== 'createEach' &&
87
+ queryOptionKeys.includes(attribute)
88
+ ) {
89
+ if (
90
+ attribute === 'where' &&
91
+ prop.value &&
92
+ prop.value.type === 'ObjectExpression'
93
+ ) {
94
+ for (const whereProp of prop.value.properties) {
95
+ if (!whereProp.key) continue
96
+ const whereAttr = whereProp.key.name || whereProp.key.value
97
+ if (
98
+ !model.attributes ||
99
+ !Object.prototype.hasOwnProperty.call(
100
+ model.attributes,
101
+ whereAttr
102
+ )
103
+ ) {
104
+ diagnostics.push(
105
+ lsp.Diagnostic.create(
106
+ lsp.Range.create(
107
+ document.positionAt(whereProp.key.start),
108
+ document.positionAt(whereProp.key.end)
109
+ ),
110
+ `'${whereAttr}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
111
+ lsp.DiagnosticSeverity.Error,
112
+ 'sails-lsp'
113
+ )
114
+ )
115
+ }
116
+ }
117
+ continue
118
+ }
119
+ if (
120
+ (attribute === 'select' || attribute === 'omit') &&
121
+ prop.value &&
122
+ prop.value.type === 'ArrayExpression'
123
+ ) {
124
+ for (const el of prop.value.elements) {
125
+ if (!el) continue
126
+ if (el.type === 'Literal' || el.type === 'StringLiteral') {
127
+ const arrAttr = el.value
128
+ if (
129
+ !model.attributes ||
130
+ !Object.prototype.hasOwnProperty.call(
131
+ model.attributes,
132
+ arrAttr
133
+ )
134
+ ) {
135
+ diagnostics.push(
136
+ lsp.Diagnostic.create(
137
+ lsp.Range.create(
138
+ document.positionAt(el.start),
139
+ document.positionAt(el.end)
140
+ ),
141
+ `'${arrAttr}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
142
+ lsp.DiagnosticSeverity.Error,
143
+ 'sails-lsp'
144
+ )
145
+ )
146
+ }
147
+ }
148
+ }
149
+ continue
150
+ }
151
+ if (attribute === 'sort' && prop.value) {
152
+ if (
153
+ prop.value.type === 'Literal' ||
154
+ prop.value.type === 'StringLiteral'
155
+ ) {
156
+ const sortStr = prop.value.value
157
+ const sortAttr = sortStr && sortStr.split(' ')[0]
158
+ if (
159
+ sortAttr &&
160
+ (!model.attributes ||
161
+ !Object.prototype.hasOwnProperty.call(
162
+ model.attributes,
163
+ sortAttr
164
+ ))
165
+ ) {
166
+ diagnostics.push(
167
+ lsp.Diagnostic.create(
168
+ lsp.Range.create(
169
+ document.positionAt(prop.value.start),
170
+ document.positionAt(prop.value.end)
171
+ ),
172
+ `'${sortAttr}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
173
+ lsp.DiagnosticSeverity.Error,
174
+ 'sails-lsp'
175
+ )
176
+ )
177
+ }
178
+ continue
179
+ } else if (prop.value.type === 'ArrayExpression') {
180
+ for (const el of prop.value.elements) {
181
+ if (!el) continue
182
+ if (el.type === 'ObjectExpression') {
183
+ for (const sortProp of el.properties) {
184
+ if (!sortProp.key) continue
185
+ const sortAttr =
186
+ sortProp.key.name || sortProp.key.value
187
+ if (
188
+ !model.attributes ||
189
+ !Object.prototype.hasOwnProperty.call(
190
+ model.attributes,
191
+ sortAttr
192
+ )
193
+ ) {
194
+ diagnostics.push(
195
+ lsp.Diagnostic.create(
196
+ lsp.Range.create(
197
+ document.positionAt(sortProp.key.start),
198
+ document.positionAt(sortProp.key.end)
199
+ ),
200
+ `'${sortAttr}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
201
+ lsp.DiagnosticSeverity.Error,
202
+ 'sails-lsp'
203
+ )
204
+ )
205
+ }
206
+ }
207
+ } else if (
208
+ el.type === 'Literal' ||
209
+ el.type === 'StringLiteral'
210
+ ) {
211
+ const sortStr = el.value
212
+ const sortAttr = sortStr && sortStr.split(' ')[0]
213
+ if (
214
+ sortAttr &&
215
+ (!model.attributes ||
216
+ !Object.prototype.hasOwnProperty.call(
217
+ model.attributes,
218
+ sortAttr
219
+ ))
220
+ ) {
221
+ diagnostics.push(
222
+ lsp.Diagnostic.create(
223
+ lsp.Range.create(
224
+ document.positionAt(el.start),
225
+ document.positionAt(el.end)
226
+ ),
227
+ `'${sortAttr}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
228
+ lsp.DiagnosticSeverity.Error,
229
+ 'sails-lsp'
230
+ )
231
+ )
232
+ }
233
+ }
234
+ }
235
+ continue
236
+ } else if (prop.value.type === 'ObjectExpression') {
237
+ for (const sortProp of prop.value.properties) {
238
+ if (!sortProp.key) continue
239
+ const sortAttr = sortProp.key.name || sortProp.key.value
240
+ if (
241
+ !model.attributes ||
242
+ !Object.prototype.hasOwnProperty.call(
243
+ model.attributes,
244
+ sortAttr
245
+ )
246
+ ) {
247
+ diagnostics.push(
248
+ lsp.Diagnostic.create(
249
+ lsp.Range.create(
250
+ document.positionAt(sortProp.key.start),
251
+ document.positionAt(sortProp.key.end)
252
+ ),
253
+ `'${sortAttr}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
254
+ lsp.DiagnosticSeverity.Error,
255
+ 'sails-lsp'
256
+ )
257
+ )
258
+ }
259
+ }
260
+ continue
261
+ }
262
+ }
263
+ // For all other query option keys, skip validation
264
+ continue
265
+ }
266
+ // --- END ROBUST FIX ---
267
+ // Only validate top-level for create/createEach
268
+ if (
269
+ (method === 'create' || method === 'createEach') &&
270
+ (!model.attributes ||
271
+ !Object.prototype.hasOwnProperty.call(
272
+ model.attributes,
273
+ attribute
274
+ ))
275
+ ) {
276
+ diagnostics.push(
277
+ lsp.Diagnostic.create(
278
+ lsp.Range.create(
279
+ document.positionAt(prop.key.start),
280
+ document.positionAt(prop.key.end)
281
+ ),
282
+ `'${attribute}' is not a valid attribute of model '${modelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
283
+ lsp.DiagnosticSeverity.Error,
284
+ 'sails-lsp'
285
+ )
286
+ )
287
+ }
288
+ }
289
+ }
290
+ }
291
+ }
292
+ })
293
+ } catch (err) {
294
+ // No regex fallback: rely solely on AST-based validation for accuracy
125
295
  }
126
-
127
296
  return diagnostics
128
297
  }
@@ -0,0 +1,64 @@
1
+ const lsp = require('vscode-languageserver/node')
2
+ /**
3
+ * Validate if a referenced model exists in Sails.js queries using regex.
4
+ * @param {TextDocument} document - The text document to validate.
5
+ * @param {Object} typeMap - The type map containing models.
6
+ * @returns {Array} diagnostics - Array of LSP diagnostics.
7
+ */
8
+ module.exports = function validateModelExist(document, typeMap) {
9
+ const diagnostics = []
10
+ const text = document.getText()
11
+ // Build a lowercased model map for robust case-insensitive lookup
12
+ const modelMap = {}
13
+ if (typeMap.models) {
14
+ for (const key of Object.keys(typeMap.models)) {
15
+ modelMap[key.toLowerCase()] = typeMap.models[key]
16
+ }
17
+ }
18
+ function modelExists(name) {
19
+ if (!name) return false
20
+ return !!modelMap[name.toLowerCase()]
21
+ }
22
+ // User.find() or User.create() etc
23
+ const modelCallRegex =
24
+ /\b([A-Za-z0-9_]+)\s*\.(?:find|findOne|create|createEach|update|destroy|count|sum|where|findOrCreate)\s*\(/g
25
+ let match
26
+ while ((match = modelCallRegex.exec(text)) !== null) {
27
+ const modelName = match[1]
28
+ if (!modelExists(modelName)) {
29
+ diagnostics.push(
30
+ lsp.Diagnostic.create(
31
+ lsp.Range.create(
32
+ document.positionAt(match.index),
33
+ document.positionAt(match.index + modelName.length)
34
+ ),
35
+ `Model '${modelName}' not found. Make sure it exists under your api/models directory.`,
36
+ lsp.DiagnosticSeverity.Error,
37
+ 'sails-lsp'
38
+ )
39
+ )
40
+ }
41
+ }
42
+ // sails.models.user.find() or sails.models.User.find()
43
+ const sailsModelCallRegex =
44
+ /sails\.models\.([A-Za-z0-9_]+)\s*\.(?:find|findOne|create|createEach|update|destroy|count|sum|where|findOrCreate)\s*\(/g
45
+ while ((match = sailsModelCallRegex.exec(text)) !== null) {
46
+ const modelName = match[1]
47
+ if (!modelExists(modelName)) {
48
+ diagnostics.push(
49
+ lsp.Diagnostic.create(
50
+ lsp.Range.create(
51
+ document.positionAt(match.index + 'sails.models.'.length),
52
+ document.positionAt(
53
+ match.index + 'sails.models.'.length + modelName.length
54
+ )
55
+ ),
56
+ `Model '${modelName}' does not exist in this Sails project.`,
57
+ lsp.DiagnosticSeverity.Error,
58
+ 'sails-lsp'
59
+ )
60
+ )
61
+ }
62
+ }
63
+ return diagnostics
64
+ }
@@ -0,0 +1,49 @@
1
+ const lsp = require('vscode-languageserver/node')
2
+
3
+ module.exports = function validateRequiredHelperInput(document, typeMap) {
4
+ const diagnostics = []
5
+ const text = document.getText()
6
+
7
+ // Regex to match sails.helpers.foo.bar.with({ ... })
8
+ // Captures: 1) helper path, 2) object literal content
9
+ const regex = /sails\.helpers((?:\.[a-zA-Z0-9_]+)+)\.with\s*\(\s*\{([^}]*)\}/g
10
+ let match
11
+ while ((match = regex.exec(text)) !== null) {
12
+ // Build helper name: e.g. .foo.bar => foo/bar
13
+ const segments = match[1].split('.').filter(Boolean)
14
+ const toKebab = (s) => s.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
15
+ const fullHelperName = segments.map(toKebab).join('/')
16
+ const helperInfo = typeMap.helpers && typeMap.helpers[fullHelperName]
17
+ if (!helperInfo || !helperInfo.inputs) continue
18
+
19
+ // Find all property names in the object literal
20
+ const propsRegex = /([a-zA-Z0-9_]+)\s*:/g
21
+ let propMatch
22
+ const providedKeys = new Set()
23
+ while ((propMatch = propsRegex.exec(match[2])) !== null) {
24
+ providedKeys.add(propMatch[1])
25
+ }
26
+ // Check for missing required inputs (support boolean or string 'required')
27
+ for (const [inputKey, inputDef] of Object.entries(helperInfo.inputs)) {
28
+ const isRequired =
29
+ inputDef && (inputDef.required === true || inputDef.required === 'true')
30
+ if (isRequired && !providedKeys.has(inputKey)) {
31
+ // Find the start/end of the object literal for the diagnostic range
32
+ const objStart = match.index + match[0].indexOf('{')
33
+ const objEnd = objStart + match[2].length + 1 // +1 for closing }
34
+ diagnostics.push(
35
+ lsp.Diagnostic.create(
36
+ lsp.Range.create(
37
+ document.positionAt(objStart),
38
+ document.positionAt(objEnd)
39
+ ),
40
+ `Missing required input '${inputKey}' for helper '${fullHelperName}'.`,
41
+ lsp.DiagnosticSeverity.Error,
42
+ 'sails-lsp'
43
+ )
44
+ )
45
+ }
46
+ }
47
+ }
48
+ return diagnostics
49
+ }
@@ -0,0 +1,56 @@
1
+ const lsp = require('vscode-languageserver/node')
2
+
3
+ module.exports = function validateRequiredModelAttribute(document, typeMap) {
4
+ const diagnostics = []
5
+ const text = document.getText()
6
+ const models = typeMap.models || {}
7
+
8
+ // For each model, check for .create({ ... }) and .createEach({ ... }) calls
9
+ for (const [modelName, modelInfo] of Object.entries(models)) {
10
+ if (!modelInfo || !modelInfo.attributes) continue
11
+ // Only validate required attributes for create and createEach
12
+ const methodRegex = new RegExp(
13
+ `${modelName}\\s*\\.\\s*(create|createEach)\\s*\\(\\s*\\{([\\s\\S]*?)\\}\\s*\\)`,
14
+ 'g'
15
+ )
16
+ let match
17
+ while ((match = methodRegex.exec(text)) !== null) {
18
+ // match[2] is the object literal content
19
+ // Find all property names in the object literal, including shorthand
20
+ const propsRegex = /([a-zA-Z0-9_]+)\s*:/g
21
+ const shorthandRegex = /([a-zA-Z0-9_]+)\s*(,|(?=\n|\}))/g
22
+ let propMatch
23
+ const providedKeys = new Set()
24
+ while ((propMatch = propsRegex.exec(match[2])) !== null) {
25
+ providedKeys.add(propMatch[1])
26
+ }
27
+ while ((propMatch = shorthandRegex.exec(match[2])) !== null) {
28
+ if (!providedKeys.has(propMatch[1])) {
29
+ providedKeys.add(propMatch[1])
30
+ }
31
+ }
32
+ // Check for missing required attributes
33
+ for (const [attr, def] of Object.entries(modelInfo.attributes)) {
34
+ const isRequired =
35
+ def && (def.required === true || def.required === 'true')
36
+ if (isRequired && !providedKeys.has(attr)) {
37
+ // Find the start/end of the object literal for the diagnostic range
38
+ const objStart = match.index + match[0].indexOf('{')
39
+ const objEnd = objStart + match[2].length + 1 // +1 for closing }
40
+ diagnostics.push(
41
+ lsp.Diagnostic.create(
42
+ lsp.Range.create(
43
+ document.positionAt(objStart),
44
+ document.positionAt(objEnd)
45
+ ),
46
+ `Missing required attribute '${attr}' in ${modelName}.${match[1]}().`,
47
+ lsp.DiagnosticSeverity.Error,
48
+ 'sails-lsp'
49
+ )
50
+ )
51
+ }
52
+ }
53
+ }
54
+ }
55
+ return diagnostics
56
+ }
@@ -0,0 +1,86 @@
1
+ const lsp = require('vscode-languageserver/node')
2
+
3
+ module.exports = function validateViewExist(document, typeMap) {
4
+ const diagnostics = []
5
+
6
+ // Only check routes.js and actions (api/controllers)
7
+ const isRoutes = document.uri.endsWith('routes.js')
8
+ const isAction = document.uri.includes('/api/controllers/')
9
+ if (!isRoutes && !isAction) return diagnostics
10
+
11
+ // Extract { view: '...' } in routes.js
12
+ if (isRoutes) {
13
+ const views = extractViewReferences(document)
14
+ for (const { view, range } of views) {
15
+ if (!typeMap.views?.[view]) {
16
+ diagnostics.push(
17
+ lsp.Diagnostic.create(
18
+ range,
19
+ `View '${view}' not found. Make sure it exists in your /views directory.`,
20
+ lsp.DiagnosticSeverity.Error,
21
+ 'sails-lsp'
22
+ )
23
+ )
24
+ }
25
+ }
26
+ }
27
+
28
+ // Extract viewTemplatePath: '...' in actions
29
+ if (isAction) {
30
+ const exits = extractViewTemplatePathReferences(document)
31
+ for (const { view, range } of exits) {
32
+ if (!typeMap.views?.[view]) {
33
+ diagnostics.push(
34
+ lsp.Diagnostic.create(
35
+ range,
36
+ `View '${view}' not found. Make sure it exists in your /views directory.`,
37
+ lsp.DiagnosticSeverity.Error,
38
+ 'sails-lsp'
39
+ )
40
+ )
41
+ }
42
+ }
43
+ }
44
+
45
+ return diagnostics
46
+ }
47
+
48
+ function extractViewReferences(document) {
49
+ const text = document.getText()
50
+ const regex = /view\s*:\s*['"]([^'"]+)['"]/g
51
+ const views = []
52
+ let match
53
+ while ((match = regex.exec(text)) !== null) {
54
+ const view = match[1]
55
+ const viewStart = match.index + match[0].indexOf(view)
56
+ const viewEnd = viewStart + view.length
57
+ views.push({
58
+ view,
59
+ range: lsp.Range.create(
60
+ document.positionAt(viewStart),
61
+ document.positionAt(viewEnd)
62
+ )
63
+ })
64
+ }
65
+ return views
66
+ }
67
+
68
+ function extractViewTemplatePathReferences(document) {
69
+ const text = document.getText()
70
+ const regex = /viewTemplatePath\s*:\s*['"]([^'"]+)['"]/g
71
+ const views = []
72
+ let match
73
+ while ((match = regex.exec(text)) !== null) {
74
+ const view = match[1]
75
+ const viewStart = match.index + match[0].indexOf(view)
76
+ const viewEnd = viewStart + view.length
77
+ views.push({
78
+ view,
79
+ range: lsp.Range.create(
80
+ document.positionAt(viewStart),
81
+ document.positionAt(viewEnd)
82
+ )
83
+ })
84
+ }
85
+ return views
86
+ }