@sailshq/language-server 0.0.5 → 0.2.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.
Files changed (38) hide show
  1. package/SailsParser.js +652 -0
  2. package/completions/actions-completion.js +36 -0
  3. package/completions/data-types-completion.js +39 -0
  4. package/completions/helper-inputs-completion.js +91 -0
  5. package/completions/helpers-completion.js +85 -0
  6. package/completions/inertia-pages-completion.js +33 -0
  7. package/completions/input-props-completion.js +52 -0
  8. package/completions/model-attribute-props-completion.js +57 -0
  9. package/completions/model-attributes-completion.js +195 -0
  10. package/completions/model-methods-completion.js +71 -0
  11. package/completions/models-completion.js +52 -0
  12. package/completions/policies-completion.js +32 -0
  13. package/completions/views-completion.js +35 -0
  14. package/go-to-definitions/go-to-action.js +26 -49
  15. package/go-to-definitions/go-to-helper.js +37 -45
  16. package/go-to-definitions/go-to-model.js +39 -0
  17. package/go-to-definitions/go-to-page.js +38 -0
  18. package/go-to-definitions/go-to-policy.js +23 -72
  19. package/go-to-definitions/go-to-view.js +28 -55
  20. package/index.js +103 -19
  21. package/package.json +1 -1
  22. package/validators/validate-action-exist.js +28 -51
  23. package/validators/validate-data-type.js +34 -0
  24. package/validators/validate-document.js +42 -4
  25. package/validators/validate-helper-input-exist.js +42 -0
  26. package/validators/validate-model-attribute-exist.js +297 -0
  27. package/validators/validate-model-exist.js +64 -0
  28. package/validators/validate-page-exist.js +42 -0
  29. package/validators/validate-policy-exist.js +45 -0
  30. package/validators/validate-required-helper-input.js +49 -0
  31. package/validators/validate-required-model-attribute.js +56 -0
  32. package/validators/validate-view-exist.js +86 -0
  33. package/completions/sails-completions.js +0 -63
  34. package/go-to-definitions/go-to-inertia-page.js +0 -53
  35. package/helpers/find-fn-line.js +0 -21
  36. package/helpers/find-project-root.js +0 -18
  37. package/helpers/find-sails.js +0 -12
  38. package/helpers/load-sails.js +0 -39
@@ -0,0 +1,297 @@
1
+ const lsp = require('vscode-languageserver/node')
2
+ const acorn = require('acorn')
3
+ const walk = require('acorn-walk')
4
+
5
+ /**
6
+ * Validate if a Waterline model attribute exists when used in criteria or chainable methods.
7
+ * @param {TextDocument} document - The text document to validate.
8
+ * @param {Object} typeMap - The type map containing models and their attributes.
9
+ * @returns {Array} diagnostics - Array of LSP diagnostics.
10
+ */
11
+ module.exports = function validateModelAttributeExist(document, typeMap) {
12
+ const diagnostics = []
13
+ const text = document.getText()
14
+
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]
20
+ }
21
+ }
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]
27
+ }
28
+
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
295
+ }
296
+ return diagnostics
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,42 @@
1
+ const lsp = require('vscode-languageserver/node')
2
+
3
+ module.exports = function validatePageExist(document, typeMap) {
4
+ const diagnostics = []
5
+
6
+ const pages = extractPageReferences(document)
7
+ for (const { page, range } of pages) {
8
+ if (!typeMap.pages?.[page]) {
9
+ diagnostics.push(
10
+ lsp.Diagnostic.create(
11
+ range,
12
+ `Inertia page '${page}' not found. Make sure it exists under your /assets/js/pages directory.`,
13
+ lsp.DiagnosticSeverity.Error,
14
+ 'sails-lsp'
15
+ )
16
+ )
17
+ }
18
+ }
19
+ return diagnostics
20
+ }
21
+
22
+ function extractPageReferences(document) {
23
+ const text = document.getText()
24
+ const regex = /\bpage\s*:\s*['"]([^'"]+)['"]/g
25
+ const pages = []
26
+
27
+ let match
28
+ while ((match = regex.exec(text)) !== null) {
29
+ const page = match[1]
30
+ const pageStart = match.index + match[0].indexOf(page)
31
+ const pageEnd = pageStart + page.length
32
+
33
+ pages.push({
34
+ page,
35
+ range: lsp.Range.create(
36
+ document.positionAt(pageStart),
37
+ document.positionAt(pageEnd)
38
+ )
39
+ })
40
+ }
41
+ return pages
42
+ }
@@ -0,0 +1,45 @@
1
+ const lsp = require('vscode-languageserver/node')
2
+
3
+ module.exports = function validatePolicyExist(document, typeMap) {
4
+ const diagnostics = []
5
+ if (!document.uri.endsWith('policies.js')) return diagnostics
6
+
7
+ const policyRefs = extractPolicyReferences(document)
8
+ for (const { policy, range } of policyRefs) {
9
+ if (!typeMap.policies?.[policy]) {
10
+ diagnostics.push(
11
+ lsp.Diagnostic.create(
12
+ range,
13
+ `Policy '${policy}' not found. Make sure it exists in api/policies.`,
14
+ lsp.DiagnosticSeverity.Error,
15
+ 'sails-lsp'
16
+ )
17
+ )
18
+ }
19
+ }
20
+
21
+ return diagnostics
22
+ }
23
+
24
+ function extractPolicyReferences(document) {
25
+ const text = document.getText()
26
+ const policyRegex = /(?::|=)\s*(?:\[\s*)?['"]([^'"]+)['"]/g
27
+ const policies = []
28
+
29
+ let match
30
+ while ((match = policyRegex.exec(text)) !== null) {
31
+ const policy = match[1]
32
+ const policyStart = match.index + match[0].indexOf(policy)
33
+ const policyEnd = policyStart + policy.length
34
+
35
+ policies.push({
36
+ policy,
37
+ range: lsp.Range.create(
38
+ document.positionAt(policyStart),
39
+ document.positionAt(policyEnd)
40
+ )
41
+ })
42
+ }
43
+
44
+ return policies
45
+ }
@@ -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
+ }