@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 ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ if (process.argv.includes('--version')) {
3
+ const pkgJSON = require('../package.json')
4
+ console.log(`${pkgJSON['version']}`)
5
+ } else {
6
+ require('../index.js')
7
+ }
@@ -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
+ }
@@ -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
+ }