@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.
- package/SailsParser.js +652 -0
- package/completions/actions-completion.js +36 -0
- package/completions/data-types-completion.js +39 -0
- package/completions/helper-inputs-completion.js +91 -0
- package/completions/helpers-completion.js +85 -0
- package/completions/inertia-pages-completion.js +33 -0
- package/completions/input-props-completion.js +52 -0
- package/completions/model-attribute-props-completion.js +57 -0
- package/completions/model-attributes-completion.js +195 -0
- package/completions/model-methods-completion.js +71 -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 +37 -45
- 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 +103 -19
- 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 +42 -4
- package/validators/validate-helper-input-exist.js +42 -0
- package/validators/validate-model-attribute-exist.js +297 -0
- package/validators/validate-model-exist.js +64 -0
- package/validators/validate-page-exist.js +42 -0
- package/validators/validate-policy-exist.js +45 -0
- package/validators/validate-required-helper-input.js +49 -0
- package/validators/validate-required-model-attribute.js +56 -0
- package/validators/validate-view-exist.js +86 -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
package/index.js
CHANGED
|
@@ -1,40 +1,74 @@
|
|
|
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
|
|
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')
|
|
27
|
+
const helperInputsCompletion = require('./completions/helper-inputs-completion')
|
|
28
|
+
const helpersCompletion = require('./completions/helpers-completion')
|
|
16
29
|
|
|
17
30
|
const connection = lsp.createConnection(lsp.ProposedFeatures.all)
|
|
18
31
|
const documents = new lsp.TextDocuments(TextDocument)
|
|
19
32
|
|
|
20
|
-
|
|
33
|
+
// Create a new SailsParser instance
|
|
34
|
+
const sailsParser = new SailsParser()
|
|
35
|
+
let typeMap
|
|
36
|
+
|
|
37
|
+
connection.onInitialize(async (params) => {
|
|
38
|
+
const rootPath = params.workspaceFolders?.[0]?.uri
|
|
39
|
+
? new URL(params.workspaceFolders[0].uri).pathname
|
|
40
|
+
: undefined
|
|
41
|
+
|
|
42
|
+
sailsParser.setRootDir(rootPath)
|
|
43
|
+
typeMap = await sailsParser.buildTypeMap()
|
|
44
|
+
|
|
21
45
|
return {
|
|
22
46
|
capabilities: {
|
|
23
47
|
textDocumentSync: lsp.TextDocumentSyncKind.Incremental,
|
|
24
48
|
definitionProvider: true,
|
|
25
49
|
completionProvider: {
|
|
26
|
-
triggerCharacters: ['"', "'", '.']
|
|
50
|
+
triggerCharacters: ['"', "'", '.', '{', ',', ' ', '\n']
|
|
27
51
|
}
|
|
28
52
|
}
|
|
29
53
|
}
|
|
30
54
|
})
|
|
31
55
|
|
|
32
56
|
documents.onDidOpen((open) => {
|
|
33
|
-
|
|
57
|
+
if (typeMap) {
|
|
58
|
+
validateDocument(connection, open.document, typeMap)
|
|
59
|
+
}
|
|
34
60
|
})
|
|
35
61
|
|
|
36
|
-
documents.onDidChangeContent((change) => {
|
|
37
|
-
|
|
62
|
+
documents.onDidChangeContent(async (change) => {
|
|
63
|
+
const documentUri = change.document.uri
|
|
64
|
+
if (documentUri.includes('api/') || documentUri.includes('config')) {
|
|
65
|
+
typeMap = await sailsParser.buildTypeMap()
|
|
66
|
+
connection.console.log('Type map updated due to file change.')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeMap) {
|
|
70
|
+
validateDocument(connection, change.document, typeMap)
|
|
71
|
+
}
|
|
38
72
|
})
|
|
39
73
|
|
|
40
74
|
connection.onDefinition(async (params) => {
|
|
@@ -43,20 +77,30 @@ connection.onDefinition(async (params) => {
|
|
|
43
77
|
return null
|
|
44
78
|
}
|
|
45
79
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
80
|
+
const [
|
|
81
|
+
actionDefinition,
|
|
82
|
+
viewDefinition,
|
|
83
|
+
pageDefinition,
|
|
84
|
+
policyDefinition,
|
|
85
|
+
helperDefinition,
|
|
86
|
+
modelDefinition
|
|
87
|
+
] = await Promise.all([
|
|
88
|
+
goToAction(document, params.position, typeMap),
|
|
89
|
+
goToView(document, params.position, typeMap),
|
|
90
|
+
goToPage(document, params.position, typeMap),
|
|
91
|
+
goToPolicy(document, params.position, typeMap),
|
|
92
|
+
goToHelper(document, params.position, typeMap),
|
|
93
|
+
goToModel(document, params.position, typeMap)
|
|
94
|
+
])
|
|
51
95
|
|
|
52
96
|
const definitions = [
|
|
53
97
|
actionDefinition,
|
|
54
|
-
policyDefinition,
|
|
55
98
|
viewDefinition,
|
|
56
|
-
|
|
57
|
-
|
|
99
|
+
pageDefinition,
|
|
100
|
+
policyDefinition,
|
|
101
|
+
helperDefinition,
|
|
102
|
+
modelDefinition
|
|
58
103
|
].filter(Boolean)
|
|
59
|
-
|
|
60
104
|
return definitions.length > 0 ? definitions : null
|
|
61
105
|
})
|
|
62
106
|
|
|
@@ -65,8 +109,48 @@ connection.onCompletion(async (params) => {
|
|
|
65
109
|
if (!document) {
|
|
66
110
|
return null
|
|
67
111
|
}
|
|
68
|
-
|
|
69
|
-
|
|
112
|
+
const [
|
|
113
|
+
actionCompletion,
|
|
114
|
+
dataTypeCompletion,
|
|
115
|
+
modelAttributePropCompletion,
|
|
116
|
+
inputPropCompletion,
|
|
117
|
+
inertiaPageCompletion,
|
|
118
|
+
modelCompletion,
|
|
119
|
+
policyCompletion,
|
|
120
|
+
viewCompletion,
|
|
121
|
+
modelMethodCompletion,
|
|
122
|
+
modelAttributeCompletion,
|
|
123
|
+
helperCompletion,
|
|
124
|
+
helperInputCompletion
|
|
125
|
+
] = await Promise.all([
|
|
126
|
+
actionsCompletion(document, params.position, typeMap),
|
|
127
|
+
dataTypesCompletion(document, params.position, typeMap),
|
|
128
|
+
modelAttributePropsCompletion(document, params.position, typeMap),
|
|
129
|
+
inputPropsCompletion(document, params.position, typeMap),
|
|
130
|
+
inertiaPagesCompletion(document, params.position, typeMap),
|
|
131
|
+
modelsCompletion(document, params.position, typeMap),
|
|
132
|
+
policiesCompletion(document, params.position, typeMap),
|
|
133
|
+
viewsCompletion(document, params.position, typeMap),
|
|
134
|
+
modelMethodsCompletion(document, params.position, typeMap),
|
|
135
|
+
modelAttributesCompletion(document, params.position, typeMap),
|
|
136
|
+
helpersCompletion(document, params.position, typeMap),
|
|
137
|
+
helperInputsCompletion(document, params.position, typeMap)
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
const completions = [
|
|
141
|
+
...actionCompletion,
|
|
142
|
+
...dataTypeCompletion,
|
|
143
|
+
...modelAttributePropCompletion,
|
|
144
|
+
...inputPropCompletion,
|
|
145
|
+
...inertiaPageCompletion,
|
|
146
|
+
...modelCompletion,
|
|
147
|
+
...policyCompletion,
|
|
148
|
+
...viewCompletion,
|
|
149
|
+
...modelMethodCompletion,
|
|
150
|
+
...modelAttributeCompletion,
|
|
151
|
+
...helperCompletion,
|
|
152
|
+
...helperInputCompletion
|
|
153
|
+
].filter(Boolean)
|
|
70
154
|
|
|
71
155
|
if (completions) {
|
|
72
156
|
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,51 @@
|
|
|
1
1
|
const validateAutomigrationStrategy = require('./validate-auto-migration-strategy')
|
|
2
2
|
const validateActionExist = require('./validate-action-exist')
|
|
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
|
+
const validateViewExist = require('./validate-view-exist')
|
|
8
|
+
const validateHelperInputExist = require('./validate-helper-input-exist')
|
|
9
|
+
const validateRequiredHelperInput = require('./validate-required-helper-input')
|
|
10
|
+
const validateRequiredModelAttribute = require('./validate-required-model-attribute')
|
|
11
|
+
const validateModelExist = require('./validate-model-exist')
|
|
3
12
|
|
|
4
|
-
module.exports = function validateDocument(connection, document) {
|
|
13
|
+
module.exports = function validateDocument(connection, document, typeMap) {
|
|
5
14
|
const diagnostics = []
|
|
6
15
|
|
|
7
16
|
const modelDiagnostics = validateAutomigrationStrategy(document)
|
|
8
|
-
const actionDiagnostics = validateActionExist(document)
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
const actionDiagnostics = validateActionExist(document, typeMap)
|
|
18
|
+
const pageDiagnostics = validatePageExist(document, typeMap)
|
|
19
|
+
const dataTypeDiagnostics = validateDataTypes(document, typeMap)
|
|
20
|
+
const policyDiagnostics = validatePolicyExist(document, typeMap)
|
|
21
|
+
const modelAttributeDiagnostics = validateModelAttributeExist(
|
|
22
|
+
document,
|
|
23
|
+
typeMap
|
|
24
|
+
)
|
|
25
|
+
const viewDiagnostics = validateViewExist(document, typeMap)
|
|
26
|
+
const helperInputDiagnostics = validateHelperInputExist(document, typeMap)
|
|
27
|
+
const requiredHelperInputDiagnostics = validateRequiredHelperInput(
|
|
28
|
+
document,
|
|
29
|
+
typeMap
|
|
30
|
+
)
|
|
31
|
+
const requiredModelAttributeDiagnostics = validateRequiredModelAttribute(
|
|
32
|
+
document,
|
|
33
|
+
typeMap
|
|
34
|
+
)
|
|
35
|
+
const modelExistDiagnostics = validateModelExist(document, typeMap)
|
|
36
|
+
diagnostics.push(
|
|
37
|
+
...modelDiagnostics,
|
|
38
|
+
...actionDiagnostics,
|
|
39
|
+
...pageDiagnostics,
|
|
40
|
+
...dataTypeDiagnostics,
|
|
41
|
+
...policyDiagnostics,
|
|
42
|
+
...modelAttributeDiagnostics,
|
|
43
|
+
...viewDiagnostics,
|
|
44
|
+
...helperInputDiagnostics,
|
|
45
|
+
...requiredHelperInputDiagnostics,
|
|
46
|
+
...requiredModelAttributeDiagnostics,
|
|
47
|
+
...modelExistDiagnostics
|
|
48
|
+
)
|
|
11
49
|
|
|
12
50
|
connection.sendDiagnostics({ uri: document.uri, diagnostics })
|
|
13
51
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
|
|
3
|
+
module.exports = function validateHelperInputExist(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
|
+
while ((propMatch = propsRegex.exec(match[2])) !== null) {
|
|
23
|
+
const key = propMatch[1]
|
|
24
|
+
if (!Object.prototype.hasOwnProperty.call(helperInfo.inputs, key)) {
|
|
25
|
+
const start = match.index + match[0].indexOf(key)
|
|
26
|
+
const end = start + key.length
|
|
27
|
+
diagnostics.push(
|
|
28
|
+
lsp.Diagnostic.create(
|
|
29
|
+
lsp.Range.create(
|
|
30
|
+
document.positionAt(start),
|
|
31
|
+
document.positionAt(end)
|
|
32
|
+
),
|
|
33
|
+
`Unknown input property '${key}' for helper '${fullHelperName}'.`,
|
|
34
|
+
lsp.DiagnosticSeverity.Error,
|
|
35
|
+
'sails-lsp'
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return diagnostics
|
|
42
|
+
}
|