@sailshq/language-server 0.0.1
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/bin/server.js +7 -0
- package/go-to-definitions/go-to-action.js +82 -0
- package/index.js +42 -0
- package/package.json +26 -0
- package/sailshq-language-server-0.0.0.tgz +0 -0
- package/validators/validate-action-exist.js +65 -0
- package/validators/validate-auto-migration-strategy.js +40 -0
- package/validators/validate-document.js +13 -0
package/bin/server.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const fs = require('fs').promises
|
|
4
|
+
const url = require('url')
|
|
5
|
+
|
|
6
|
+
module.exports = async function goToAction(document, position) {
|
|
7
|
+
const fileName = path.basename(document.uri)
|
|
8
|
+
|
|
9
|
+
if (fileName !== 'routes.js') {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
const actionInfo = extractActionInfo(document, position)
|
|
13
|
+
|
|
14
|
+
if (!actionInfo) {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const projectRoot = path.dirname(path.dirname(document.uri))
|
|
19
|
+
|
|
20
|
+
const fullActionPath = resolveActionPath(projectRoot, actionInfo.action)
|
|
21
|
+
|
|
22
|
+
if (fullActionPath) {
|
|
23
|
+
const fnLineNumber = await findFnLine(fullActionPath)
|
|
24
|
+
return lsp.Location.create(
|
|
25
|
+
fullActionPath,
|
|
26
|
+
lsp.Range.create(fnLineNumber, 0, fnLineNumber, 0)
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractActionInfo(document, position) {
|
|
34
|
+
const text = document.getText()
|
|
35
|
+
const offset = document.offsetAt(position)
|
|
36
|
+
|
|
37
|
+
// This regex matches both object and string notations
|
|
38
|
+
const regex = /(['"])(.+?)\1:\s*(?:{?\s*action\s*:\s*)?(['"])(.+?)\3/g
|
|
39
|
+
let match
|
|
40
|
+
|
|
41
|
+
while ((match = regex.exec(text)) !== null) {
|
|
42
|
+
const [fullMatch, , route, , action] = match
|
|
43
|
+
const start = match.index
|
|
44
|
+
const end = start + fullMatch.length
|
|
45
|
+
|
|
46
|
+
// Check if the cursor is anywhere within the entire match
|
|
47
|
+
if (start <= offset && offset <= end) {
|
|
48
|
+
// Find the start and end positions of the action part
|
|
49
|
+
const actionStart = text.indexOf(action, start)
|
|
50
|
+
const actionEnd = actionStart + action.length
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
action,
|
|
54
|
+
range: lsp.Range.create(
|
|
55
|
+
document.positionAt(actionStart),
|
|
56
|
+
document.positionAt(actionEnd)
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveActionPath(projectRoot, actionPath) {
|
|
66
|
+
return path.join(projectRoot, 'api', 'controllers', `${actionPath}.js`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function findFnLine(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
const content = await fs.readFile(url.fileURLToPath(filePath), 'utf8')
|
|
72
|
+
const lines = content.split('\n')
|
|
73
|
+
for (let i = 0; i < lines.length; i++) {
|
|
74
|
+
if (lines[i].includes('fn:')) {
|
|
75
|
+
return i // Return the line number (0-based index)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return 0 // If 'fn:' is not found, return the first line
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return 0 // Return the first line if there's an error
|
|
81
|
+
}
|
|
82
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver/node')
|
|
2
|
+
const TextDocument = require('vscode-languageserver-textdocument').TextDocument
|
|
3
|
+
const validateDocument = require('./validators/validate-document')
|
|
4
|
+
const goToAction = require('./go-to-definitions/go-to-action')
|
|
5
|
+
|
|
6
|
+
const connection = lsp.createConnection(lsp.ProposedFeatures.all)
|
|
7
|
+
const documents = new lsp.TextDocuments(TextDocument)
|
|
8
|
+
|
|
9
|
+
connection.onInitialize((params) => {
|
|
10
|
+
return {
|
|
11
|
+
capabilities: {
|
|
12
|
+
textDocumentSync: lsp.TextDocumentSyncKind.Incremental,
|
|
13
|
+
definitionProvider: true
|
|
14
|
+
// completionProvider: {
|
|
15
|
+
// resolveProvider: true,
|
|
16
|
+
// triggerCharacters: ['"', "'", '.']
|
|
17
|
+
// }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
documents.onDidOpen((open) => {
|
|
23
|
+
validateDocument(connection, open.document)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
documents.onDidChangeContent((change) => {
|
|
27
|
+
validateDocument(connection, change.document)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
connection.onDefinition(async (params) => {
|
|
31
|
+
const document = documents.get(params.textDocument.uri)
|
|
32
|
+
if (!document) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
const definitions = []
|
|
36
|
+
const actionDefinitions = await goToAction(document, params.position)
|
|
37
|
+
definitions.push(actionDefinitions)
|
|
38
|
+
return definitions || null
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
documents.listen(connection)
|
|
42
|
+
connection.listen()
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sailshq/language-server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Language Server Protocol server for Sails Language Service",
|
|
5
|
+
"homepage": "https://sailjs.com",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/sailshq/language-tools",
|
|
9
|
+
"directory": "packages/language-server"
|
|
10
|
+
},
|
|
11
|
+
"main": "index.js",
|
|
12
|
+
"bin": {
|
|
13
|
+
"sails-language-server": "./bin/sails-language-server.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node server.js",
|
|
17
|
+
"watch": "node --watch server.js",
|
|
18
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
19
|
+
},
|
|
20
|
+
"author": "Kelvin Omereshone",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"vscode-languageserver": "^9.0.1",
|
|
24
|
+
"vscode-languageserver-textdocument": "^1.0.12"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
|
|
13
|
+
if (!actionInfo) {
|
|
14
|
+
return diagnostics
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const projectRoot = path.dirname(path.dirname(document.uri))
|
|
18
|
+
const actions = extractActionInfo(document) // Get all actions
|
|
19
|
+
|
|
20
|
+
for (const { action, range } of actions) {
|
|
21
|
+
const fullActionPath = resolveActionPath(projectRoot, action)
|
|
22
|
+
if (!fs.existsSync(url.fileURLToPath(fullActionPath))) {
|
|
23
|
+
const diagnostic = {
|
|
24
|
+
severity: lsp.DiagnosticSeverity.Error,
|
|
25
|
+
range,
|
|
26
|
+
message: `Action '${action}' does not exist. Please check the controller file.`,
|
|
27
|
+
source: 'Sails Validator'
|
|
28
|
+
}
|
|
29
|
+
diagnostics.push(diagnostic)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return diagnostics
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function extractActionInfo(document) {
|
|
37
|
+
const text = document.getText()
|
|
38
|
+
|
|
39
|
+
// This regex matches both object and string notations
|
|
40
|
+
const regex = /(['"])(.+?)\1:\s*(?:{?\s*action\s*:\s*)?(['"])(.+?)\3/g
|
|
41
|
+
let match
|
|
42
|
+
const actions = []
|
|
43
|
+
|
|
44
|
+
while ((match = regex.exec(text)) !== null) {
|
|
45
|
+
const [fullMatch, , route, , action] = match
|
|
46
|
+
|
|
47
|
+
// Store the action and its range
|
|
48
|
+
const actionStart = match.index + fullMatch.indexOf(action)
|
|
49
|
+
const actionEnd = actionStart + action.length
|
|
50
|
+
|
|
51
|
+
actions.push({
|
|
52
|
+
action,
|
|
53
|
+
range: lsp.Range.create(
|
|
54
|
+
document.positionAt(actionStart),
|
|
55
|
+
document.positionAt(actionEnd)
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return actions // Return an array of actions
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveActionPath(projectRoot, actionPath) {
|
|
64
|
+
return path.join(projectRoot, 'api', 'controllers', `${actionPath}.js`)
|
|
65
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const lsp = require('vscode-languageserver')
|
|
2
|
+
|
|
3
|
+
module.exports = function validateAutomigrationStrategy(document) {
|
|
4
|
+
const text = document.getText()
|
|
5
|
+
|
|
6
|
+
const migrationFiles = ['config/models.js', 'api/models/', 'config/env/']
|
|
7
|
+
const diagnostics = []
|
|
8
|
+
|
|
9
|
+
if (!migrationFiles.some((file) => document.uri.includes(file))) {
|
|
10
|
+
return diagnostics
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const migrateRegex = /^\s*migrate:\s*['"](\w+)['"]\s*,?\s*$/m
|
|
14
|
+
|
|
15
|
+
const match = text.match(migrateRegex)
|
|
16
|
+
const validAutomigrationStrategies = ['safe', 'alter', 'drop']
|
|
17
|
+
|
|
18
|
+
if (match) {
|
|
19
|
+
const automigrationStrategy = match[1]
|
|
20
|
+
if (!validAutomigrationStrategies.includes(automigrationStrategy)) {
|
|
21
|
+
const startIndex = match.index + match[0].indexOf(automigrationStrategy)
|
|
22
|
+
const endIndex = startIndex + automigrationStrategy.length
|
|
23
|
+
|
|
24
|
+
const startPosition = document.positionAt(startIndex)
|
|
25
|
+
const endPosition = document.positionAt(endIndex)
|
|
26
|
+
const diagnostic = {
|
|
27
|
+
severity: lsp.DiagnosticSeverity.Error,
|
|
28
|
+
range: {
|
|
29
|
+
start: startPosition,
|
|
30
|
+
end: endPosition
|
|
31
|
+
},
|
|
32
|
+
message: `Invalid auto-migration strategy: '${automigrationStrategy}'. Supported strategies are: ${validAutomigrationStrategies.join(', ')}.`,
|
|
33
|
+
source: 'Sails Validator'
|
|
34
|
+
}
|
|
35
|
+
diagnostics.push(diagnostic)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return diagnostics
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const validateAutomigrationStrategy = require('./validate-auto-migration-strategy')
|
|
2
|
+
const validateActionExist = require('./validate-action-exist')
|
|
3
|
+
|
|
4
|
+
module.exports = function validateDocument(connection, document) {
|
|
5
|
+
const diagnostics = []
|
|
6
|
+
|
|
7
|
+
const modelDiagnostics = validateAutomigrationStrategy(document)
|
|
8
|
+
const actionDiagnostics = validateActionExist(document)
|
|
9
|
+
|
|
10
|
+
diagnostics.push(...modelDiagnostics, ...actionDiagnostics)
|
|
11
|
+
|
|
12
|
+
connection.sendDiagnostics({ uri: document.uri, diagnostics })
|
|
13
|
+
}
|