@sailshq/language-server 0.0.5 → 0.1.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 +403 -0
- package/completions/actions-completion.js +36 -0
- package/completions/data-types-completion.js +38 -0
- package/completions/inertia-pages-completion.js +33 -0
- package/completions/input-props-completion.js +56 -0
- package/completions/model-attribute-props-completion.js +53 -0
- package/completions/model-attributes-completion.js +102 -0
- package/completions/model-methods-completion.js +61 -0
- package/completions/models-completion.js +52 -0
- package/completions/policies-completion.js +32 -0
- package/completions/views-completion.js +35 -0
- package/go-to-definitions/go-to-action.js +26 -49
- package/go-to-definitions/go-to-helper.js +33 -46
- package/go-to-definitions/go-to-model.js +39 -0
- package/go-to-definitions/go-to-page.js +38 -0
- package/go-to-definitions/go-to-policy.js +23 -72
- package/go-to-definitions/go-to-view.js +28 -55
- package/index.js +95 -20
- package/package.json +1 -1
- package/validators/validate-action-exist.js +28 -51
- package/validators/validate-data-type.js +34 -0
- package/validators/validate-document.js +21 -5
- package/validators/validate-model-attribute-exist.js +128 -0
- package/validators/validate-page-exist.js +42 -0
- package/validators/validate-policy-exist.js +45 -0
- package/completions/sails-completions.js +0 -63
- package/go-to-definitions/go-to-inertia-page.js +0 -53
- package/helpers/find-fn-line.js +0 -21
- package/helpers/find-project-root.js +0 -18
- package/helpers/find-sails.js +0 -12
- package/helpers/load-sails.js +0 -39
|
@@ -1,70 +1,43 @@
|
|
|
1
1
|
const lsp = require('vscode-languageserver/node')
|
|
2
2
|
const path = require('path')
|
|
3
|
-
const fs = require('fs').promises
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const viewInfo = extractViewInfo(document, position)
|
|
9
|
-
|
|
10
|
-
if (!viewInfo) {
|
|
11
|
-
return null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const projectRoot = await findProjectRoot(document.uri)
|
|
15
|
-
|
|
16
|
-
const fullViewPath = resolveViewPath(projectRoot, viewInfo.view)
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
await fs.access(fullViewPath)
|
|
20
|
-
return lsp.Location.create(fullViewPath, lsp.Range.create(0, 0, 0, 0))
|
|
21
|
-
} catch (error) {
|
|
22
|
-
return null
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function resolveViewPath(projectRoot, viewPath) {
|
|
27
|
-
return path.join(projectRoot, 'views', `${viewPath}.ejs`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function extractViewInfo(document, position) {
|
|
4
|
+
module.exports = async function goToView(document, position, typeMap) {
|
|
5
|
+
const fileName = path.basename(document.uri)
|
|
6
|
+
const filePath = document.uri
|
|
31
7
|
const text = document.getText()
|
|
32
8
|
const offset = document.offsetAt(position)
|
|
33
9
|
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
/(?:(['"])(.+?)\1\s*:\s*{\s*view\s*:\s*(['"])(.+?)\3\s*}|viewTemplatePath\s*:\s*(['"])(.+?)\5)/g
|
|
37
|
-
let match
|
|
10
|
+
const isRoutes = fileName === 'routes.js'
|
|
11
|
+
const isController = filePath.includes('/api/controllers/')
|
|
38
12
|
|
|
39
|
-
|
|
40
|
-
const [fullMatch, , , , viewInObject, , viewInController] = match
|
|
41
|
-
const view = viewInObject || viewInController
|
|
42
|
-
const start = match.index
|
|
43
|
-
const end = start + fullMatch.length
|
|
13
|
+
if (!isRoutes && !isController) return null
|
|
44
14
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// Find the start and end positions of the view part, including quotes
|
|
48
|
-
const viewStartWithQuote = text.lastIndexOf(
|
|
49
|
-
"'",
|
|
50
|
-
text.indexOf(view, start)
|
|
51
|
-
) // Find the opening quote
|
|
52
|
-
const viewEndWithQuote =
|
|
53
|
-
text.indexOf("'", text.indexOf(view, start) + view.length) + 1 // Find the closing quote and include it
|
|
15
|
+
const regex =
|
|
16
|
+
/\b(viewTemplatePath|view)\s*:\s*(?<quote>['"])(?<view>[^'"]+)\k<quote>/g
|
|
54
17
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
18
|
+
let match
|
|
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
|
|
25
|
+
|
|
26
|
+
if (offset >= fullMatchStart && offset <= fullMatchEnd) {
|
|
27
|
+
const viewPath = typeMap.views?.[viewName]
|
|
28
|
+
if (viewPath) {
|
|
29
|
+
const uri = `file://${viewPath.path}`
|
|
30
|
+
return lsp.LocationLink.create(
|
|
31
|
+
uri,
|
|
32
|
+
lsp.Range.create(0, 0, 0, 0),
|
|
33
|
+
lsp.Range.create(0, 0, 0, 0),
|
|
34
|
+
lsp.Range.create(
|
|
35
|
+
document.positionAt(fullMatchStart),
|
|
36
|
+
document.positionAt(fullMatchEnd)
|
|
37
|
+
)
|
|
60
38
|
)
|
|
61
39
|
}
|
|
62
40
|
}
|
|
63
41
|
}
|
|
64
|
-
|
|
65
42
|
return null
|
|
66
43
|
}
|
|
67
|
-
|
|
68
|
-
function resolveViewPath(projectRoot, viewPath) {
|
|
69
|
-
return path.join(projectRoot, 'views', `${viewPath}.ejs`)
|
|
70
|
-
}
|
package/index.js
CHANGED
|
@@ -1,40 +1,71 @@
|
|
|
1
1
|
const lsp = require('vscode-languageserver/node')
|
|
2
2
|
const TextDocument = require('vscode-languageserver-textdocument').TextDocument
|
|
3
|
+
const SailsParser = require('./SailsParser')
|
|
3
4
|
|
|
4
5
|
// Validators
|
|
5
6
|
const validateDocument = require('./validators/validate-document')
|
|
6
7
|
|
|
7
8
|
// Go-to definitions
|
|
8
9
|
const goToAction = require('./go-to-definitions/go-to-action')
|
|
9
|
-
const goToPolicy = require('./go-to-definitions/go-to-policy')
|
|
10
10
|
const goToView = require('./go-to-definitions/go-to-view')
|
|
11
|
-
const
|
|
11
|
+
const goToPage = require('./go-to-definitions/go-to-page')
|
|
12
|
+
const goToPolicy = require('./go-to-definitions/go-to-policy')
|
|
12
13
|
const goToHelper = require('./go-to-definitions/go-to-helper')
|
|
14
|
+
const goToModel = require('./go-to-definitions/go-to-model')
|
|
13
15
|
|
|
14
16
|
// Completions
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
+
const actionsCompletion = require('./completions/actions-completion')
|
|
18
|
+
const dataTypesCompletion = require('./completions/data-types-completion')
|
|
19
|
+
const modelAttributePropsCompletion = require('./completions/model-attribute-props-completion')
|
|
20
|
+
const inputPropsCompletion = require('./completions/input-props-completion')
|
|
21
|
+
const inertiaPagesCompletion = require('./completions/inertia-pages-completion')
|
|
22
|
+
const modelsCompletion = require('./completions/models-completion')
|
|
23
|
+
const policiesCompletion = require('./completions/policies-completion')
|
|
24
|
+
const viewsCompletion = require('./completions/views-completion')
|
|
25
|
+
const modelMethodsCompletion = require('./completions/model-methods-completion')
|
|
26
|
+
const modelAttributesCompletion = require('./completions/model-attributes-completion')
|
|
17
27
|
const connection = lsp.createConnection(lsp.ProposedFeatures.all)
|
|
18
28
|
const documents = new lsp.TextDocuments(TextDocument)
|
|
19
29
|
|
|
20
|
-
|
|
30
|
+
// Create a new SailsParser instance
|
|
31
|
+
const sailsParser = new SailsParser()
|
|
32
|
+
let typeMap
|
|
33
|
+
|
|
34
|
+
connection.onInitialize(async (params) => {
|
|
35
|
+
const rootPath = params.workspaceFolders?.[0]?.uri
|
|
36
|
+
? new URL(params.workspaceFolders[0].uri).pathname
|
|
37
|
+
: undefined
|
|
38
|
+
|
|
39
|
+
sailsParser.setRootDir(rootPath)
|
|
40
|
+
typeMap = await sailsParser.buildTypeMap()
|
|
41
|
+
|
|
21
42
|
return {
|
|
22
43
|
capabilities: {
|
|
23
44
|
textDocumentSync: lsp.TextDocumentSyncKind.Incremental,
|
|
24
45
|
definitionProvider: true,
|
|
25
46
|
completionProvider: {
|
|
26
|
-
triggerCharacters: ['"', "'", '.']
|
|
47
|
+
triggerCharacters: ['"', "'", '.', '{', ',', ' ', '\n']
|
|
27
48
|
}
|
|
28
49
|
}
|
|
29
50
|
}
|
|
30
51
|
})
|
|
31
52
|
|
|
32
53
|
documents.onDidOpen((open) => {
|
|
33
|
-
|
|
54
|
+
if (typeMap) {
|
|
55
|
+
validateDocument(connection, open.document, typeMap)
|
|
56
|
+
}
|
|
34
57
|
})
|
|
35
58
|
|
|
36
|
-
documents.onDidChangeContent((change) => {
|
|
37
|
-
|
|
59
|
+
documents.onDidChangeContent(async (change) => {
|
|
60
|
+
const documentUri = change.document.uri
|
|
61
|
+
if (documentUri.includes('api/') || documentUri.includes('config')) {
|
|
62
|
+
typeMap = await sailsParser.buildTypeMap()
|
|
63
|
+
connection.console.log('Type map updated due to file change.')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeMap) {
|
|
67
|
+
validateDocument(connection, change.document, typeMap)
|
|
68
|
+
}
|
|
38
69
|
})
|
|
39
70
|
|
|
40
71
|
connection.onDefinition(async (params) => {
|
|
@@ -43,20 +74,30 @@ connection.onDefinition(async (params) => {
|
|
|
43
74
|
return null
|
|
44
75
|
}
|
|
45
76
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
const [
|
|
78
|
+
actionDefinition,
|
|
79
|
+
viewDefinition,
|
|
80
|
+
pageDefinition,
|
|
81
|
+
policyDefinition,
|
|
82
|
+
helperDefinition,
|
|
83
|
+
modelDefinition
|
|
84
|
+
] = await Promise.all([
|
|
85
|
+
goToAction(document, params.position, typeMap),
|
|
86
|
+
goToView(document, params.position, typeMap),
|
|
87
|
+
goToPage(document, params.position, typeMap),
|
|
88
|
+
goToPolicy(document, params.position, typeMap),
|
|
89
|
+
goToHelper(document, params.position, typeMap),
|
|
90
|
+
goToModel(document, params.position, typeMap)
|
|
91
|
+
])
|
|
51
92
|
|
|
52
93
|
const definitions = [
|
|
53
94
|
actionDefinition,
|
|
54
|
-
policyDefinition,
|
|
55
95
|
viewDefinition,
|
|
56
|
-
|
|
57
|
-
|
|
96
|
+
pageDefinition,
|
|
97
|
+
policyDefinition,
|
|
98
|
+
helperDefinition,
|
|
99
|
+
modelDefinition
|
|
58
100
|
].filter(Boolean)
|
|
59
|
-
|
|
60
101
|
return definitions.length > 0 ? definitions : null
|
|
61
102
|
})
|
|
62
103
|
|
|
@@ -65,8 +106,42 @@ connection.onCompletion(async (params) => {
|
|
|
65
106
|
if (!document) {
|
|
66
107
|
return null
|
|
67
108
|
}
|
|
68
|
-
|
|
69
|
-
|
|
109
|
+
const [
|
|
110
|
+
actionCompletion,
|
|
111
|
+
dataTypeCompletion,
|
|
112
|
+
modelAttributePropCompletion,
|
|
113
|
+
inputPropCompletion,
|
|
114
|
+
inertiaPageCompletion,
|
|
115
|
+
modelCompletion,
|
|
116
|
+
policyCompletion,
|
|
117
|
+
viewCompletion,
|
|
118
|
+
modelMethodCompletion,
|
|
119
|
+
modelAttributeCompletion
|
|
120
|
+
] = await Promise.all([
|
|
121
|
+
actionsCompletion(document, params.position, typeMap),
|
|
122
|
+
dataTypesCompletion(document, params.position, typeMap),
|
|
123
|
+
modelAttributePropsCompletion(document, params.position, typeMap),
|
|
124
|
+
inputPropsCompletion(document, params.position, typeMap),
|
|
125
|
+
inertiaPagesCompletion(document, params.position, typeMap),
|
|
126
|
+
modelsCompletion(document, params.position, typeMap),
|
|
127
|
+
policiesCompletion(document, params.position, typeMap),
|
|
128
|
+
viewsCompletion(document, params.position, typeMap),
|
|
129
|
+
modelMethodsCompletion(document, params.position, typeMap),
|
|
130
|
+
modelAttributesCompletion(document, params.position, typeMap)
|
|
131
|
+
])
|
|
132
|
+
|
|
133
|
+
const completions = [
|
|
134
|
+
...actionCompletion,
|
|
135
|
+
...dataTypeCompletion,
|
|
136
|
+
...modelAttributePropCompletion,
|
|
137
|
+
...inputPropCompletion,
|
|
138
|
+
...inertiaPageCompletion,
|
|
139
|
+
...modelCompletion,
|
|
140
|
+
...policyCompletion,
|
|
141
|
+
...viewCompletion,
|
|
142
|
+
...modelMethodCompletion,
|
|
143
|
+
...modelAttributeCompletion
|
|
144
|
+
].filter(Boolean)
|
|
70
145
|
|
|
71
146
|
if (completions) {
|
|
72
147
|
return {
|
package/package.json
CHANGED
|
@@ -1,55 +1,40 @@
|
|
|
1
1
|
const lsp = require('vscode-languageserver/node')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const url = require('url')
|
|
5
|
-
module.exports = function validateActionExist(document) {
|
|
6
|
-
const diagnostics = []
|
|
7
|
-
|
|
8
|
-
if (!document.uri.endsWith('routes.js')) {
|
|
9
|
-
return diagnostics // Return empty diagnostics if not routes.js
|
|
10
|
-
}
|
|
11
|
-
const actionInfo = extractActionInfo(document)
|
|
12
2
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
3
|
+
module.exports = function validateActionExist(document, typeMap) {
|
|
4
|
+
const diagnostics = []
|
|
16
5
|
|
|
17
|
-
|
|
18
|
-
const actions = extractActionInfo(document)
|
|
6
|
+
if (!document.uri.endsWith('routes.js')) return diagnostics
|
|
7
|
+
const actions = extractActionInfo(document)
|
|
19
8
|
|
|
20
9
|
for (const { action, range } of actions) {
|
|
21
|
-
if (isUrlOrRedirect(action))
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (!
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
10
|
+
if (isUrlOrRedirect(action)) continue
|
|
11
|
+
const routeExists = Object.values(typeMap.routes || {}).some(
|
|
12
|
+
(route) => route.action?.name === action
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if (!routeExists) {
|
|
16
|
+
diagnostics.push(
|
|
17
|
+
lsp.Diagnostic.create(
|
|
18
|
+
range,
|
|
19
|
+
`'${action}' action does not exist. Please check the name or create it.`,
|
|
20
|
+
lsp.DiagnosticSeverity.Error,
|
|
21
|
+
'sails-lsp'
|
|
22
|
+
)
|
|
23
|
+
)
|
|
34
24
|
}
|
|
35
25
|
}
|
|
36
|
-
|
|
37
26
|
return diagnostics
|
|
38
27
|
}
|
|
39
28
|
|
|
40
29
|
function extractActionInfo(document) {
|
|
41
30
|
const text = document.getText()
|
|
42
|
-
|
|
43
|
-
// This regex matches both object and string notations
|
|
44
|
-
const regex = /(['"])(.+?)\1:\s*(?:{?\s*action\s*:\s*)?(['"])(.+?)\3/g
|
|
45
|
-
let match
|
|
31
|
+
const regex = /(['"])(.+?)\1\s*:\s*(?:{?\s*action\s*:\s*)?(['"])(.+?)\3/g
|
|
46
32
|
const actions = []
|
|
33
|
+
let match
|
|
47
34
|
|
|
48
35
|
while ((match = regex.exec(text)) !== null) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
// Store the action and its range
|
|
52
|
-
const actionStart = match.index + fullMatch.indexOf(action)
|
|
36
|
+
const action = match[4]
|
|
37
|
+
const actionStart = match.index + match[0].lastIndexOf(action)
|
|
53
38
|
const actionEnd = actionStart + action.length
|
|
54
39
|
|
|
55
40
|
actions.push({
|
|
@@ -61,21 +46,13 @@ function extractActionInfo(document) {
|
|
|
61
46
|
})
|
|
62
47
|
}
|
|
63
48
|
|
|
64
|
-
return actions
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function resolveActionPath(projectRoot, actionPath) {
|
|
68
|
-
return path.join(projectRoot, 'api', 'controllers', `${actionPath}.js`)
|
|
49
|
+
return actions
|
|
69
50
|
}
|
|
70
51
|
|
|
71
52
|
function isUrlOrRedirect(action) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return true
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return false
|
|
53
|
+
return (
|
|
54
|
+
action.startsWith('http://') ||
|
|
55
|
+
action.startsWith('https://') ||
|
|
56
|
+
action.startsWith('/')
|
|
57
|
+
)
|
|
81
58
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
|
|
3
|
+
module.exports = function validateDataType(document, typeMap) {
|
|
4
|
+
const diagnostics = []
|
|
5
|
+
|
|
6
|
+
const text = document.getText()
|
|
7
|
+
|
|
8
|
+
// Regex to match lines like: type: 'string' or type: "number"
|
|
9
|
+
const regex = /type\s*:\s*['"]([a-zA-Z0-9_-]+)['"]/g
|
|
10
|
+
|
|
11
|
+
let match
|
|
12
|
+
while ((match = regex.exec(text)) !== null) {
|
|
13
|
+
const dataType = match[1]
|
|
14
|
+
const typeStart = match.index + match[0].indexOf(dataType)
|
|
15
|
+
const typeEnd = typeStart + dataType.length
|
|
16
|
+
|
|
17
|
+
const isValid = typeMap.dataTypes.some((dt) => dt.type === dataType)
|
|
18
|
+
|
|
19
|
+
if (!isValid) {
|
|
20
|
+
diagnostics.push(
|
|
21
|
+
lsp.Diagnostic.create(
|
|
22
|
+
lsp.Range.create(
|
|
23
|
+
document.positionAt(typeStart),
|
|
24
|
+
document.positionAt(typeEnd)
|
|
25
|
+
),
|
|
26
|
+
`'${dataType}' is not a recognized data type. Valid data types are: ${typeMap.dataTypes.map((dataType) => dataType.type).join(', ')}.`,
|
|
27
|
+
lsp.DiagnosticSeverity.Error,
|
|
28
|
+
'sails-lsp'
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return diagnostics
|
|
34
|
+
}
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
const validateAutomigrationStrategy = require('./validate-auto-migration-strategy')
|
|
2
2
|
const validateActionExist = require('./validate-action-exist')
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const validatePageExist = require('./validate-page-exist')
|
|
4
|
+
const validateDataTypes = require('./validate-data-type')
|
|
5
|
+
const validatePolicyExist = require('./validate-policy-exist')
|
|
6
|
+
const validateModelAttributeExist = require('./validate-model-attribute-exist')
|
|
7
|
+
module.exports = function validateDocument(connection, document, typeMap) {
|
|
5
8
|
const diagnostics = []
|
|
6
9
|
|
|
7
10
|
const modelDiagnostics = validateAutomigrationStrategy(document)
|
|
8
|
-
const actionDiagnostics = validateActionExist(document)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
const actionDiagnostics = validateActionExist(document, typeMap)
|
|
12
|
+
const pageDiagnostics = validatePageExist(document, typeMap)
|
|
13
|
+
const dataTypeDiagnostics = validateDataTypes(document, typeMap)
|
|
14
|
+
const policyDiagnostics = validatePolicyExist(document, typeMap)
|
|
15
|
+
const modelAttributeDiagnostics = validateModelAttributeExist(
|
|
16
|
+
document,
|
|
17
|
+
typeMap
|
|
18
|
+
)
|
|
19
|
+
diagnostics.push(
|
|
20
|
+
...modelDiagnostics,
|
|
21
|
+
...actionDiagnostics,
|
|
22
|
+
...pageDiagnostics,
|
|
23
|
+
...dataTypeDiagnostics,
|
|
24
|
+
...policyDiagnostics,
|
|
25
|
+
...modelAttributeDiagnostics
|
|
26
|
+
)
|
|
11
27
|
|
|
12
28
|
connection.sendDiagnostics({ uri: document.uri, diagnostics })
|
|
13
29
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate if a Waterline model attribute exists when used in criteria or chainable methods.
|
|
5
|
+
* @param {TextDocument} document - The text document to validate.
|
|
6
|
+
* @param {Object} typeMap - The type map containing models and their attributes.
|
|
7
|
+
* @returns {Array} diagnostics - Array of LSP diagnostics.
|
|
8
|
+
*/
|
|
9
|
+
module.exports = function validateModelAttributeExist(document, typeMap) {
|
|
10
|
+
const diagnostics = []
|
|
11
|
+
const text = document.getText()
|
|
12
|
+
|
|
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
|
+
)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
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
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
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
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return diagnostics
|
|
128
|
+
}
|
|
@@ -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
|
+
}
|