@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.
Files changed (31) hide show
  1. package/SailsParser.js +403 -0
  2. package/completions/actions-completion.js +36 -0
  3. package/completions/data-types-completion.js +38 -0
  4. package/completions/inertia-pages-completion.js +33 -0
  5. package/completions/input-props-completion.js +56 -0
  6. package/completions/model-attribute-props-completion.js +53 -0
  7. package/completions/model-attributes-completion.js +102 -0
  8. package/completions/model-methods-completion.js +61 -0
  9. package/completions/models-completion.js +52 -0
  10. package/completions/policies-completion.js +32 -0
  11. package/completions/views-completion.js +35 -0
  12. package/go-to-definitions/go-to-action.js +26 -49
  13. package/go-to-definitions/go-to-helper.js +33 -46
  14. package/go-to-definitions/go-to-model.js +39 -0
  15. package/go-to-definitions/go-to-page.js +38 -0
  16. package/go-to-definitions/go-to-policy.js +23 -72
  17. package/go-to-definitions/go-to-view.js +28 -55
  18. package/index.js +95 -20
  19. package/package.json +1 -1
  20. package/validators/validate-action-exist.js +28 -51
  21. package/validators/validate-data-type.js +34 -0
  22. package/validators/validate-document.js +21 -5
  23. package/validators/validate-model-attribute-exist.js +128 -0
  24. package/validators/validate-page-exist.js +42 -0
  25. package/validators/validate-policy-exist.js +45 -0
  26. package/completions/sails-completions.js +0 -63
  27. package/go-to-definitions/go-to-inertia-page.js +0 -53
  28. package/helpers/find-fn-line.js +0 -21
  29. package/helpers/find-project-root.js +0 -18
  30. package/helpers/find-sails.js +0 -12
  31. 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
- const findProjectRoot = require('../helpers/find-project-root')
6
-
7
- module.exports = async function goToView(document, position) {
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
- // This regex matches both object notation for views and viewTemplatePath
35
- const regex =
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
- while ((match = regex.exec(text)) !== null) {
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
- // Check if the cursor is anywhere within the entire match
46
- if (start <= offset && offset <= end) {
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
- return {
56
- view,
57
- range: lsp.Range.create(
58
- document.positionAt(viewStartWithQuote),
59
- document.positionAt(viewEndWithQuote)
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 goToInertiaPage = require('./go-to-definitions/go-to-inertia-page')
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 sailsCompletions = require('./completions/sails-completions')
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
- connection.onInitialize((params) => {
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
- validateDocument(connection, open.document)
54
+ if (typeMap) {
55
+ validateDocument(connection, open.document, typeMap)
56
+ }
34
57
  })
35
58
 
36
- documents.onDidChangeContent((change) => {
37
- validateDocument(connection, change.document)
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 actionDefinition = await goToAction(document, params.position)
47
- const policyDefinition = await goToPolicy(document, params.position)
48
- const viewDefinition = await goToView(document, params.position)
49
- const inertiaPageDefinition = await goToInertiaPage(document, params.position)
50
- const helperDefinition = await goToHelper(document, params.position)
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
- inertiaPageDefinition,
57
- helperDefinition
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
- const completions = await sailsCompletions(document, params.position)
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailshq/language-server",
3
- "version": "0.0.5",
3
+ "version": "0.1.0",
4
4
  "description": "Language Server Protocol server for Sails Language Service",
5
5
  "homepage": "https://sailjs.com",
6
6
  "repository": {
@@ -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
- if (!actionInfo) {
14
- return diagnostics
15
- }
3
+ module.exports = function validateActionExist(document, typeMap) {
4
+ const diagnostics = []
16
5
 
17
- const projectRoot = path.dirname(path.dirname(document.uri))
18
- const actions = extractActionInfo(document) // Get all actions
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
- continue
23
- }
24
-
25
- const fullActionPath = resolveActionPath(projectRoot, action)
26
- if (!fs.existsSync(url.fileURLToPath(fullActionPath))) {
27
- const diagnostic = {
28
- severity: lsp.DiagnosticSeverity.Error,
29
- range,
30
- message: `Action '${action}' does not exist. Please check the controller file.`,
31
- source: 'Sails Validator'
32
- }
33
- diagnostics.push(diagnostic)
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 [fullMatch, , route, , action] = match
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 // Return an array of 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
- if (action.startsWith('http://') || action.startsWith('https://')) {
73
- return true
74
- }
75
-
76
- if (action.startsWith('/')) {
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
- module.exports = function validateDocument(connection, document) {
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
- diagnostics.push(...modelDiagnostics, ...actionDiagnostics)
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
+ }