@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
|
@@ -1,34 +1,97 @@
|
|
|
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 validateDataType(document, typeMap) {
|
|
4
6
|
const diagnostics = []
|
|
5
|
-
|
|
6
7
|
const text = document.getText()
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
9
|
+
try {
|
|
10
|
+
const ast = acorn.parse(text, {
|
|
11
|
+
ecmaVersion: 'latest',
|
|
12
|
+
sourceType: 'module'
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
walk.simple(ast, {
|
|
16
|
+
ObjectExpression(node) {
|
|
17
|
+
// Check if this object has both 'type' and other properties that suggest it's a definition
|
|
18
|
+
// (like 'required', 'description', 'allowNull', 'defaultsTo', 'example', etc.)
|
|
19
|
+
let hasTypeProperty = false
|
|
20
|
+
let typePropertyNode = null
|
|
21
|
+
let typeValue = null
|
|
22
|
+
let hasDefinitionProperties = false
|
|
23
|
+
|
|
24
|
+
for (const prop of node.properties) {
|
|
25
|
+
if (prop.type !== 'Property') continue
|
|
26
|
+
|
|
27
|
+
const keyName = prop.key.name || prop.key.value
|
|
28
|
+
|
|
29
|
+
// Check if this is a 'type' property
|
|
30
|
+
if (keyName === 'type' && prop.value.type === 'Literal') {
|
|
31
|
+
hasTypeProperty = true
|
|
32
|
+
typePropertyNode = prop.value
|
|
33
|
+
typeValue = prop.value.value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check for properties that indicate this is a model/action/helper definition
|
|
37
|
+
if (
|
|
38
|
+
[
|
|
39
|
+
'required',
|
|
40
|
+
'description',
|
|
41
|
+
'allowNull',
|
|
42
|
+
'defaultsTo',
|
|
43
|
+
'columnName',
|
|
44
|
+
'columnType',
|
|
45
|
+
'autoMigrations',
|
|
46
|
+
'autoCreatedAt',
|
|
47
|
+
'autoUpdatedAt',
|
|
48
|
+
'model',
|
|
49
|
+
'collection',
|
|
50
|
+
'via',
|
|
51
|
+
'through',
|
|
52
|
+
'unique',
|
|
53
|
+
'isEmail',
|
|
54
|
+
'isURL',
|
|
55
|
+
'isIn',
|
|
56
|
+
'min',
|
|
57
|
+
'max',
|
|
58
|
+
'minLength',
|
|
59
|
+
'maxLength',
|
|
60
|
+
'example',
|
|
61
|
+
'validations',
|
|
62
|
+
'regex',
|
|
63
|
+
'extendedDescription',
|
|
64
|
+
'moreInfoUrl',
|
|
65
|
+
'whereToGet'
|
|
66
|
+
].includes(keyName)
|
|
67
|
+
) {
|
|
68
|
+
hasDefinitionProperties = true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Only validate if this looks like an attribute/input definition
|
|
73
|
+
if (hasTypeProperty && hasDefinitionProperties && typeValue) {
|
|
74
|
+
const isValid = typeMap.dataTypes.some((dt) => dt.type === typeValue)
|
|
75
|
+
|
|
76
|
+
if (!isValid) {
|
|
77
|
+
diagnostics.push(
|
|
78
|
+
lsp.Diagnostic.create(
|
|
79
|
+
lsp.Range.create(
|
|
80
|
+
document.positionAt(typePropertyNode.start),
|
|
81
|
+
document.positionAt(typePropertyNode.end)
|
|
82
|
+
),
|
|
83
|
+
`'${typeValue}' is not a recognized data type. Valid data types are: ${typeMap.dataTypes.map((dataType) => dataType.type).join(', ')}.`,
|
|
84
|
+
lsp.DiagnosticSeverity.Error,
|
|
85
|
+
'sails-lsp'
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
} catch (error) {
|
|
93
|
+
// Ignore parse errors
|
|
32
94
|
}
|
|
95
|
+
|
|
33
96
|
return diagnostics
|
|
34
97
|
}
|
|
@@ -1,42 +1,108 @@
|
|
|
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 validateHelperInputExist(document, typeMap) {
|
|
4
6
|
const diagnostics = []
|
|
5
7
|
const text = document.getText()
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
9
|
+
try {
|
|
10
|
+
const ast = acorn.parse(text, {
|
|
11
|
+
ecmaVersion: 'latest',
|
|
12
|
+
sourceType: 'module'
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
walk.simple(ast, {
|
|
16
|
+
CallExpression(node) {
|
|
17
|
+
// Match sails.helpers.foo.bar.with({ ... })
|
|
18
|
+
if (
|
|
19
|
+
node.callee &&
|
|
20
|
+
node.callee.type === 'MemberExpression' &&
|
|
21
|
+
node.callee.property.name === 'with' &&
|
|
22
|
+
node.callee.object &&
|
|
23
|
+
node.callee.object.type === 'MemberExpression'
|
|
24
|
+
) {
|
|
25
|
+
// Extract helper path from sails.helpers.foo.bar
|
|
26
|
+
const helperPath = extractHelperPath(node.callee.object)
|
|
27
|
+
if (!helperPath) return
|
|
28
|
+
|
|
29
|
+
const helperInfo = typeMap.helpers && typeMap.helpers[helperPath]
|
|
30
|
+
if (!helperInfo || !helperInfo.inputs) return
|
|
31
|
+
|
|
32
|
+
// Get the object argument to .with()
|
|
33
|
+
const objArg = node.arguments[0]
|
|
34
|
+
if (!objArg || objArg.type !== 'ObjectExpression') return
|
|
35
|
+
|
|
36
|
+
// Validate each property in the object
|
|
37
|
+
for (const prop of objArg.properties) {
|
|
38
|
+
// Handle both regular properties and shorthand properties
|
|
39
|
+
let key
|
|
40
|
+
if (prop.type === 'Property') {
|
|
41
|
+
if (prop.key.type === 'Identifier') {
|
|
42
|
+
key = prop.key.name
|
|
43
|
+
} else if (prop.key.type === 'Literal') {
|
|
44
|
+
key = prop.key.value
|
|
45
|
+
}
|
|
46
|
+
} else if (prop.type === 'SpreadElement') {
|
|
47
|
+
// Skip spread elements
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!key) continue
|
|
52
|
+
|
|
53
|
+
// Check if this key exists in the helper's inputs
|
|
54
|
+
if (!Object.prototype.hasOwnProperty.call(helperInfo.inputs, key)) {
|
|
55
|
+
diagnostics.push(
|
|
56
|
+
lsp.Diagnostic.create(
|
|
57
|
+
lsp.Range.create(
|
|
58
|
+
document.positionAt(prop.key.start),
|
|
59
|
+
document.positionAt(prop.key.end)
|
|
60
|
+
),
|
|
61
|
+
`Unknown input property '${key}' for helper '${helperPath}'.`,
|
|
62
|
+
lsp.DiagnosticSeverity.Error,
|
|
63
|
+
'sails-lsp'
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
38
69
|
}
|
|
39
|
-
}
|
|
70
|
+
})
|
|
71
|
+
} catch (error) {
|
|
72
|
+
// Ignore parse errors
|
|
40
73
|
}
|
|
74
|
+
|
|
41
75
|
return diagnostics
|
|
42
76
|
}
|
|
77
|
+
|
|
78
|
+
function extractHelperPath(node) {
|
|
79
|
+
// Walk up the member expression to extract the full helper path
|
|
80
|
+
const segments = []
|
|
81
|
+
let current = node
|
|
82
|
+
|
|
83
|
+
// Collect all segments until we reach sails.helpers
|
|
84
|
+
while (current && current.type === 'MemberExpression') {
|
|
85
|
+
if (current.property && current.property.type === 'Identifier') {
|
|
86
|
+
const propName = current.property.name
|
|
87
|
+
// Stop when we reach 'helpers'
|
|
88
|
+
if (propName === 'helpers') {
|
|
89
|
+
// Check if the object is 'sails'
|
|
90
|
+
if (
|
|
91
|
+
current.object &&
|
|
92
|
+
current.object.type === 'Identifier' &&
|
|
93
|
+
current.object.name === 'sails'
|
|
94
|
+
) {
|
|
95
|
+
// Valid sails.helpers path found
|
|
96
|
+
const toKebab = (s) =>
|
|
97
|
+
s.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
|
|
98
|
+
return segments.map(toKebab).join('/')
|
|
99
|
+
}
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
segments.unshift(propName)
|
|
103
|
+
}
|
|
104
|
+
current = current.object
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
@@ -2,6 +2,126 @@ const lsp = require('vscode-languageserver/node')
|
|
|
2
2
|
const acorn = require('acorn')
|
|
3
3
|
const walk = require('acorn-walk')
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Waterline query modifiers and operators
|
|
7
|
+
*/
|
|
8
|
+
const WATERLINE_MODIFIERS = ['or', 'and', 'not']
|
|
9
|
+
const WATERLINE_OPERATORS = [
|
|
10
|
+
'<',
|
|
11
|
+
'<=',
|
|
12
|
+
'>',
|
|
13
|
+
'>=',
|
|
14
|
+
'!=',
|
|
15
|
+
'nin',
|
|
16
|
+
'in',
|
|
17
|
+
'contains',
|
|
18
|
+
'startsWith',
|
|
19
|
+
'endsWith',
|
|
20
|
+
'like',
|
|
21
|
+
'!'
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Helper function to recursively validate criteria attributes
|
|
26
|
+
* @param {Object} objNode - AST ObjectExpression node
|
|
27
|
+
* @param {Object} model - Model with attributes
|
|
28
|
+
* @param {TextDocument} document - Text document
|
|
29
|
+
* @param {Array} diagnostics - Diagnostics array to push to
|
|
30
|
+
* @param {string} effectiveModelName - Model name for error messages
|
|
31
|
+
*/
|
|
32
|
+
function validateCriteriaAttributes(
|
|
33
|
+
objNode,
|
|
34
|
+
model,
|
|
35
|
+
document,
|
|
36
|
+
diagnostics,
|
|
37
|
+
effectiveModelName
|
|
38
|
+
) {
|
|
39
|
+
if (!objNode || objNode.type !== 'ObjectExpression' || !objNode.properties) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const prop of objNode.properties) {
|
|
44
|
+
if (!prop.key) continue
|
|
45
|
+
const attrName = prop.key.name || prop.key.value
|
|
46
|
+
|
|
47
|
+
if (WATERLINE_MODIFIERS.includes(attrName)) {
|
|
48
|
+
if (
|
|
49
|
+
prop.value &&
|
|
50
|
+
prop.value.type === 'ArrayExpression' &&
|
|
51
|
+
prop.value.elements
|
|
52
|
+
) {
|
|
53
|
+
for (const el of prop.value.elements) {
|
|
54
|
+
if (el && el.type === 'ObjectExpression') {
|
|
55
|
+
validateCriteriaAttributes(
|
|
56
|
+
el,
|
|
57
|
+
model,
|
|
58
|
+
document,
|
|
59
|
+
diagnostics,
|
|
60
|
+
effectiveModelName
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} else if (prop.value && prop.value.type === 'ObjectExpression') {
|
|
65
|
+
validateCriteriaAttributes(
|
|
66
|
+
prop.value,
|
|
67
|
+
model,
|
|
68
|
+
document,
|
|
69
|
+
diagnostics,
|
|
70
|
+
effectiveModelName
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
prop.value &&
|
|
78
|
+
prop.value.type === 'ObjectExpression' &&
|
|
79
|
+
prop.value.properties &&
|
|
80
|
+
prop.value.properties.length > 0
|
|
81
|
+
) {
|
|
82
|
+
const firstKey =
|
|
83
|
+
prop.value.properties[0].key?.name ||
|
|
84
|
+
prop.value.properties[0].key?.value
|
|
85
|
+
if (WATERLINE_OPERATORS.includes(firstKey)) {
|
|
86
|
+
if (
|
|
87
|
+
!model.attributes ||
|
|
88
|
+
!Object.prototype.hasOwnProperty.call(model.attributes, attrName)
|
|
89
|
+
) {
|
|
90
|
+
diagnostics.push(
|
|
91
|
+
lsp.Diagnostic.create(
|
|
92
|
+
lsp.Range.create(
|
|
93
|
+
document.positionAt(prop.key.start),
|
|
94
|
+
document.positionAt(prop.key.end)
|
|
95
|
+
),
|
|
96
|
+
`'${attrName}' is not a valid attribute of model '${effectiveModelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
|
|
97
|
+
lsp.DiagnosticSeverity.Error,
|
|
98
|
+
'sails-lsp'
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (
|
|
107
|
+
!model.attributes ||
|
|
108
|
+
!Object.prototype.hasOwnProperty.call(model.attributes, attrName)
|
|
109
|
+
) {
|
|
110
|
+
diagnostics.push(
|
|
111
|
+
lsp.Diagnostic.create(
|
|
112
|
+
lsp.Range.create(
|
|
113
|
+
document.positionAt(prop.key.start),
|
|
114
|
+
document.positionAt(prop.key.end)
|
|
115
|
+
),
|
|
116
|
+
`'${attrName}' is not a valid attribute of model '${effectiveModelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
|
|
117
|
+
lsp.DiagnosticSeverity.Error,
|
|
118
|
+
'sails-lsp'
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
5
125
|
/**
|
|
6
126
|
* Validate if a Waterline model attribute exists when used in criteria or chainable methods.
|
|
7
127
|
* @param {TextDocument} document - The text document to validate.
|
|
@@ -26,6 +146,36 @@ module.exports = function validateModelAttributeExist(document, typeMap) {
|
|
|
26
146
|
return typeMap.models[upper]
|
|
27
147
|
}
|
|
28
148
|
|
|
149
|
+
// Helper to check if an identifier is likely a Sails model
|
|
150
|
+
function isLikelyModel(name) {
|
|
151
|
+
if (!name) return false
|
|
152
|
+
|
|
153
|
+
// Exclude common globals and libraries
|
|
154
|
+
const knownGlobals = [
|
|
155
|
+
'_',
|
|
156
|
+
'sails',
|
|
157
|
+
'require',
|
|
158
|
+
'module',
|
|
159
|
+
'exports',
|
|
160
|
+
'console',
|
|
161
|
+
'process'
|
|
162
|
+
]
|
|
163
|
+
if (knownGlobals.includes(name)) {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check if it's in the typeMap models (case-insensitive)
|
|
168
|
+
const upper = name.charAt(0).toUpperCase() + name.slice(1)
|
|
169
|
+
if (typeMap.models && typeMap.models[upper]) {
|
|
170
|
+
return true
|
|
171
|
+
}
|
|
172
|
+
// Also check lowercase version
|
|
173
|
+
if (typeMap.models && typeMap.models[name.toLowerCase()]) {
|
|
174
|
+
return true
|
|
175
|
+
}
|
|
176
|
+
return false
|
|
177
|
+
}
|
|
178
|
+
|
|
29
179
|
// AST-based: Validate Model.create({ ... }) and similar
|
|
30
180
|
try {
|
|
31
181
|
const ast = acorn.parse(text, {
|
|
@@ -53,6 +203,9 @@ module.exports = function validateModelAttributeExist(document, typeMap) {
|
|
|
53
203
|
break
|
|
54
204
|
}
|
|
55
205
|
}
|
|
206
|
+
// Only proceed if this is actually a known Sails model
|
|
207
|
+
if (!isLikelyModel(effectiveModelName)) return
|
|
208
|
+
|
|
56
209
|
const model = getModelByName(effectiveModelName)
|
|
57
210
|
if (!model) return
|
|
58
211
|
|
|
@@ -287,63 +440,42 @@ module.exports = function validateModelAttributeExist(document, typeMap) {
|
|
|
287
440
|
'and',
|
|
288
441
|
'not'
|
|
289
442
|
]
|
|
290
|
-
// For non-create methods,
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
model.attributes,
|
|
300
|
-
attribute
|
|
301
|
-
)
|
|
302
|
-
) {
|
|
303
|
-
diagnostics.push(
|
|
304
|
-
lsp.Diagnostic.create(
|
|
305
|
-
lsp.Range.create(
|
|
306
|
-
document.positionAt(prop.key.start),
|
|
307
|
-
document.positionAt(prop.key.end)
|
|
308
|
-
),
|
|
309
|
-
`'${attribute}' is not a valid attribute of model '${effectiveModelName}'. Valid attributes: ${Object.keys(model.attributes || {}).join(', ')}`,
|
|
310
|
-
lsp.DiagnosticSeverity.Error,
|
|
311
|
-
'sails-lsp'
|
|
312
|
-
)
|
|
443
|
+
// For non-create methods, use the helper to validate criteria
|
|
444
|
+
if (method !== 'create' && method !== 'createEach') {
|
|
445
|
+
if (!queryOptionKeys.includes(attribute)) {
|
|
446
|
+
validateCriteriaAttributes(
|
|
447
|
+
{ type: 'ObjectExpression', properties: [prop] },
|
|
448
|
+
model,
|
|
449
|
+
document,
|
|
450
|
+
diagnostics,
|
|
451
|
+
effectiveModelName
|
|
313
452
|
)
|
|
314
|
-
}
|
|
315
|
-
continue
|
|
316
|
-
}
|
|
317
|
-
if (
|
|
318
|
-
method !== 'create' &&
|
|
319
|
-
method !== 'createEach' &&
|
|
320
|
-
queryOptionKeys.includes(attribute)
|
|
321
|
-
) {
|
|
322
|
-
if (
|
|
453
|
+
} else if (
|
|
323
454
|
attribute === 'where' &&
|
|
324
455
|
prop.value &&
|
|
325
456
|
prop.value.type === 'ObjectExpression'
|
|
326
457
|
) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
458
|
+
validateCriteriaAttributes(
|
|
459
|
+
prop.value,
|
|
460
|
+
model,
|
|
461
|
+
document,
|
|
462
|
+
diagnostics,
|
|
463
|
+
effectiveModelName
|
|
464
|
+
)
|
|
465
|
+
continue
|
|
466
|
+
} else if (
|
|
467
|
+
WATERLINE_MODIFIERS.includes(attribute) &&
|
|
468
|
+
prop.value &&
|
|
469
|
+
prop.value.type === 'ArrayExpression'
|
|
470
|
+
) {
|
|
471
|
+
for (const el of prop.value.elements) {
|
|
472
|
+
if (el && el.type === 'ObjectExpression') {
|
|
473
|
+
validateCriteriaAttributes(
|
|
474
|
+
el,
|
|
475
|
+
model,
|
|
476
|
+
document,
|
|
477
|
+
diagnostics,
|
|
478
|
+
effectiveModelName
|
|
347
479
|
)
|
|
348
480
|
}
|
|
349
481
|
}
|
|
@@ -19,12 +19,26 @@ module.exports = function validateModelExist(document, typeMap) {
|
|
|
19
19
|
if (!name) return false
|
|
20
20
|
return !!modelMap[name.toLowerCase()]
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
const knownGlobals = [
|
|
24
|
+
'_',
|
|
25
|
+
'sails',
|
|
26
|
+
'require',
|
|
27
|
+
'module',
|
|
28
|
+
'exports',
|
|
29
|
+
'console',
|
|
30
|
+
'process'
|
|
31
|
+
]
|
|
32
|
+
|
|
22
33
|
// User.find() or User.create() etc
|
|
23
34
|
const modelCallRegex =
|
|
24
35
|
/\b([A-Za-z0-9_]+)\s*\.(?:find|findOne|create|createEach|update|destroy|count|sum|where|findOrCreate)\s*\(/g
|
|
25
36
|
let match
|
|
26
37
|
while ((match = modelCallRegex.exec(text)) !== null) {
|
|
27
38
|
const modelName = match[1]
|
|
39
|
+
if (knownGlobals.includes(modelName)) {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
28
42
|
if (!modelExists(modelName)) {
|
|
29
43
|
diagnostics.push(
|
|
30
44
|
lsp.Diagnostic.create(
|