@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.
@@ -1,49 +1,110 @@
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 validateRequiredHelperInput(document, typeMap) {
4
6
  const diagnostics = []
5
7
  const text = document.getText()
6
8
 
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
- )
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
+ // Collect provided keys (handles both regular and shorthand properties)
37
+ const providedKeys = new Set()
38
+ for (const prop of objArg.properties) {
39
+ if (prop.type === 'Property') {
40
+ if (prop.key.type === 'Identifier') {
41
+ providedKeys.add(prop.key.name)
42
+ } else if (prop.key.type === 'Literal') {
43
+ providedKeys.add(prop.key.value)
44
+ }
45
+ }
46
+ }
47
+
48
+ // Check for missing required inputs
49
+ for (const [inputKey, inputDef] of Object.entries(
50
+ helperInfo.inputs
51
+ )) {
52
+ const requiredValue =
53
+ inputDef?.value?.required?.value ?? inputDef?.required
54
+ const isRequired =
55
+ requiredValue === true || requiredValue === 'true'
56
+ if (isRequired && !providedKeys.has(inputKey)) {
57
+ diagnostics.push(
58
+ lsp.Diagnostic.create(
59
+ lsp.Range.create(
60
+ document.positionAt(objArg.start),
61
+ document.positionAt(objArg.end)
62
+ ),
63
+ `Missing required input '${inputKey}' for helper '${helperPath}'.`,
64
+ lsp.DiagnosticSeverity.Error,
65
+ 'sails-lsp'
66
+ )
67
+ )
68
+ }
69
+ }
70
+ }
45
71
  }
46
- }
72
+ })
73
+ } catch (error) {
74
+ // Ignore parse errors
47
75
  }
76
+
48
77
  return diagnostics
49
78
  }
79
+
80
+ function extractHelperPath(node) {
81
+ // Walk up the member expression to extract the full helper path
82
+ const segments = []
83
+ let current = node
84
+
85
+ // Collect all segments until we reach sails.helpers
86
+ while (current && current.type === 'MemberExpression') {
87
+ if (current.property && current.property.type === 'Identifier') {
88
+ const propName = current.property.name
89
+ // Stop when we reach 'helpers'
90
+ if (propName === 'helpers') {
91
+ // Check if the object is 'sails'
92
+ if (
93
+ current.object &&
94
+ current.object.type === 'Identifier' &&
95
+ current.object.name === 'sails'
96
+ ) {
97
+ // Valid sails.helpers path found
98
+ const toKebab = (s) =>
99
+ s.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
100
+ return segments.map(toKebab).join('/')
101
+ }
102
+ return null
103
+ }
104
+ segments.unshift(propName)
105
+ }
106
+ current = current.object
107
+ }
108
+
109
+ return null
110
+ }