From 2bb9bc9533e57a8148413c2f52521d6ed6cf84c5 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 12:38:55 -0400 Subject: [PATCH 01/13] ignore tsconfig.tsbuildinfo --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2545611..71dd22a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ **/out/ *.vsix /server/lib/isclexer.node +**/tsconfig.tsbuildinfo \ No newline at end of file From fc52de69030cb54c4a886f6a1c261b1f559b4eda Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 12:40:00 -0400 Subject: [PATCH 02/13] simpl root tsconfig.json --- tsconfig.json | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index fd51c50..a092747 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,21 +1,11 @@ { - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "outDir": "out", - "rootDir": "src", - "sourceMap": true, - "noImplicitAny": false - }, - "include": [ - "src" - ], - "exclude": [ - "node_modules", - ".vscode-test" - ], + "files": [], "references": [ - { "path": "./client" }, - { "path": "./server" } - ] + { + "path": "./client" + }, + { + "path": "./server" + } + ], } \ No newline at end of file From 709e1ed521513d320d503b62731e1c326d9f3824 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 13:46:47 -0400 Subject: [PATCH 03/13] add prettier and simpl eslint config --- client/package-lock.json | 4 ++-- eslint.config.mjs | 14 +------------- package-lock.json | 23 +++++++++++++++++++++++ package.json | 7 ++++--- server/package-lock.json | 4 ++-- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 9ea5fd0..158d7b1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "language-server-client", - "version": "2.8.3-SNAPSHOT", + "version": "2.8.4-SNAPSHOT", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "language-server-client", - "version": "2.8.3-SNAPSHOT", + "version": "2.8.4-SNAPSHOT", "dependencies": { "axios": "^1.15.0", "vscode-languageclient": "^9.0.1" diff --git a/eslint.config.mjs b/eslint.config.mjs index 79665e7..904d3f9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,25 +1,13 @@ import eslint from "@eslint/js"; import tseslint from "typescript-eslint"; -import globals from "globals"; export default tseslint.config( { - ignores: ["**/vscode.d.ts", "**/vscode.proposed.d.ts", "client/out/**", "server/out/**", "**/*.config.js"], + ignores: ["**/vscode.d.ts", "**/vscode.proposed.d.ts", "client/out/**", "server/out/**", "**/*.config.{mjs,js}"], }, eslint.configs.recommended, ...tseslint.configs.recommended, { - languageOptions: { - globals: { - ...globals.node, - }, - ecmaVersion: 2018, - sourceType: "module", - parserOptions: { - server: "./server/tsconfig.json", - client: "./client/tsconfig.json", - }, - }, rules: { "@typescript-eslint/no-explicit-any": "off", }, diff --git a/package-lock.json b/package-lock.json index 843b3b7..dc414ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "merge-options": "^3.0.4", "node-loader": "^2.1.0", "ovsx": "^0.10.11", + "prettier": "^3.8.3", "rimraf": "^6.0.1", "ts-loader": "^9.5.2", "typescript": "^5.5.4", @@ -5132,6 +5133,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -10316,6 +10333,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", diff --git a/package.json b/package.json index 2ce8fec..221a865 100644 --- a/package.json +++ b/package.json @@ -1790,8 +1790,8 @@ "compile": "tsc -b", "watch": "tsc -b -w", "clean": "rimraf client/out && rimraf server/out", - "lint": "eslint client/src server/src", - "lint-fix": "eslint --fix client/src server/src", + "lint": "eslint && prettier --check .", + "lint-fix": "eslint --fix", "postinstall": "cd client && npm install && cd ../server && npm install && cd .." }, "devDependencies": { @@ -1804,6 +1804,7 @@ "merge-options": "^3.0.4", "node-loader": "^2.1.0", "ovsx": "^0.10.11", + "prettier": "^3.8.3", "rimraf": "^6.0.1", "ts-loader": "^9.5.2", "typescript": "^5.5.4", @@ -1814,4 +1815,4 @@ "extensionDependencies": [ "intersystems-community.vscode-objectscript" ] -} +} \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index d3077c8..0233843 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "language-server-server", - "version": "2.8.3-SNAPSHOT", + "version": "2.8.4-SNAPSHOT", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "language-server-server", - "version": "2.8.3-SNAPSHOT", + "version": "2.8.4-SNAPSHOT", "dependencies": { "node-html-parser": "^7.1.0", "turndown": "^7.2.4", From 9ad593cc46f60ad4317e827e5be61c667a093e69 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 13:49:43 -0400 Subject: [PATCH 04/13] add prettier write to lint-fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 221a865..66aab8e 100644 --- a/package.json +++ b/package.json @@ -1791,7 +1791,7 @@ "watch": "tsc -b -w", "clean": "rimraf client/out && rimraf server/out", "lint": "eslint && prettier --check .", - "lint-fix": "eslint --fix", + "lint-fix": "prettier --write . && eslint --fix", "postinstall": "cd client && npm install && cd ../server && npm install && cd .." }, "devDependencies": { From bcbb21b497a51650d27d6980eacc40a19dbdb670 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 14:14:30 -0400 Subject: [PATCH 05/13] prettierignore and eslint.config.mjs --- .prettierignore | 5 +++++ eslint.config.mjs | 1 + 2 files changed, 6 insertions(+) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..fcf999d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +**/*.json +**/*.md +**/*.js +**/*.mjs +**/*.yml \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 904d3f9..29e90f7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,6 +10,7 @@ export default tseslint.config( { rules: { "@typescript-eslint/no-explicit-any": "off", + "no-control-regex": "off", }, } ); \ No newline at end of file From 7f4fe6202d9f5ff2d9cc6ca21761fe61f32318bc Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 14:15:14 -0400 Subject: [PATCH 06/13] useTab --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 66aab8e..bdc6775 100644 --- a/package.json +++ b/package.json @@ -1814,5 +1814,8 @@ }, "extensionDependencies": [ "intersystems-community.vscode-objectscript" - ] + ], + "prettier": { + "useTabs": true + } } \ No newline at end of file From ddd589fbba4aaa1ad099ec0954e36d9fb55d1b85 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 14:15:54 -0400 Subject: [PATCH 07/13] width: 120 --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bdc6775..d0b76d2 100644 --- a/package.json +++ b/package.json @@ -1816,6 +1816,7 @@ "intersystems-community.vscode-objectscript" ], "prettier": { - "useTabs": true + "useTabs": true, + "printWidth": 120 } } \ No newline at end of file From 27d938c3c92d4d459d41c7136247daaadd4a249f Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 14:22:22 -0400 Subject: [PATCH 08/13] enforce style with prettier --- client/src/commands.ts | 84 +- client/src/evaluatableExpressionProvider.ts | 17 +- client/src/extension.ts | 843 ++++--- client/src/makeRESTRequest.ts | 285 +-- client/src/requestForwarding.ts | 73 +- server/lib/isclexer.node.d.ts | 69 +- server/src/parse/parse.ts | 53 +- server/src/parse/routineheader/linesource.ts | 307 ++- .../parse/routineheader/parseroutineheader.ts | 400 ++- .../parse/routineheader/routineheaderutils.ts | 26 +- server/src/parse/routineheader/validate.ts | 263 +- server/src/providers/completion.ts | 1262 ++++++---- server/src/providers/declaration.ts | 161 +- server/src/providers/definition.ts | 777 ++++-- server/src/providers/diagnostic.ts | 750 +++--- server/src/providers/documentLink.ts | 47 +- server/src/providers/documentSymbol.ts | 342 ++- server/src/providers/evaluatableExpression.ts | 215 +- server/src/providers/foldingRange.ts | 408 +-- server/src/providers/formatting.ts | 402 +-- server/src/providers/hover.ts | 793 +++--- server/src/providers/refactoring.ts | 1157 +++++---- server/src/providers/rename.ts | 312 ++- server/src/providers/requestForwarding.ts | 113 +- server/src/providers/semanticTokens.ts | 30 +- server/src/providers/signatureHelp.ts | 352 ++- server/src/providers/typeDefinition.ts | 67 +- server/src/providers/typeHierarchy.ts | 119 +- server/src/server.ts | 164 +- server/src/utils/functions.ts | 2206 +++++++++++------ server/src/utils/languageDefinitions.ts | 43 +- server/src/utils/types.ts | 176 +- server/src/utils/variables.ts | 302 ++- 33 files changed, 7528 insertions(+), 5090 deletions(-) diff --git a/client/src/commands.ts b/client/src/commands.ts index 783b432..777a3d5 100644 --- a/client/src/commands.ts +++ b/client/src/commands.ts @@ -10,18 +10,17 @@ import { TextEditorRevealType, Selection, TextEditor, - TextEditorEdit -} from 'vscode'; + TextEditorEdit, +} from "vscode"; -import { WorkspaceEdit, TextEdit } from 'vscode-languageclient/node'; +import { WorkspaceEdit, TextEdit } from "vscode-languageclient/node"; -import { client } from './extension'; +import { client } from "./extension"; /** * Callback function for the `intersystems.language-server.overrideClassMembers` command. */ export async function overrideClassMembers() { - // Get the open document and check that it's an ObjectScript class const openDoc = window.activeTextEditor.document; if (openDoc.languageId != "objectscript-class") { @@ -58,7 +57,7 @@ export async function overrideClassMembers() { if (cursorvalid) { docposvalid = await client.sendRequest("intersystems/refactor/validateOverrideCursor", { uri: openDoc.uri.toString(), - line: selection.active.line + line: selection.active.line, }); } if (!cursorvalid || !docposvalid) { @@ -68,9 +67,12 @@ export async function overrideClassMembers() { } // Ask the user to select the type of member that they want to override - const selectedType = await window.showQuickPick(["Method", "Parameter", "Projection", "Property", "Query", "Trigger", "XData"], { - title: "Pick the type of class member to override" - }); + const selectedType = await window.showQuickPick( + ["Method", "Parameter", "Projection", "Property", "Query", "Trigger", "XData"], + { + title: "Pick the type of class member to override", + }, + ); if (!selectedType) { // No member type was selected, so exit return; @@ -79,18 +81,16 @@ export async function overrideClassMembers() { let plural = selectedType + "s"; if (selectedType == "Query") { plural = "Queries"; - } - else if (selectedType == "XData") { + } else if (selectedType == "XData") { plural = "XData blocks"; - } - else if (selectedType == "Property") { + } else if (selectedType == "Property") { plural = "Properties"; } // Ask the server for all overridable members of the selected type const overridableMembers: QuickPickItem[] = await client.sendRequest("intersystems/refactor/listOverridableMembers", { uri: openDoc.uri.toString(), - memberType: selectedType + memberType: selectedType, }); if (!overridableMembers?.length) { // There are no members of this type to override, so tell the user and exit @@ -103,7 +103,7 @@ export async function overrideClassMembers() { title: `Pick the ${plural} to override`, matchOnDescription: true, matchOnDetail: true, - canPickMany: true + canPickMany: true, }); if (!selectedMembers?.length) { // No members were selected, so exit @@ -115,7 +115,7 @@ export async function overrideClassMembers() { uri: openDoc.uri.toString(), members: selectedMembers, cursor: selection.active, - memberType: selectedType + memberType: selectedType, }); // Apply the workspace edit @@ -133,7 +133,7 @@ export async function selectParameterType(uri: string, parameterRange: Range) { const selectedParameter = await window.showQuickPick(allparametertypes, { title: "Pick the Parameter type", matchOnDescription: true, - canPickMany: false + canPickMany: false, }); if (!selectedParameter) { // No parameter was selected @@ -143,12 +143,12 @@ export async function selectParameterType(uri: string, parameterRange: Range) { // Compute the workspace edit const change: TextEdit = { range: parameterRange, - newText: selectedParameter.label + newText: selectedParameter.label, }; const edit: WorkspaceEdit = { changes: { - [uri]: [change] - } + [uri]: [change], + }, }; // Apply the workspace edit @@ -162,7 +162,7 @@ export async function selectImportPackage(uri: string, classname: string) { // Ask for all import packages const allimportpackages: QuickPickItem[] = await client.sendRequest("intersystems/refactor/listImportPackages", { uri: uri, - classmame: classname + classmame: classname, }); let selectedPackage: QuickPickItem; @@ -177,7 +177,7 @@ export async function selectImportPackage(uri: string, classname: string) { // Ask the user to select an import package selectedPackage = await window.showQuickPick(allimportpackages, { title: "Pick the package to import", - canPickMany: false + canPickMany: false, }); if (!selectedPackage) { // No package was selected @@ -198,7 +198,13 @@ export async function selectImportPackage(uri: string, classname: string) { /** * Callback function for the `intersystems.language-server.extractMethod` command. */ -export async function extractMethod(uri: string, lnstart: number, lnend: number, lnmethod: number, newmethodtype: string) { +export async function extractMethod( + uri: string, + lnstart: number, + lnend: number, + lnmethod: number, + newmethodtype: string, +) { // Get the list of class member names const symbols = await commands.executeCommand("vscode.executeDocumentSymbolProvider", Uri.parse(uri)); const clsmembers: string[] = []; @@ -216,7 +222,6 @@ export async function extractMethod(uri: string, lnstart: number, lnend: number, let testname: string = newmethodname; if ( (newmethodname.charAt(0) !== '"' || newmethodname.charAt(newmethodname.length) !== '"') && - // eslint-disable-next-line no-control-regex newmethodname.match(/(^([A-Za-z]|%)$)|(^([A-Za-z]|%)([A-Za-z]|\d|[^\x00-\x7F])+$)/g) === null ) { // Input contains forbidden characters so double exisiting " and add leading and trailing " @@ -228,15 +233,15 @@ export async function extractMethod(uri: string, lnstart: number, lnend: number, if (clsmembers.includes(testname)) { return "Name already in use"; } - } + }, }); if (!newmethodname) { // No name return; } - // Format name - // eslint-disable-next-line no-control-regex + // Format name + if (newmethodname.match(/(^([A-Za-z]|%)$)|(^([A-Za-z]|%)([A-Za-z]|\d|[^\x00-\x7F])+$)/g) === null) { // Add quotes if the name does not start with a letter or %, then followed by letter/number/ascii>128 newmethodname = '"' + newmethodname.replace('"', '""') + '"'; @@ -249,7 +254,7 @@ export async function extractMethod(uri: string, lnstart: number, lnend: number, lnstart: lnstart, lnend: lnend, lnmethod: lnmethod, - newmethodtype: newmethodtype + newmethodtype: newmethodtype, }); // Apply the workspace edit @@ -272,21 +277,23 @@ export async function extractMethod(uri: string, lnstart: number, lnend: number, const linesize = lspWorkspaceEdit.changes[uri][lspWorkspaceEdit.changes[uri].length - 1].newText.length; const range2: Range = new Range( new Position(anchor2.line + methodsize + 1, anchor2.character), - new Position(anchor2.line + methodsize + 1, anchor2.character + linesize + 1) + new Position(anchor2.line + methodsize + 1, anchor2.character + linesize + 1), ); // Scroll to the extracted method activeEditor.revealRange(range); // Highlight extracted method and method call - const color: string = "#ffff0020"; // Transparent yellow + const color: string = "#ffff0020"; // Transparent yellow const timeout: number = 2000; // Highlight disapears after 2 seconds const decoration = window.createTextEditorDecorationType({ - backgroundColor: color + backgroundColor: color, }); activeEditor.setDecorations(decoration, [range, range2]); - await new Promise(r => setTimeout(r, timeout)); - setTimeout(function () { decoration.dispose(); }, 0); + await new Promise((r) => setTimeout(r, timeout)); + setTimeout(function () { + decoration.dispose(); + }, 0); } } @@ -304,7 +311,7 @@ export async function showSymbolInClass(uri: string, memberType: string, memberN return; } const symbol = symbols[0].children.find( - (symbol) => symbol.detail.toLowerCase().includes(memberType.toLowerCase()) && symbol.name === memberName + (symbol) => symbol.detail.toLowerCase().includes(memberType.toLowerCase()) && symbol.name === memberName, ); if (symbol !== undefined) { // Show the symbol in the editor @@ -320,7 +327,14 @@ export async function showSymbolInClass(uri: string, memberType: string, memberN /** * Callback function for the `intersystems.language-server.setSelection` command. */ -export function setSelection(editor: TextEditor, _edit: TextEditorEdit, startLine: number, startCharacter: number, endLine: number, endCharacter: number) { +export function setSelection( + editor: TextEditor, + _edit: TextEditorEdit, + startLine: number, + startCharacter: number, + endLine: number, + endCharacter: number, +) { const range = new Range(startLine, startCharacter, endLine, endCharacter); editor.selection = new Selection(range.start, range.end); editor.revealRange(range, TextEditorRevealType.InCenter); diff --git a/client/src/evaluatableExpressionProvider.ts b/client/src/evaluatableExpressionProvider.ts index dbf0716..5655d41 100644 --- a/client/src/evaluatableExpressionProvider.ts +++ b/client/src/evaluatableExpressionProvider.ts @@ -1,22 +1,15 @@ -import { - EvaluatableExpressionProvider, - Position, - TextDocument, - EvaluatableExpression -} from 'vscode'; +import { EvaluatableExpressionProvider, Position, TextDocument, EvaluatableExpression } from "vscode"; -import { client } from './extension'; +import { client } from "./extension"; export class ObjectScriptEvaluatableExpressionProvider implements EvaluatableExpressionProvider { - constructor() {} - + provideEvaluatableExpression(document: TextDocument, position: Position): Promise { // Have the server do the work - return client.sendRequest("intersystems/debugger/evaluatableExpression",{ + return client.sendRequest("intersystems/debugger/evaluatableExpression", { uri: document.uri.toString(), - position: position + position: position, }); } - } diff --git a/client/src/extension.ts b/client/src/extension.ts index 8edd7d7..eb7a363 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -1,404 +1,439 @@ -import * as path from 'path'; -import { - ExtensionContext, - extensions, - Uri, - window, - ColorThemeKind, - workspace, - commands, - languages, - authentication -} from 'vscode'; - -import { - DocumentSelector, - LanguageClient, - LanguageClientOptions, - ServerOptions, - TransportKind -} from 'vscode-languageclient/node'; - -import { gt, lte, lt } from "semver"; -import * as serverManager from "@intersystems-community/intersystems-servermanager"; - -import { ObjectScriptEvaluatableExpressionProvider } from './evaluatableExpressionProvider'; -import { - extractMethod, - showSymbolInClass, - overrideClassMembers, - selectImportPackage, - selectParameterType, - setSelection -} from './commands'; -import { makeRESTRequest, ServerSpec } from './makeRESTRequest'; -import { ISCEmbeddedContentProvider, requestForwardingMiddleware } from './requestForwarding'; - -export let client: LanguageClient; - -/** - * Cache for cookies from REST requests to InterSystems servers. - */ -const cookiesCache: Map = new Map(); - -export function updateCookies(newCookies: string[], server: ServerSpec): string[] { - const key = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`; - const cookies = cookiesCache.get(key) ?? []; - newCookies.forEach((cookie) => { - const [cookieName] = cookie.split("="); - const index = cookies.findIndex((el) => el.startsWith(cookieName)); - if (index >= 0) { - cookies[index] = cookie; - } else { - cookies.push(cookie); - } - }); - cookiesCache.set(key, cookies); - return cookies; -} - -export function getCookies(server: ServerSpec): string[] { - return cookiesCache.get(`${server.username}@${server.host}:${server.port}${server.pathPrefix}`) ?? []; -} - -let objectScriptApi: any; -let serverManagerApi: serverManager.ServerManagerAPI; - -/** Resolved connection information for each workspace folder */ -const wsFolderServerSpecs: Map = new Map(); - -type MakeRESTRequestParams = { - method: "GET" | "POST"; - api: number; - path: string; - server: ServerSpec; - data?: any; - checksum?: string; - params?: any; -}; - -export async function activate(context: ExtensionContext) { - // Get the main extension exported API - const objectScriptExt = extensions.getExtension("intersystems-community.vscode-objectscript")!; - objectScriptApi = objectScriptExt.isActive ? objectScriptExt.exports : await objectScriptExt.activate(); - - // The server is implemented in node - const serverModule = context.asAbsolutePath( - path.join('server', 'out', 'server.js') - ); - // The debug options for the server - // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging - const debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] }; - - // If the extension is launched in debug mode then the debug server options are used - // Otherwise the run options are used - const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { - module: serverModule, - transport: TransportKind.ipc, - options: debugOptions - } - }; - - // The languages we handle - const targetLanguages = [ - 'objectscript', - 'objectscript-int', - 'objectscript-class', - 'objectscript-csp', - 'objectscript-macros', - ]; - - // The uri schemes we handle those languages for - const targetSchemes = [ - 'isfs', - 'isfs-readonly', - 'objectscript', - 'objectscriptxml', - 'file', - 'vscode-remote', - 'vscode-notebook-cell' - ]; - - // A document selector to target the right {language, scheme} tuples - const documentSelector: DocumentSelector = []; - targetLanguages.forEach(language => { - targetSchemes.forEach(scheme => { - documentSelector.push({ language, scheme }); - }); - }); - - // Options to control the language client - const clientOptions: LanguageClientOptions = { - // Register the server for InterSystems files handled by vscode-objectscript extension - documentSelector: documentSelector, - // Register middleware for embedded language request forwarding - middleware: requestForwardingMiddleware, - // Allow the rendering of HTML tags like , and
- markdown: { - supportHtml: true - } - }; - - // Create the language client and start the client. - client = new LanguageClient( - 'intersystems.language-server', - 'InterSystems Language Server', - serverOptions, - clientOptions - ); - - // Send custom notifications when the connection or password changes - objectScriptApi.onDidChangeConnection()(() => { - wsFolderServerSpecs.clear(); - client.sendNotification("intersystems/server/connectionChange"); - }); - const serverManagerExt = extensions.getExtension("intersystems-community.servermanager"); - if (serverManagerExt !== undefined) { - // The server manager extension is installed - serverManagerApi = serverManagerExt.isActive ? serverManagerExt.exports : await serverManagerExt.activate(); - serverManagerApi.onDidChangePassword()((serverName: string) => { - for (const [k, v] of wsFolderServerSpecs.entries()) { - if (v.serverName == serverName) wsFolderServerSpecs.delete(k); - } - client.sendNotification("intersystems/server/passwordChange", serverName); - }); - } - - const textDecoder = new TextDecoder(); - context.subscriptions.push( - // Register custom request handlers - client.onRequest("intersystems/server/resolveFromUri", async (uri: string) => { - const uriObj = Uri.parse(uri); - const wsFolderUriString = workspace.getWorkspaceFolder(uriObj)?.uri.toString(); - const serverSpec = objectScriptApi.serverForUri(uriObj); - if ( - // Server was resolved - serverSpec.host !== "" && - // Connection isn't unauthenticated - serverSpec.username != undefined && - serverSpec.username != "" && - serverSpec.username.toLowerCase() != "unknownuser" && - // A password is missing - typeof serverSpec.password === "undefined" && - // A supported version of the Server Manager is installed - serverManagerExt != undefined && - gt(serverManagerExt.packageJSON.version, "3.0.0") - ) { - // The main extension didn't provide a password, so we must - // get it from the server manager's authentication provider. - const scopes = [serverSpec.serverName, serverSpec.username]; - try { - const account = serverManagerApi?.getAccount ? serverManagerApi.getAccount({ name: serverSpec.serverName, ...serverSpec }) : undefined; - let session = await authentication.getSession(serverManager.AUTHENTICATION_PROVIDER, scopes, { silent: true, account }); - if (!session) { - session = await authentication.getSession(serverManager.AUTHENTICATION_PROVIDER, scopes, { createIfNone: true, account }); - } - if (session) { - serverSpec.username = session.scopes[1]; - serverSpec.password = session.accessToken; - } - } catch (error) { - // The user did not consent to sharing authentication information - if (error instanceof Error) { - client.warn(`${serverManager.AUTHENTICATION_PROVIDER}: ${error.message}`); - } - } - } - if (typeof serverSpec.username == "string" && serverSpec.username.toLowerCase() == "unknownuser" && typeof serverSpec.password == "undefined") { - // UnknownUser without a password means "unauthenticated" - serverSpec.username = undefined; - } - if (wsFolderUriString && !wsFolderServerSpecs.has(wsFolderUriString)) { - wsFolderServerSpecs.set(wsFolderUriString, serverSpec); - } - return serverSpec; - }), - client.onRequest("intersystems/uri/localToVirtual", (uri: string): string => { - const newuri: Uri = objectScriptApi.serverDocumentUriForUri(Uri.parse(uri)); - return newuri.toString(); - }), - client.onRequest("intersystems/uri/forDocument", (document: string): string | null => { - if (lte(objectScriptExt.packageJSON.version, "1.0.10")) { - // If the active version of vscode-objectscript doesn't expose - // DocumentContentProvider.getUri(), just return the empty string. - return ""; - } - const uri: Uri | null = objectScriptApi.getUriForDocument(document); - return uri == null ? null : uri.toString(); - }), - client.onRequest("intersystems/uri/forTypeHierarchyClasses", (classes: string[]): string[] => { - // vscode-objectscript version 1.0.11+ has been available for long enough that - // it's safe to assume that users have upgraded to at least 1.0.11 - return classes.map( - (cls: string) => { - const uri: Uri = objectScriptApi.getUriForDocument(`${cls}.cls`); - return uri.toString(); - } - ); - }), - client.onRequest("intersystems/server/makeRESTRequest", async (args: MakeRESTRequestParams): Promise => { - // As of version 2.0.0, REST requests are made on the client side - return makeRESTRequest(args.method, args.api, args.path, args.server, args.data, args.checksum, args.params).then(respdata => { - if (respdata) { - // Can't return the entire AxiosResponse object because it's not JSON.stringify-able due to circularity - return { data: respdata.data }; - } else { - return undefined; - } - }); - }), - client.onRequest("intersystems/uri/getText", async (params: { uri: string, server: ServerSpec }): Promise => { - try { - const uri = Uri.parse(params.uri); - if (uri.scheme == "objectscript") { - // Can't use the FileSystem with a DocumentContentProvider, so fetch the text directly from the server - const uriParams = new URLSearchParams(uri.query); - const fileName = - uriParams.has("csp") && ["", "1"].includes(uriParams.get("csp") ?? "") - ? uri.path.slice(1) - : uri.path.split("/").slice(1).join("."); - const docParams = - params.server.apiVersion >= 4 && workspace.getConfiguration("objectscript", - workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()) - ).get("multilineMethodArgs") - ? { format: "udl-multiline" } - : undefined; - const resp = await makeRESTRequest("GET", 1, `/doc/${fileName}`, params.server, undefined, undefined, docParams); - return resp?.data?.result?.content || []; - } else { - // Read the contents of the file at uri - return textDecoder.decode(await workspace.fs.readFile(uri)).split(/\r?\n/); - } - } catch { - // The file wasn't found or wasn't valid utf-8 - return []; - } - }), - - // Register commands - commands.registerCommand("intersystems.language-server.overrideClassMembers", overrideClassMembers), - commands.registerCommand("intersystems.language-server.selectParameterType", selectParameterType), - commands.registerCommand("intersystems.language-server.selectImportPackage", selectImportPackage), - commands.registerCommand("intersystems.language-server.extractMethod", extractMethod), - commands.registerCommand("intersystems.language-server.showSymbolInClass", showSymbolInClass), - commands.registerTextEditorCommand("intersystems.language-server.setSelection", setSelection), - - // Register EvaluatableExpressionProvider - languages.registerEvaluatableExpressionProvider(documentSelector, new ObjectScriptEvaluatableExpressionProvider()), - - // Register embedded language request forwarding content provider - workspace.registerTextDocumentContentProvider("isc-embedded-content", new ISCEmbeddedContentProvider()) - ); - - // Start the client. This will also launch the server - client.start(); - - const workbenchConfig = workspace.getConfiguration("workbench"); - if ( - workspace.getConfiguration( - "intersystems.language-server", - workspace.workspaceFolders != undefined ? workspace.workspaceFolders[0] : undefined - ).get("suggestTheme") === true && - !workbenchConfig.get("colorTheme")?.startsWith("InterSystems Default ") - ) { - // Suggest an InterSystems default theme depending on the current active theme type - if (window.activeColorTheme.kind === ColorThemeKind.Light) { - if (workspace.name === undefined) { - window.showInformationMessage( - `For the best user experience, InterSystems recommends that you activate the default light theme included with the [InterSystems Language Server extension](https://marketplace.visualstudio.com/items?itemName=intersystems.language-server). Activate now?`, - "Yes", - "Don't Ask Again" - ).then((answer) => { - if (answer === "Yes") { - workbenchConfig.update("colorTheme", "InterSystems Default Light Modern", true); - } - else if (answer === "Don't Ask Again") { - workspace.getConfiguration("intersystems.language-server").update("suggestTheme", false, true); - } - }); - } - else { - // Only give the "Only This Workspace" option if a workspace is open - window.showInformationMessage( - `For the best user experience, InterSystems recommends that you activate the default light theme included with the [InterSystems Language Server extension](https://marketplace.visualstudio.com/items?itemName=intersystems.language-server). Activate now?`, - "Globally", - "Only This Workspace", - "Don't Ask Again" - ).then((answer) => { - if (answer === "Globally") { - workbenchConfig.update("colorTheme", "InterSystems Default Light Modern", true); - } - else if (answer === "Only This Workspace") { - workbenchConfig.update("colorTheme", "InterSystems Default Light Modern", false); - } - else if (answer === "Don't Ask Again") { - workspace.getConfiguration("intersystems.language-server").update("suggestTheme", false, true); - } - }); - } - } - else if (window.activeColorTheme.kind === ColorThemeKind.Dark) { - if (workspace.name === undefined) { - window.showInformationMessage( - `For the best user experience, InterSystems recommends that you activate the default dark theme included with the [InterSystems Language Server extension](https://marketplace.visualstudio.com/items?itemName=intersystems.language-server). Activate now?`, - "Yes", - "Don't Ask Again" - ).then((answer) => { - if (answer === "Yes") { - workbenchConfig.update("colorTheme", "InterSystems Default Dark Modern", true); - } - else if (answer === "Don't Ask Again") { - workspace.getConfiguration("intersystems.language-server").update("suggestTheme", false, true); - } - }); - } - else { - // Only give the "Only This Workspace" option if a workspace is open - window.showInformationMessage( - `For the best user experience, InterSystems recommends that you activate the default dark theme included with the [InterSystems Language Server extension](https://marketplace.visualstudio.com/items?itemName=intersystems.language-server). Activate now?`, - "Globally", - "Only This Workspace", - "Don't Ask Again" - ).then((answer) => { - if (answer === "Globally") { - workbenchConfig.update("colorTheme", "InterSystems Default Dark Modern", true); - } - else if (answer === "Only This Workspace") { - workbenchConfig.update("colorTheme", "InterSystems Default Dark Modern", false); - } - else if (answer === "Don't Ask Again") { - workspace.getConfiguration("intersystems.language-server").update("suggestTheme", false, true); - } - }); - } - } - } -} - -export async function deactivate(): Promise { - // Stop the server and log out of all CSP sessions - const loggedOut: Set = new Set(); - const promises: Promise[] = client ? [client.stop()] : []; - for (const f of workspace.workspaceFolders ?? []) { - const serverSpec = wsFolderServerSpecs.get(f.uri.toString()); - if (!serverSpec?.active) continue; - const sessionCookie = getCookies(serverSpec).find((c) => c.startsWith("CSPSESSIONID-")); - if (!sessionCookie || loggedOut.has(sessionCookie)) continue; - loggedOut.add(sessionCookie); - promises.push( - makeRESTRequest( - "HEAD", - 0, - "", - serverSpec, - undefined, - undefined, - // Prefer IRISLogout for servers that support it - lt(serverSpec.serverVersion, "2018.2.0") ? { CacheLogout: "end" } : { IRISLogout: "end" } - ) - ); - } - await Promise.allSettled(promises); -} +import * as path from "path"; +import { + ExtensionContext, + extensions, + Uri, + window, + ColorThemeKind, + workspace, + commands, + languages, + authentication, +} from "vscode"; + +import { + DocumentSelector, + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind, +} from "vscode-languageclient/node"; + +import { gt, lte, lt } from "semver"; +import * as serverManager from "@intersystems-community/intersystems-servermanager"; + +import { ObjectScriptEvaluatableExpressionProvider } from "./evaluatableExpressionProvider"; +import { + extractMethod, + showSymbolInClass, + overrideClassMembers, + selectImportPackage, + selectParameterType, + setSelection, +} from "./commands"; +import { makeRESTRequest, ServerSpec } from "./makeRESTRequest"; +import { ISCEmbeddedContentProvider, requestForwardingMiddleware } from "./requestForwarding"; + +export let client: LanguageClient; + +/** + * Cache for cookies from REST requests to InterSystems servers. + */ +const cookiesCache: Map = new Map(); + +export function updateCookies(newCookies: string[], server: ServerSpec): string[] { + const key = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`; + const cookies = cookiesCache.get(key) ?? []; + newCookies.forEach((cookie) => { + const [cookieName] = cookie.split("="); + const index = cookies.findIndex((el) => el.startsWith(cookieName)); + if (index >= 0) { + cookies[index] = cookie; + } else { + cookies.push(cookie); + } + }); + cookiesCache.set(key, cookies); + return cookies; +} + +export function getCookies(server: ServerSpec): string[] { + return cookiesCache.get(`${server.username}@${server.host}:${server.port}${server.pathPrefix}`) ?? []; +} + +let objectScriptApi: any; +let serverManagerApi: serverManager.ServerManagerAPI; + +/** Resolved connection information for each workspace folder */ +const wsFolderServerSpecs: Map = new Map(); + +type MakeRESTRequestParams = { + method: "GET" | "POST"; + api: number; + path: string; + server: ServerSpec; + data?: any; + checksum?: string; + params?: any; +}; + +export async function activate(context: ExtensionContext) { + // Get the main extension exported API + const objectScriptExt = extensions.getExtension("intersystems-community.vscode-objectscript")!; + objectScriptApi = objectScriptExt.isActive ? objectScriptExt.exports : await objectScriptExt.activate(); + + // The server is implemented in node + const serverModule = context.asAbsolutePath(path.join("server", "out", "server.js")); + // The debug options for the server + // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging + const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; + + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions, + }, + }; + + // The languages we handle + const targetLanguages = [ + "objectscript", + "objectscript-int", + "objectscript-class", + "objectscript-csp", + "objectscript-macros", + ]; + + // The uri schemes we handle those languages for + const targetSchemes = [ + "isfs", + "isfs-readonly", + "objectscript", + "objectscriptxml", + "file", + "vscode-remote", + "vscode-notebook-cell", + ]; + + // A document selector to target the right {language, scheme} tuples + const documentSelector: DocumentSelector = []; + targetLanguages.forEach((language) => { + targetSchemes.forEach((scheme) => { + documentSelector.push({ language, scheme }); + }); + }); + + // Options to control the language client + const clientOptions: LanguageClientOptions = { + // Register the server for InterSystems files handled by vscode-objectscript extension + documentSelector: documentSelector, + // Register middleware for embedded language request forwarding + middleware: requestForwardingMiddleware, + // Allow the rendering of HTML tags like , and
+ markdown: { + supportHtml: true, + }, + }; + + // Create the language client and start the client. + client = new LanguageClient( + "intersystems.language-server", + "InterSystems Language Server", + serverOptions, + clientOptions, + ); + + // Send custom notifications when the connection or password changes + objectScriptApi.onDidChangeConnection()(() => { + wsFolderServerSpecs.clear(); + client.sendNotification("intersystems/server/connectionChange"); + }); + const serverManagerExt = extensions.getExtension("intersystems-community.servermanager"); + if (serverManagerExt !== undefined) { + // The server manager extension is installed + serverManagerApi = serverManagerExt.isActive ? serverManagerExt.exports : await serverManagerExt.activate(); + serverManagerApi.onDidChangePassword()((serverName: string) => { + for (const [k, v] of wsFolderServerSpecs.entries()) { + if (v.serverName == serverName) wsFolderServerSpecs.delete(k); + } + client.sendNotification("intersystems/server/passwordChange", serverName); + }); + } + + const textDecoder = new TextDecoder(); + context.subscriptions.push( + // Register custom request handlers + client.onRequest("intersystems/server/resolveFromUri", async (uri: string) => { + const uriObj = Uri.parse(uri); + const wsFolderUriString = workspace.getWorkspaceFolder(uriObj)?.uri.toString(); + const serverSpec = objectScriptApi.serverForUri(uriObj); + if ( + // Server was resolved + serverSpec.host !== "" && + // Connection isn't unauthenticated + serverSpec.username != undefined && + serverSpec.username != "" && + serverSpec.username.toLowerCase() != "unknownuser" && + // A password is missing + typeof serverSpec.password === "undefined" && + // A supported version of the Server Manager is installed + serverManagerExt != undefined && + gt(serverManagerExt.packageJSON.version, "3.0.0") + ) { + // The main extension didn't provide a password, so we must + // get it from the server manager's authentication provider. + const scopes = [serverSpec.serverName, serverSpec.username]; + try { + const account = serverManagerApi?.getAccount + ? serverManagerApi.getAccount({ name: serverSpec.serverName, ...serverSpec }) + : undefined; + let session = await authentication.getSession(serverManager.AUTHENTICATION_PROVIDER, scopes, { + silent: true, + account, + }); + if (!session) { + session = await authentication.getSession(serverManager.AUTHENTICATION_PROVIDER, scopes, { + createIfNone: true, + account, + }); + } + if (session) { + serverSpec.username = session.scopes[1]; + serverSpec.password = session.accessToken; + } + } catch (error) { + // The user did not consent to sharing authentication information + if (error instanceof Error) { + client.warn(`${serverManager.AUTHENTICATION_PROVIDER}: ${error.message}`); + } + } + } + if ( + typeof serverSpec.username == "string" && + serverSpec.username.toLowerCase() == "unknownuser" && + typeof serverSpec.password == "undefined" + ) { + // UnknownUser without a password means "unauthenticated" + serverSpec.username = undefined; + } + if (wsFolderUriString && !wsFolderServerSpecs.has(wsFolderUriString)) { + wsFolderServerSpecs.set(wsFolderUriString, serverSpec); + } + return serverSpec; + }), + client.onRequest("intersystems/uri/localToVirtual", (uri: string): string => { + const newuri: Uri = objectScriptApi.serverDocumentUriForUri(Uri.parse(uri)); + return newuri.toString(); + }), + client.onRequest("intersystems/uri/forDocument", (document: string): string | null => { + if (lte(objectScriptExt.packageJSON.version, "1.0.10")) { + // If the active version of vscode-objectscript doesn't expose + // DocumentContentProvider.getUri(), just return the empty string. + return ""; + } + const uri: Uri | null = objectScriptApi.getUriForDocument(document); + return uri == null ? null : uri.toString(); + }), + client.onRequest("intersystems/uri/forTypeHierarchyClasses", (classes: string[]): string[] => { + // vscode-objectscript version 1.0.11+ has been available for long enough that + // it's safe to assume that users have upgraded to at least 1.0.11 + return classes.map((cls: string) => { + const uri: Uri = objectScriptApi.getUriForDocument(`${cls}.cls`); + return uri.toString(); + }); + }), + client.onRequest( + "intersystems/server/makeRESTRequest", + async (args: MakeRESTRequestParams): Promise => { + // As of version 2.0.0, REST requests are made on the client side + return makeRESTRequest( + args.method, + args.api, + args.path, + args.server, + args.data, + args.checksum, + args.params, + ).then((respdata) => { + if (respdata) { + // Can't return the entire AxiosResponse object because it's not JSON.stringify-able due to circularity + return { data: respdata.data }; + } else { + return undefined; + } + }); + }, + ), + client.onRequest( + "intersystems/uri/getText", + async (params: { uri: string; server: ServerSpec }): Promise => { + try { + const uri = Uri.parse(params.uri); + if (uri.scheme == "objectscript") { + // Can't use the FileSystem with a DocumentContentProvider, so fetch the text directly from the server + const uriParams = new URLSearchParams(uri.query); + const fileName = + uriParams.has("csp") && ["", "1"].includes(uriParams.get("csp") ?? "") + ? uri.path.slice(1) + : uri.path.split("/").slice(1).join("."); + const docParams = + params.server.apiVersion >= 4 && + workspace + .getConfiguration( + "objectscript", + workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), + ) + .get("multilineMethodArgs") + ? { format: "udl-multiline" } + : undefined; + const resp = await makeRESTRequest( + "GET", + 1, + `/doc/${fileName}`, + params.server, + undefined, + undefined, + docParams, + ); + return resp?.data?.result?.content || []; + } else { + // Read the contents of the file at uri + return textDecoder.decode(await workspace.fs.readFile(uri)).split(/\r?\n/); + } + } catch { + // The file wasn't found or wasn't valid utf-8 + return []; + } + }, + ), + + // Register commands + commands.registerCommand("intersystems.language-server.overrideClassMembers", overrideClassMembers), + commands.registerCommand("intersystems.language-server.selectParameterType", selectParameterType), + commands.registerCommand("intersystems.language-server.selectImportPackage", selectImportPackage), + commands.registerCommand("intersystems.language-server.extractMethod", extractMethod), + commands.registerCommand("intersystems.language-server.showSymbolInClass", showSymbolInClass), + commands.registerTextEditorCommand("intersystems.language-server.setSelection", setSelection), + + // Register EvaluatableExpressionProvider + languages.registerEvaluatableExpressionProvider(documentSelector, new ObjectScriptEvaluatableExpressionProvider()), + + // Register embedded language request forwarding content provider + workspace.registerTextDocumentContentProvider("isc-embedded-content", new ISCEmbeddedContentProvider()), + ); + + // Start the client. This will also launch the server + client.start(); + + const workbenchConfig = workspace.getConfiguration("workbench"); + if ( + workspace + .getConfiguration( + "intersystems.language-server", + workspace.workspaceFolders != undefined ? workspace.workspaceFolders[0] : undefined, + ) + .get("suggestTheme") === true && + !workbenchConfig.get("colorTheme")?.startsWith("InterSystems Default ") + ) { + // Suggest an InterSystems default theme depending on the current active theme type + if (window.activeColorTheme.kind === ColorThemeKind.Light) { + if (workspace.name === undefined) { + window + .showInformationMessage( + `For the best user experience, InterSystems recommends that you activate the default light theme included with the [InterSystems Language Server extension](https://marketplace.visualstudio.com/items?itemName=intersystems.language-server). Activate now?`, + "Yes", + "Don't Ask Again", + ) + .then((answer) => { + if (answer === "Yes") { + workbenchConfig.update("colorTheme", "InterSystems Default Light Modern", true); + } else if (answer === "Don't Ask Again") { + workspace.getConfiguration("intersystems.language-server").update("suggestTheme", false, true); + } + }); + } else { + // Only give the "Only This Workspace" option if a workspace is open + window + .showInformationMessage( + `For the best user experience, InterSystems recommends that you activate the default light theme included with the [InterSystems Language Server extension](https://marketplace.visualstudio.com/items?itemName=intersystems.language-server). Activate now?`, + "Globally", + "Only This Workspace", + "Don't Ask Again", + ) + .then((answer) => { + if (answer === "Globally") { + workbenchConfig.update("colorTheme", "InterSystems Default Light Modern", true); + } else if (answer === "Only This Workspace") { + workbenchConfig.update("colorTheme", "InterSystems Default Light Modern", false); + } else if (answer === "Don't Ask Again") { + workspace.getConfiguration("intersystems.language-server").update("suggestTheme", false, true); + } + }); + } + } else if (window.activeColorTheme.kind === ColorThemeKind.Dark) { + if (workspace.name === undefined) { + window + .showInformationMessage( + `For the best user experience, InterSystems recommends that you activate the default dark theme included with the [InterSystems Language Server extension](https://marketplace.visualstudio.com/items?itemName=intersystems.language-server). Activate now?`, + "Yes", + "Don't Ask Again", + ) + .then((answer) => { + if (answer === "Yes") { + workbenchConfig.update("colorTheme", "InterSystems Default Dark Modern", true); + } else if (answer === "Don't Ask Again") { + workspace.getConfiguration("intersystems.language-server").update("suggestTheme", false, true); + } + }); + } else { + // Only give the "Only This Workspace" option if a workspace is open + window + .showInformationMessage( + `For the best user experience, InterSystems recommends that you activate the default dark theme included with the [InterSystems Language Server extension](https://marketplace.visualstudio.com/items?itemName=intersystems.language-server). Activate now?`, + "Globally", + "Only This Workspace", + "Don't Ask Again", + ) + .then((answer) => { + if (answer === "Globally") { + workbenchConfig.update("colorTheme", "InterSystems Default Dark Modern", true); + } else if (answer === "Only This Workspace") { + workbenchConfig.update("colorTheme", "InterSystems Default Dark Modern", false); + } else if (answer === "Don't Ask Again") { + workspace.getConfiguration("intersystems.language-server").update("suggestTheme", false, true); + } + }); + } + } + } +} + +export async function deactivate(): Promise { + // Stop the server and log out of all CSP sessions + const loggedOut: Set = new Set(); + const promises: Promise[] = client ? [client.stop()] : []; + for (const f of workspace.workspaceFolders ?? []) { + const serverSpec = wsFolderServerSpecs.get(f.uri.toString()); + if (!serverSpec?.active) continue; + const sessionCookie = getCookies(serverSpec).find((c) => c.startsWith("CSPSESSIONID-")); + if (!sessionCookie || loggedOut.has(sessionCookie)) continue; + loggedOut.add(sessionCookie); + promises.push( + makeRESTRequest( + "HEAD", + 0, + "", + serverSpec, + undefined, + undefined, + // Prefer IRISLogout for servers that support it + lt(serverSpec.serverVersion, "2018.2.0") ? { CacheLogout: "end" } : { IRISLogout: "end" }, + ), + ); + } + await Promise.allSettled(promises); +} diff --git a/client/src/makeRESTRequest.ts b/client/src/makeRESTRequest.ts index 4ef61e6..3a4f7d5 100644 --- a/client/src/makeRESTRequest.ts +++ b/client/src/makeRESTRequest.ts @@ -1,27 +1,27 @@ -import { workspace } from 'vscode'; +import { workspace } from "vscode"; -import axios, { AxiosResponse } from 'axios'; -import * as https from 'https'; +import axios, { AxiosResponse } from "axios"; +import * as https from "https"; -import { client, getCookies, updateCookies } from './extension'; +import { client, getCookies, updateCookies } from "./extension"; export type ServerSpec = { - scheme: string, - host: string, - port: number, - pathPrefix: string, - apiVersion: number, - namespace: string, - username: string, - serverName: string, - password: string, - serverVersion: string, - active: boolean + scheme: string; + host: string; + port: number; + pathPrefix: string; + apiVersion: number; + namespace: string; + username: string; + serverName: string; + password: string; + serverVersion: string; + active: boolean; }; /** * Send a REST request to an InterSystems server. - * + * * @param method The REST method. * @param api The version of the Atelier API required for this request. * @param path The path portion of the URL. @@ -30,7 +30,15 @@ export type ServerSpec = { * @param checksum Optional checksum. Only passed for SASchema requests. * @param params Optional URL parameters. Only passed for GET /doc/ requests. */ -export async function makeRESTRequest(method: "GET" | "POST" | "HEAD", api: number, path: string, server: ServerSpec, data?: any, checksum?: string, params?: any): Promise | undefined> { +export async function makeRESTRequest( + method: "GET" | "POST" | "HEAD", + api: number, + path: string, + server: ServerSpec, + data?: any, + checksum?: string, + params?: any, +): Promise | undefined> { if (server.host === "") { // No server connection is configured client.warn("Cannot make required REST request because no server connection is configured."); @@ -44,19 +52,23 @@ export async function makeRESTRequest(method: "GET" | "POST" | "HEAD", api: numb // The server doesn't support the Atelier API version required to make this request client.warn( "Cannot make required REST request to server " + - `${server.serverName !== "" ? `'${server.serverName}'` : `${server.host}:${server.port}${server.pathPrefix}`} ` + - `because it does not support the '${path}' endpoint, which requires Atelier API version ${api}.` + `${server.serverName !== "" ? `'${server.serverName}'` : `${server.host}:${server.port}${server.pathPrefix}`} ` + + `because it does not support the '${path}' endpoint, which requires Atelier API version ${api}.`, ); return undefined; } if (server.username != undefined && server.username != "" && typeof server.password === "undefined") { // A username without a password isn't allowed - client.warn("Cannot make required REST request because the configured server connection has a username but no password."); + client.warn( + "Cannot make required REST request because the configured server connection has a username but no password.", + ); return undefined; } // Build the URL - const url = encodeURI(`${server.scheme}://${server.host}:${server.port}${server.pathPrefix}/api/atelier/${api ? `v${server.apiVersion}/${server.namespace}${path}` : ""}`); + const url = encodeURI( + `${server.scheme}://${server.host}:${server.port}${server.pathPrefix}/api/atelier/${api ? `v${server.apiVersion}/${server.namespace}${path}` : ""}`, + ); // Create the HTTPS agent const httpsAgent = new https.Agent({ rejectUnauthorized: workspace.getConfiguration("http").get("proxyStrictSSL") }); @@ -71,176 +83,153 @@ export async function makeRESTRequest(method: "GET" | "POST" | "HEAD", api: numb // This is a SASchema request // Make the initial request - respdata = await axios.request( - { + respdata = await axios.request({ + method: "GET", + url: url, + headers: { + "if-none-match": checksum, + Cookie: cookies.join(" "), + }, + withCredentials: true, + httpsAgent, + validateStatus: function (status) { + return status < 500; + }, + }); + cookies = updateCookies(respdata.headers["set-cookie"] || [], server); + if (respdata.status === 202) { + // The schema is being recalculated so we need to make another call to get it + respdata = await axios.request({ method: "GET", url: url, - headers: { - "if-none-match": checksum, - "Cookie": cookies.join(" ") - }, withCredentials: true, httpsAgent, - validateStatus: function (status) { - return status < 500; - } - } - ); - cookies = updateCookies(respdata.headers['set-cookie'] || [], server); - if (respdata.status === 202) { - // The schema is being recalculated so we need to make another call to get it - respdata = await axios.request( - { - method: "GET", - url: url, - withCredentials: true, - httpsAgent, - headers: { - "Cookie": cookies.join(" ") - } + headers: { + Cookie: cookies.join(" "), }, - ); - updateCookies(respdata.headers['set-cookie'] || [], server); + }); + updateCookies(respdata.headers["set-cookie"] || [], server); return respdata; - } - else if (respdata.status === 304) { + } else if (respdata.status === 304) { // The schema hasn't changed return undefined; - } - else if (respdata.status === 401) { + } else if (respdata.status === 401) { // Either we had no cookies or they expired, so resend the request with basic auth - respdata = await axios.request( - { + respdata = await axios.request({ + method: "GET", + url: url, + headers: { + "if-none-match": checksum, + }, + auth: { + username: server.username, + password: server.password, + }, + withCredentials: true, + httpsAgent, + }); + cookies = updateCookies(respdata.headers["set-cookie"] || [], server); + if (respdata.status === 202) { + // The schema is being recalculated so we need to make another call to get it + respdata = await axios.request({ method: "GET", url: url, + withCredentials: true, + httpsAgent, headers: { - "if-none-match": checksum - }, - auth: { - username: server.username, - password: server.password + Cookie: cookies.join(" "), }, - withCredentials: true, - httpsAgent - } - ); - cookies = updateCookies(respdata.headers['set-cookie'] || [], server); - if (respdata.status === 202) { - // The schema is being recalculated so we need to make another call to get it - respdata = await axios.request( - { - method: "GET", - url: url, - withCredentials: true, - httpsAgent, - headers: { - "Cookie": cookies.join(" ") - } - } - ); - updateCookies(respdata.headers['set-cookie'] || [], server); + }); + updateCookies(respdata.headers["set-cookie"] || [], server); return respdata; - } - else if (respdata.status === 304) { + } else if (respdata.status === 304) { // The schema hasn't changed return undefined; - } - else { + } else { // We got the schema return respdata; } - } - else { + } else { // We got the schema return respdata; } - } - else { + } else { // This is a different request if (data !== undefined) { - respdata = await axios.request( - { + respdata = await axios.request({ + method: method, + url: url, + data: data, + headers: { + "Content-Type": "application/json", + Cookie: cookies.join(" "), + }, + withCredentials: true, + httpsAgent, + validateStatus: function (status) { + return status < 500; + }, + }); + if (respdata.status === 401) { + // Either we had no cookies or they expired, so resend the request with basic auth + + respdata = await axios.request({ method: method, url: url, data: data, headers: { - 'Content-Type': 'application/json', - "Cookie": cookies.join(" ") + "Content-Type": "application/json", + }, + auth: { + username: server.username, + password: server.password, }, withCredentials: true, httpsAgent, - validateStatus: function (status) { - return status < 500; - } - } - ); - if (respdata.status === 401) { + }); + } + updateCookies(respdata.headers["set-cookie"] || [], server); + } else { + respdata = await axios.request({ + method: method, + url: url, + withCredentials: true, + httpsAgent, + params: params, + headers: { + Cookie: cookies.join(" "), + }, + validateStatus: function (status) { + return status < 500; + }, + }); + if (respdata.status === 401 && api) { + // api is only 0 when calling HEAD / to log out of the session // Either we had no cookies or they expired, so resend the request with basic auth - respdata = await axios.request( - { - method: method, - url: url, - data: data, - headers: { - 'Content-Type': 'application/json' - }, - auth: { - username: server.username, - password: server.password - }, - withCredentials: true, - httpsAgent - } - ); - } - updateCookies(respdata.headers['set-cookie'] || [], server); - } - else { - respdata = await axios.request( - { + respdata = await axios.request({ method: method, url: url, + auth: { + username: server.username, + password: server.password, + }, withCredentials: true, httpsAgent, params: params, - headers: { - "Cookie": cookies.join(" ") - }, - validateStatus: function (status) { - return status < 500; - } - } - ); - if (respdata.status === 401 && api) { // api is only 0 when calling HEAD / to log out of the session - // Either we had no cookies or they expired, so resend the request with basic auth - - respdata = await axios.request( - { - method: method, - url: url, - auth: { - username: server.username, - password: server.password - }, - withCredentials: true, - httpsAgent, - params: params - } - ); + }); } - updateCookies(respdata.headers['set-cookie'] || [], server); + updateCookies(respdata.headers["set-cookie"] || [], server); } return respdata; } } catch (error) { - client.warn(`Error making REST request ${method} ${path}: ${typeof error == "string" - ? error - : error instanceof Error - ? error.toString() - : JSON.stringify(error) - }`); + client.warn( + `Error making REST request ${method} ${path}: ${ + typeof error == "string" ? error : error instanceof Error ? error.toString() : JSON.stringify(error) + }`, + ); return undefined; } -}; +} diff --git a/client/src/requestForwarding.ts b/client/src/requestForwarding.ts index 2491f43..7ad2796 100644 --- a/client/src/requestForwarding.ts +++ b/client/src/requestForwarding.ts @@ -1,20 +1,29 @@ -import { commands, CompletionList, Hover, Position, ProviderResult, SignatureHelp, TextDocumentContentProvider, Uri } from 'vscode'; -import { Middleware } from 'vscode-languageclient'; -import { client } from './extension'; +import { + commands, + CompletionList, + Hover, + Position, + ProviderResult, + SignatureHelp, + TextDocumentContentProvider, + Uri, +} from "vscode"; +import { Middleware } from "vscode-languageclient"; +import { client } from "./extension"; export const requestForwardingMiddleware: Middleware = { provideCompletionItem: async (document, position, context, token, next) => { // If not in a class or CSP file, do not attempt request forwarding - if (!["objectscript-class","objectscript-csp"].includes(document.languageId)) { + if (!["objectscript-class", "objectscript-csp"].includes(document.languageId)) { return await next(document, position, context, token); } const originalUri = document.uri.toString(true); - const language: number = await client.sendRequest("intersystems/embedded/languageAtPosition",{ + const language: number = await client.sendRequest("intersystems/embedded/languageAtPosition", { textDocument: { - uri: originalUri + uri: originalUri, }, - position: position + position: position, }); let vdocExt: string = ""; @@ -32,14 +41,14 @@ export const requestForwardingMiddleware: Middleware = { if (vdocExt != "") { // Forward the request const vdocUriString = `isc-embedded-content://${language}:${position.line}-${position.character}/${encodeURIComponent( - originalUri + originalUri, )}.${vdocExt}`; const vdocUri = Uri.parse(vdocUriString); return await commands.executeCommand( "vscode.executeCompletionItemProvider", vdocUri, position, - context.triggerCharacter + context.triggerCharacter, ); } else { // Do not forward the request @@ -55,16 +64,16 @@ export const requestForwardingMiddleware: Middleware = { }, provideHover: async (document, position, token, next) => { // If not in a class or CSP file, do not attempt request forwarding - if (!["objectscript-class","objectscript-csp"].includes(document.languageId)) { + if (!["objectscript-class", "objectscript-csp"].includes(document.languageId)) { return await next(document, position, token); } const originalUri = document.uri.toString(true); - const language: number = await client.sendRequest("intersystems/embedded/languageAtPosition",{ + const language: number = await client.sendRequest("intersystems/embedded/languageAtPosition", { textDocument: { - uri: originalUri + uri: originalUri, }, - position: position + position: position, }); let vdocExt: string = ""; @@ -81,14 +90,12 @@ export const requestForwardingMiddleware: Middleware = { if (vdocExt != "") { // Forward the request const vdocUriString = `isc-embedded-content://${language}:${position.line}-${position.character}/${encodeURIComponent( - originalUri + originalUri, )}.${vdocExt}`; const vdocUri = Uri.parse(vdocUriString); - return await commands.executeCommand( - "vscode.executeHoverProvider", - vdocUri, - position - ).then((hovers) => Array.isArray(hovers) && hovers.length ? hovers[0] : undefined); + return await commands + .executeCommand("vscode.executeHoverProvider", vdocUri, position) + .then((hovers) => (Array.isArray(hovers) && hovers.length ? hovers[0] : undefined)); } else { // Do not forward the request return await next(document, position, token); @@ -96,16 +103,16 @@ export const requestForwardingMiddleware: Middleware = { }, provideSignatureHelp: async (document, position, context, token, next) => { // If not in a class or CSP file, do not attempt request forwarding - if (!["objectscript-class","objectscript-csp"].includes(document.languageId)) { + if (!["objectscript-class", "objectscript-csp"].includes(document.languageId)) { return await next(document, position, context, token); } const originalUri = document.uri.toString(true); - const language: number = await client.sendRequest("intersystems/embedded/languageAtPosition",{ + const language: number = await client.sendRequest("intersystems/embedded/languageAtPosition", { textDocument: { - uri: originalUri + uri: originalUri, }, - position: position + position: position, }); let vdocExt: string = ""; @@ -116,51 +123,49 @@ export const requestForwardingMiddleware: Middleware = { if (vdocExt != "") { // Forward the request const vdocUriString = `isc-embedded-content://${language}:${position.line}-${position.character}/${encodeURIComponent( - originalUri + originalUri, )}.${vdocExt}`; const vdocUri = Uri.parse(vdocUriString); return await commands.executeCommand( "vscode.executeSignatureHelpProvider", vdocUri, position, - context.triggerCharacter + context.triggerCharacter, ); } else { // Do not forward the request return await next(document, position, context, token); } - } + }, }; export class ISCEmbeddedContentProvider implements TextDocumentContentProvider { - constructor() {} provideTextDocumentContent(uri: Uri): ProviderResult { // Get the isclexer language number and position from the URI authority const language: number = Number(uri.authority.split(":")[0]); const positionText = uri.authority.split(":")[1]; - const position = new Position(Number(positionText.split("-")[0]),Number(positionText.split("-")[1])); + const position = new Position(Number(positionText.split("-")[0]), Number(positionText.split("-")[1])); // Use the language number to isolate the original URI let originalUri: string; if (language == 11) { // Language is JavaScript so the extension is .js - originalUri = uri.path.slice(1).slice(0,-3); + originalUri = uri.path.slice(1).slice(0, -3); } else if (language == 5) { // Language is HTML so the extension is .html - originalUri = uri.path.slice(1).slice(0,-5); + originalUri = uri.path.slice(1).slice(0, -5); } else if (language == 15) { // Language is CSS so the extension is .css - originalUri = uri.path.slice(1).slice(0,-4); + originalUri = uri.path.slice(1).slice(0, -4); } if (originalUri) { // Ask the server to isolate the embedded language - return client.sendRequest("intersystems/embedded/isolateEmbeddedLanguage",{ + return client.sendRequest("intersystems/embedded/isolateEmbeddedLanguage", { uri: decodeURIComponent(originalUri), language: language, - position: position + position: position, }); } } - } diff --git a/server/lib/isclexer.node.d.ts b/server/lib/isclexer.node.d.ts index f951c38..827b504 100644 --- a/server/lib/isclexer.node.d.ts +++ b/server/lib/isclexer.node.d.ts @@ -1,40 +1,39 @@ - /** * A language supported by the isclexer module. */ export interface Language { - /** The language's text-based identifier. */ - moniker: string; - /** The language's numerical index. */ - index: number; + /** The language's text-based identifier. */ + moniker: string; + /** The language's numerical index. */ + index: number; } /** * A semantic token with source text. */ export interface TextSemanticToken { - /** The numerical index of the language. */ - l: number; - /** The index of the attribute within the array returned by `GetLanguageAttributes()`. */ - s: number; - /** The source of the token. */ - t: string; + /** The numerical index of the language. */ + l: number; + /** The index of the attribute within the array returned by `GetLanguageAttributes()`. */ + s: number; + /** The source of the token. */ + t: string; } /** * A semantic token with position and length within the source text. */ export interface PositionSemanticToken { - /** The numerical index of the language. */ - l: number; - /** The index of the attribute within the array returned by `GetLanguageAttributes()`. */ - s: number; - /** The starting position of this token in the source line. */ - p: number; - /** The length of this token's source. */ - c: number; - /** A short description of the syntax error. */ - e?: string; + /** The numerical index of the language. */ + l: number; + /** The index of the attribute within the array returned by `GetLanguageAttributes()`. */ + s: number; + /** The starting position of this token in the source line. */ + p: number; + /** The length of this token's source. */ + c: number; + /** A short description of the syntax error. */ + e?: string; } /** @@ -44,14 +43,14 @@ export function GetLanguages(): Language[]; /** * Get an array of all attributes for the given language. - * + * * @param moniker A language moniker returned from `GetLanguages()`. */ export function GetLanguageAttributes(moniker: string): string[]; /** * Compute the semantic tokens for `source` using the lexer for language `moniker`. - * + * * @param source The source code to tokenize. * @param moniker The language moniker of `source`. Must be a language moniker returned from `GetLanguages()`. * @param tokentext Return the corresponding source text for each token. Defaults to `false`. Note that setting this to `true` decreases performance. @@ -59,9 +58,27 @@ export function GetLanguageAttributes(moniker: string): string[]; * @param omitwhitespace Omit white space tokens. Defaults to `true`. * @returns An array of arrays of semantic tokens. The outer array corresponds to the lines of `source` and the inner array corresponds to tokens on that line. */ -export function Tokenize(source: string, moniker: string, tokentext: false, flags?: number, omitwhitespace?: boolean): PositionSemanticToken[][]; -export function Tokenize(source: string, moniker: string, tokentext: true, flags?: number, omitwhitespace?: boolean): TextSemanticToken[][]; -export function Tokenize(source: string, moniker: string, tokentext?: boolean, flags?: number, omitwhitespace?: boolean): PositionSemanticToken[][]; +export function Tokenize( + source: string, + moniker: string, + tokentext: false, + flags?: number, + omitwhitespace?: boolean, +): PositionSemanticToken[][]; +export function Tokenize( + source: string, + moniker: string, + tokentext: true, + flags?: number, + omitwhitespace?: boolean, +): TextSemanticToken[][]; +export function Tokenize( + source: string, + moniker: string, + tokentext?: boolean, + flags?: number, + omitwhitespace?: boolean, +): PositionSemanticToken[][]; /** * Print help text. diff --git a/server/src/parse/parse.ts b/server/src/parse/parse.ts index 3cf9409..0b4e544 100644 --- a/server/src/parse/parse.ts +++ b/server/src/parse/parse.ts @@ -1,8 +1,8 @@ -import { compressedresult, compressedline } from '../utils/types' -import { colorRoutineLine, isRoutineHeader, routineheadertype } from './routineheader/parseroutineheader'; -import { SemanticTokensLegend } from 'vscode-languageserver'; -import { GetLanguageAttributes, Tokenize } from '../../lib/isclexer.node'; -import { lexerLanguages } from '../utils/variables'; +import { compressedresult, compressedline } from "../utils/types"; +import { colorRoutineLine, isRoutineHeader, routineheadertype } from "./routineheader/parseroutineheader"; +import { SemanticTokensLegend } from "vscode-languageserver"; +import { GetLanguageAttributes, Tokenize } from "../../lib/isclexer.node"; +import { lexerLanguages } from "../utils/variables"; // Set this to false if routines stop using the ROUTINE header line const acceptroutineheaderline = true; @@ -22,17 +22,14 @@ export function parseDocument(languageId: string, fileExt: string, text: string) let moniker = "COS"; let flags = STANDARDPARSEFLAGS; if (languageId === "objectscript-class") { - moniker = "CLS" - } - else if (languageId === "objectscript-csp") { + moniker = "CLS"; + } else if (languageId === "objectscript-csp") { moniker = "HTML"; - } - else if (languageId === "objectscript-int" || (languageId === "objectscript" && fileExt === "int")) { + } else if (languageId === "objectscript-int" || (languageId === "objectscript" && fileExt === "int")) { moniker = "INT"; } if (acceptroutineheaderline && (moniker === "COS" || moniker === "INT") && isRoutineHeader(text)) { - // extract the routine header line (without the line-ending) const firstline = getFirstLine(text); @@ -45,40 +42,36 @@ export function parseDocument(languageId: string, fileExt: string, text: string) } // effectively replace the routine line (before the line-ending) with spaces so that the offsets are still correct - const doctoparse = ' '.repeat(firstline.length) + text.slice(firstline.length); + const doctoparse = " ".repeat(firstline.length) + text.slice(firstline.length); // parse the rest of the document using Studio libraries - const restcolors: compressedline[] = Tokenize(doctoparse,moniker,false,flags); + const restcolors: compressedline[] = Tokenize(doctoparse, moniker, false, flags); // at this point the original restcolors[0] will be either an array with a single whitespace item or an empty array // - either way, overwriting that array with the routine line coloring works restcolors[0] = routinelinecoloring.compressedline; - return {compressedlinearray: restcolors, routineheaderinfo: routinelinecoloring.routineheaderinfo}; + return { compressedlinearray: restcolors, routineheaderinfo: routinelinecoloring.routineheaderinfo }; + } else { + flags += moniker === "HTML" ? IPARSE_ALL_CSPEXTENSIONS + IPARSE_HTML_CSPMODE : 0; + return { compressedlinearray: Tokenize(text, moniker, false, flags) }; } - else { - flags += (moniker === "HTML") ? (IPARSE_ALL_CSPEXTENSIONS + IPARSE_HTML_CSPMODE) : 0; - return {compressedlinearray: Tokenize(text,moniker,false,flags)}; - } - } function getFirstLine(documenttext: string): string { - - const poslf = documenttext.indexOf('\n'); + const poslf = documenttext.indexOf("\n"); if (poslf === -1) { return documenttext; // no linefeed => first line is the whole document } - if (poslf > 0 && documenttext.charAt(poslf-1) === '\r') { - return documenttext.slice(0,poslf-1); // CRLF so return up to but not including the CR - } - else { - return documenttext.slice(0,poslf); // LF so return up to but not including the LF + if (poslf > 0 && documenttext.charAt(poslf - 1) === "\r") { + return documenttext.slice(0, poslf - 1); // CRLF so return up to but not including the CR + } else { + return documenttext.slice(0, poslf); // LF so return up to but not including the LF } } -const languageoffsets: {[index:number] : number} = {}; +const languageoffsets: { [index: number]: number } = {}; export function getLegend(): SemanticTokensLegend { const legend: string[] = []; @@ -87,16 +80,16 @@ export function getLegend(): SemanticTokensLegend { languageoffsets[lang.index] = legendoffset; const attrs: string[] = GetLanguageAttributes(lang.moniker); for (const attr of attrs) { - legend.push(`${lang.moniker}_${attr.replace(/[^a-z0-9+]+/gi,"")}`); + legend.push(`${lang.moniker}_${attr.replace(/[^a-z0-9+]+/gi, "")}`); } legendoffset += attrs.length; } return { tokenTypes: legend, - tokenModifiers: [] + tokenModifiers: [], }; } export function lookupattr(languageindex: number, attrindex: number): number { - return languageoffsets[languageindex] + attrindex; + return languageoffsets[languageindex] + attrindex; } diff --git a/server/src/parse/routineheader/linesource.ts b/server/src/parse/routineheader/linesource.ts index 40de394..160aad7 100644 --- a/server/src/parse/routineheader/linesource.ts +++ b/server/src/parse/routineheader/linesource.ts @@ -1,164 +1,161 @@ - import { cos_langindex, error_attrindex } from "../../utils/languageDefinitions"; import { isWhitespace } from "./routineheaderutils"; import { compressedline, compresseditem } from "../../utils/types"; - /** * Support token reading and coloring for a single line to be parsed. */ export class LineSource { - - public constructor(line: string) { - this.line = line; - this.pos = 0; - this.markedpos = 0; - this.coloring = []; - this.anycoloringerrors = false; - } - - /** - * Skip over any whitespace (spaces and tabs). - */ - public skipWhitespace() { - if (this.markedpos != this.pos) { - throw Error('LineSource.skipWhitespace: marked position was not updated'); - } - while (!this.ended() && isWhitespace(this.currentChar())) { - ++this.pos; - } - this.markedpos = this.pos; - } - - /** - * Return true if we're at the end of the line. - */ - public ended(): boolean { - return this.pos === this.line.length; - } - - /** - * Return the character at the current position. - */ - public currentChar(): string { - return this.line.charAt(this.pos); - } - - /** - * Skips over offset characters - throws an exception if there aren't at least that many characters left. - * @param offset how many characters to skip over. - */ - public advance(offset: number) { - if (this.pos+offset > this.line.length) { - throw Error('LineSource.advance: advancing past end of line'); - } - this.pos += offset; - } - - /** - * Skip to the end of the line. - */ - public toEnd() { - this.advance(this.line.length - this.pos); - } - - /** - * Return the current position - use this to detect no progress. - */ - public getPos(): number { - return this.pos; - } - - /** - * Return the uncolored part of the line up to the current position. - */ - public getToken(): string { - return this.line.substring(this.markedpos,this.pos); - } - - /** - * Return whether there is any uncolored text up to the current position. - */ - public anyToken(): boolean { - return this.pos > this.markedpos; - } - - /** - * Color the current token as error. - */ - public commitError(error) { - this.commitToken(error_attrindex); - if (error instanceof Error && error.message.length) this.coloring[this.coloring.length-1].e = error.message; - } - - /** - * Color the current token. - * @param attrindex the color - */ - public commitToken(attrindex: number) { - if (this.markedpos === this.pos) { - throw Error('LineSource.commitToken: region to color is empty'); - } - this.coloring.push(this.coloringFor(this.markedpos,this.pos,attrindex)); - if (attrindex === error_attrindex) { - this.anycoloringerrors = true; - } - this.markedpos = this.pos; - } - - /** - * Re-color the most-recently-colored token as error. - */ - public colorLastAsError(error) { - if (this.coloring.length === 0) { - throw Error('LineSource.colorLastAsError: no coloring available to change'); - } - this.coloring[this.coloring.length-1].s = error_attrindex; - if (error instanceof Error && error.message.length) this.coloring[this.coloring.length-1].e = error.message; - this.anycoloringerrors = true; - } - - /** - * Return the coloring for the line - this should normally be used when the whole line has been colored. - */ - public getColoring(): compressedline { - return this.coloring; - } - - public anyErrors(): boolean { - return this.anycoloringerrors; - } - - public toString(): string { - return this.line + ' @' + this.markedpos + '-' + this.pos; - } - - private coloringFor(startpos: number, afterendpos: number, attrindex: number): compresseditem { - return {'p': startpos, 'c': (afterendpos-startpos), 'l': cos_langindex, 's': attrindex}; - } - - /** - * The original line. - */ - private line: string; - - /** - * The current position in the line (start of line is at position 0). - */ - private pos: number; - - /** - * The position after the latest character which has been colored. - */ - private markedpos: number; - - /** - * The coloring for the line. - */ - private coloring: compressedline; - - /** - * Whether any characters have been colored as syntax errors. - */ - private anycoloringerrors: boolean; + public constructor(line: string) { + this.line = line; + this.pos = 0; + this.markedpos = 0; + this.coloring = []; + this.anycoloringerrors = false; + } + + /** + * Skip over any whitespace (spaces and tabs). + */ + public skipWhitespace() { + if (this.markedpos != this.pos) { + throw Error("LineSource.skipWhitespace: marked position was not updated"); + } + while (!this.ended() && isWhitespace(this.currentChar())) { + ++this.pos; + } + this.markedpos = this.pos; + } + + /** + * Return true if we're at the end of the line. + */ + public ended(): boolean { + return this.pos === this.line.length; + } + + /** + * Return the character at the current position. + */ + public currentChar(): string { + return this.line.charAt(this.pos); + } + + /** + * Skips over offset characters - throws an exception if there aren't at least that many characters left. + * @param offset how many characters to skip over. + */ + public advance(offset: number) { + if (this.pos + offset > this.line.length) { + throw Error("LineSource.advance: advancing past end of line"); + } + this.pos += offset; + } + + /** + * Skip to the end of the line. + */ + public toEnd() { + this.advance(this.line.length - this.pos); + } + + /** + * Return the current position - use this to detect no progress. + */ + public getPos(): number { + return this.pos; + } + + /** + * Return the uncolored part of the line up to the current position. + */ + public getToken(): string { + return this.line.substring(this.markedpos, this.pos); + } + + /** + * Return whether there is any uncolored text up to the current position. + */ + public anyToken(): boolean { + return this.pos > this.markedpos; + } + + /** + * Color the current token as error. + */ + public commitError(error) { + this.commitToken(error_attrindex); + if (error instanceof Error && error.message.length) this.coloring[this.coloring.length - 1].e = error.message; + } + + /** + * Color the current token. + * @param attrindex the color + */ + public commitToken(attrindex: number) { + if (this.markedpos === this.pos) { + throw Error("LineSource.commitToken: region to color is empty"); + } + this.coloring.push(this.coloringFor(this.markedpos, this.pos, attrindex)); + if (attrindex === error_attrindex) { + this.anycoloringerrors = true; + } + this.markedpos = this.pos; + } + + /** + * Re-color the most-recently-colored token as error. + */ + public colorLastAsError(error) { + if (this.coloring.length === 0) { + throw Error("LineSource.colorLastAsError: no coloring available to change"); + } + this.coloring[this.coloring.length - 1].s = error_attrindex; + if (error instanceof Error && error.message.length) this.coloring[this.coloring.length - 1].e = error.message; + this.anycoloringerrors = true; + } + + /** + * Return the coloring for the line - this should normally be used when the whole line has been colored. + */ + public getColoring(): compressedline { + return this.coloring; + } + + public anyErrors(): boolean { + return this.anycoloringerrors; + } + + public toString(): string { + return this.line + " @" + this.markedpos + "-" + this.pos; + } + + private coloringFor(startpos: number, afterendpos: number, attrindex: number): compresseditem { + return { p: startpos, c: afterendpos - startpos, l: cos_langindex, s: attrindex }; + } + + /** + * The original line. + */ + private line: string; + + /** + * The current position in the line (start of line is at position 0). + */ + private pos: number; + + /** + * The position after the latest character which has been colored. + */ + private markedpos: number; + + /** + * The coloring for the line. + */ + private coloring: compressedline; + + /** + * Whether any characters have been colored as syntax errors. + */ + private anycoloringerrors: boolean; } diff --git a/server/src/parse/routineheader/parseroutineheader.ts b/server/src/parse/routineheader/parseroutineheader.ts index 5dfc578..faade1b 100644 --- a/server/src/parse/routineheader/parseroutineheader.ts +++ b/server/src/parse/routineheader/parseroutineheader.ts @@ -1,292 +1,264 @@ - -import { compressedline, routineheaderinfotype } from '../../utils/types'; +import { compressedline, routineheaderinfotype } from "../../utils/types"; import { validInRoutineName, isValidRoutineName, isWhitespace, keywordstype } from "./routineheaderutils"; import { LineSource } from "./linesource"; import { validateKeywordValue, validateKeyword } from "./validate"; -import { cos_command_attrindex, cos_delim_attrindex, cos_rtnname_attrindex } from '../../utils/languageDefinitions'; - +import { cos_command_attrindex, cos_delim_attrindex, cos_rtnname_attrindex } from "../../utils/languageDefinitions"; /** * Return true if documenttext begins with a routine header line * @param documenttext at least the first line of the document */ export function isRoutineHeader(documenttext: string): boolean { - return documenttext.toUpperCase().startsWith(ROUTINEWORD) && - documenttext.length >= (ROUTINEWORD.length+1) && - isWhitespace(documenttext.charAt(ROUTINEWORD.length)); + return ( + documenttext.toUpperCase().startsWith(ROUTINEWORD) && + documenttext.length >= ROUTINEWORD.length + 1 && + isWhitespace(documenttext.charAt(ROUTINEWORD.length)) + ); } - -export type routineheadertype = {'compressedline': compressedline, 'routineheaderinfo'?: routineheaderinfotype}; - +export type routineheadertype = { compressedline: compressedline; routineheaderinfo?: routineheaderinfotype }; /** * Return a compressedline with coloring for the routine header line and (if there are no syntax errors) a structure summarizing the routine header. - * + * * @param routineline the first line of the document, not including a line terminator */ export function colorRoutineLine(routineline: string): routineheadertype { - - const linesource: LineSource = new LineSource(routineline); - - // color the routine line, catching exceptions thrown by the parser - let routineheaderinfo: routineheaderinfotype = {'routinename': ''}; - let rubbishError: any = new Error("Superfluous text follows correct code"); - try { - routineheaderinfo = colorRoutineLineImpl(linesource); - } - catch (error) { - - // if we're at the end of the line source .. - // (if we're NOT at the end then the 'trailing rubbish' check below will color it as error) - if (linesource.ended()) { - - // re-color the last token as an error - linesource.colorLastAsError(error); - } - rubbishError = error; - } - - /// check for trailing rubbish - linesource.skipWhitespace(); - if (!linesource.ended()) { - - // color any rubbish as error - linesource.toEnd(); - linesource.commitError(rubbishError); - } - - const result: routineheadertype = {'compressedline': linesource.getColoring()}; - if (!linesource.anyErrors()) { - result.routineheaderinfo = routineheaderinfo; - } - - return result; + const linesource: LineSource = new LineSource(routineline); + + // color the routine line, catching exceptions thrown by the parser + let routineheaderinfo: routineheaderinfotype = { routinename: "" }; + let rubbishError: any = new Error("Superfluous text follows correct code"); + try { + routineheaderinfo = colorRoutineLineImpl(linesource); + } catch (error) { + // if we're at the end of the line source .. + // (if we're NOT at the end then the 'trailing rubbish' check below will color it as error) + if (linesource.ended()) { + // re-color the last token as an error + linesource.colorLastAsError(error); + } + rubbishError = error; + } + + /// check for trailing rubbish + linesource.skipWhitespace(); + if (!linesource.ended()) { + // color any rubbish as error + linesource.toEnd(); + linesource.commitError(rubbishError); + } + + const result: routineheadertype = { compressedline: linesource.getColoring() }; + if (!linesource.anyErrors()) { + result.routineheaderinfo = routineheaderinfo; + } + + return result; } - // -- - -const ROUTINEWORD = 'ROUTINE'; - +const ROUTINEWORD = "ROUTINE"; /** * Return routine header info, or throw an exception on bad coloring. - * + * * @param linesource with the routine line */ function colorRoutineLineImpl(linesource: LineSource): routineheaderinfotype { - - // routineline: "ROUTINE" WS WS* ROUTINENAME WS* options? - // options: "[" opt/"," "]" - // opt: NAME "=" VALUE + // routineline: "ROUTINE" WS WS* ROUTINENAME WS* options? + // options: "[" opt/"," "]" + // opt: NAME "=" VALUE // WS: " " | "\t" - // ROUTINENAME: (see isValidRoutineName below) - - // parse ROUTINE keyword - linesource.advance(ROUTINEWORD.length); - linesource.commitToken(cos_command_attrindex); - - // parse routine name - linesource.skipWhitespace(); - const routinename = parseRoutineName(linesource); - const routineheaderinfo: routineheaderinfotype = {'routinename': routinename}; - - // parse options if there's a '[' .. - linesource.skipWhitespace(); - if (!linesource.ended() && linesource.currentChar() === '[') { - - // cross the '[' - linesource.advance(1); - linesource.commitToken(cos_delim_attrindex ); - - // parse the comma-delimited list of options - parseOptionsList(linesource,routineheaderinfo); - - // if there's a ']' - if (!linesource.ended() && linesource.currentChar() === ']') { - - // cross the ']' - linesource.advance(1); - linesource.commitToken(cos_delim_attrindex ); - } - else { - throw Error('Expected "]"'); - } - } - - return routineheaderinfo; + // ROUTINENAME: (see isValidRoutineName below) + + // parse ROUTINE keyword + linesource.advance(ROUTINEWORD.length); + linesource.commitToken(cos_command_attrindex); + + // parse routine name + linesource.skipWhitespace(); + const routinename = parseRoutineName(linesource); + const routineheaderinfo: routineheaderinfotype = { routinename: routinename }; + + // parse options if there's a '[' .. + linesource.skipWhitespace(); + if (!linesource.ended() && linesource.currentChar() === "[") { + // cross the '[' + linesource.advance(1); + linesource.commitToken(cos_delim_attrindex); + + // parse the comma-delimited list of options + parseOptionsList(linesource, routineheaderinfo); + + // if there's a ']' + if (!linesource.ended() && linesource.currentChar() === "]") { + // cross the ']' + linesource.advance(1); + linesource.commitToken(cos_delim_attrindex); + } else { + throw Error('Expected "]"'); + } + } + + return routineheaderinfo; } - /** * Parse and return the routine name at the current offset in linesource, or throw an exception if there is no routine name. * @param linesource with the routine line */ function parseRoutineName(linesource: LineSource): string { - - // fetch name - linesource.skipWhitespace(); - while (!linesource.ended() && validInRoutineName(linesource.currentChar())) { - linesource.advance(1); - } - - if (!linesource.anyToken()) { - throw Error("Invalid routine name"); - } - - const routinename: string = linesource.getToken(); - - // check name - if (!isValidRoutineName(routinename)) { - linesource.commitError(new Error("Invalid routine name")); - } - else { - linesource.commitToken(cos_rtnname_attrindex); - } - - return routinename; + // fetch name + linesource.skipWhitespace(); + while (!linesource.ended() && validInRoutineName(linesource.currentChar())) { + linesource.advance(1); + } + + if (!linesource.anyToken()) { + throw Error("Invalid routine name"); + } + + const routinename: string = linesource.getToken(); + + // check name + if (!isValidRoutineName(routinename)) { + linesource.commitError(new Error("Invalid routine name")); + } else { + linesource.commitToken(cos_rtnname_attrindex); + } + + return routinename; } - /** * Parse the options list (without the [..]) at the current offset in linesource, or throw an exception if there is a syntax error. * @param linesource with the routine line */ function parseOptionsList(linesource: LineSource, routineheaderinfo: routineheaderinfotype) { - - const seenkeywords = {}; - while (!linesource.ended()) { - - // parse KEY=VALUE - parseKeywordAndValue(linesource,seenkeywords,routineheaderinfo); - - linesource.skipWhitespace(); - if (linesource.ended()) { - throw Error('Syntax error in options list'); - } - - // if it's ']' then we're done (the caller will process the ']') - if (linesource.currentChar() === ']') { - break; - } - - // we expect a ',' here - if (linesource.currentChar() !== ',') { - throw Error('Expected "," in options list'); - } - - // cross and color the ',' - linesource.advance(1); - linesource.commitToken(cos_delim_attrindex ); - } + const seenkeywords = {}; + while (!linesource.ended()) { + // parse KEY=VALUE + parseKeywordAndValue(linesource, seenkeywords, routineheaderinfo); + + linesource.skipWhitespace(); + if (linesource.ended()) { + throw Error("Syntax error in options list"); + } + + // if it's ']' then we're done (the caller will process the ']') + if (linesource.currentChar() === "]") { + break; + } + + // we expect a ',' here + if (linesource.currentChar() !== ",") { + throw Error('Expected "," in options list'); + } + + // cross and color the ',' + linesource.advance(1); + linesource.commitToken(cos_delim_attrindex); + } } - /** * Parse 'NAME=VALUE'. * @param linesource with the routine line */ -function parseKeywordAndValue(linesource: LineSource, seenkeywords: keywordstype, routineheaderinfo: routineheaderinfotype) { - - // parse NAME - const keyword = parseKeyword(linesource,seenkeywords); - - // if there's an '=' .. - linesource.skipWhitespace(); - if (!linesource.ended() && linesource.currentChar() === '=') { - - // cross and color the '=' - linesource.advance(1); - linesource.commitToken(cos_delim_attrindex ); - - // parse VALUE - parseValue(keyword,linesource,routineheaderinfo); // ignore returned VALUE - } - - // .. no '=' .. - else { - - // validate the keyword with no value - validateKeywordValue(linesource,keyword,undefined,routineheaderinfo); - } +function parseKeywordAndValue( + linesource: LineSource, + seenkeywords: keywordstype, + routineheaderinfo: routineheaderinfotype, +) { + // parse NAME + const keyword = parseKeyword(linesource, seenkeywords); + + // if there's an '=' .. + linesource.skipWhitespace(); + if (!linesource.ended() && linesource.currentChar() === "=") { + // cross and color the '=' + linesource.advance(1); + linesource.commitToken(cos_delim_attrindex); + + // parse VALUE + parseValue(keyword, linesource, routineheaderinfo); // ignore returned VALUE + } + + // .. no '=' .. + else { + // validate the keyword with no value + validateKeywordValue(linesource, keyword, undefined, routineheaderinfo); + } } - /** * Parse a NAME and validate it as a keyword, returning the keyword. - * + * * @param linesource with the routine line * @param seenkeywords array of keywords already seen */ function parseKeyword(linesource: LineSource, seenkeywords: keywordstype): string { + // parse the keyword - everything up to a delimiter or whitespace + const keyword = parseToDelimiter(linesource, undefined); // undefined means don't color it - // parse the keyword - everything up to a delimiter or whitespace - const keyword = parseToDelimiter(linesource,undefined); // undefined means don't color it - - // validate the keyword and color it appropriately - validateKeyword(linesource,keyword,seenkeywords); + // validate the keyword and color it appropriately + validateKeyword(linesource, keyword, seenkeywords); - return keyword; + return keyword; } - /** * Parse a VALUE and validate it as a keyword of the given type, returning the value. - * + * * @param keyword with the previously-parsed keyword * @param linesource with the routine line */ function parseValue(keyword: string, linesource: LineSource, routineheaderinfo: routineheaderinfotype): string { - - // parse the value - everything up to a delimiter or whitespace - const value = parseToDelimiter(linesource,undefined); // undefined means don't color it + // parse the value - everything up to a delimiter or whitespace + const value = parseToDelimiter(linesource, undefined); // undefined means don't color it - // validate the value and color it appropriately - validateKeywordValue(linesource,keyword,value,routineheaderinfo); + // validate the value and color it appropriately + validateKeywordValue(linesource, keyword, value, routineheaderinfo); - return value + return value; } - /** * Parse up to (but not including) the next delimiter and return what we parsed, or throw an exception if there is a syntax error. * - a delimiter is one of ",]=" or whitespace. - * + * * @param linesource with the routine line * @param attrindex how to color the crossed tokens - or undefined, in which case this function leaves the token uncolored */ function parseToDelimiter(linesource: LineSource, attrindex: number | undefined): string { - - // note the start position - linesource.skipWhitespace(); - const startpos = linesource.getPos(); - - // cross characters until we see a delimiter or whitespace - while (!linesource.ended()) { - - const c = linesource.currentChar(); - if (c === ',' || c === ']' || c === '=' || isWhitespace(c)) { - break; // quit the while loop - } - - linesource.advance(1); - } - - // if we didn't cross anything .. - if (linesource.getPos() === startpos) { - throw Error('Syntax error'); - } - - // what we crossed - const result = linesource.getToken(); - - // color it if required - if (typeof attrindex !== 'undefined') { - linesource.commitToken(attrindex); - } - - return result; + // note the start position + linesource.skipWhitespace(); + const startpos = linesource.getPos(); + + // cross characters until we see a delimiter or whitespace + while (!linesource.ended()) { + const c = linesource.currentChar(); + if (c === "," || c === "]" || c === "=" || isWhitespace(c)) { + break; // quit the while loop + } + + linesource.advance(1); + } + + // if we didn't cross anything .. + if (linesource.getPos() === startpos) { + throw Error("Syntax error"); + } + + // what we crossed + const result = linesource.getToken(); + + // color it if required + if (typeof attrindex !== "undefined") { + linesource.commitToken(attrindex); + } + + return result; } - diff --git a/server/src/parse/routineheader/routineheaderutils.ts b/server/src/parse/routineheader/routineheaderutils.ts index 1c4bf10..aad2dd7 100644 --- a/server/src/parse/routineheader/routineheaderutils.ts +++ b/server/src/parse/routineheader/routineheaderutils.ts @@ -1,21 +1,18 @@ - /** * Return true if c is valid in a routine name. * @param c the character to test */ export function validInRoutineName(c: string): boolean { - return c === '.' || c === '%' || isStudioScannerAlpha(c) || isUnicodeDigit(c); + return c === "." || c === "%" || isStudioScannerAlpha(c) || isUnicodeDigit(c); } - /** * Return true if name is a valid routine name. * @param name the name to test */ export function isValidRoutineName(name: string): boolean { - // check for empty/dot at end/dotdot - if (name.length===0 || name.endsWith('.') || name.indexOf('..')!=-1) { + if (name.length === 0 || name.endsWith(".") || name.indexOf("..") != -1) { return false; } @@ -26,9 +23,8 @@ export function isValidRoutineName(name: string): boolean { // check the remaining characters for (let index = 1; index < name.length; ++index) { - const c = name.charAt(index); - if (c !== '.' && !isStandardObjectScriptNameTail(c)) { + if (c !== "." && !isStandardObjectScriptNameTail(c)) { return false; } } @@ -36,28 +32,25 @@ export function isValidRoutineName(name: string): boolean { return true; } - /** * Return true if c is a whitespace character - defined as space or tab here. * @param c the character to test */ export function isWhitespace(c: string): boolean { - return c===' ' || c==='\t'; + return c === " " || c === "\t"; } // for tracking which keywords we've seen -export type keywordstype = {UC_TYPE_KEYWORD?: string, UC_LANGUAGEMODE_KEYWORD?: string, UC_TYPE_GENERATED?: string}; - +export type keywordstype = { UC_TYPE_KEYWORD?: string; UC_LANGUAGEMODE_KEYWORD?: string; UC_TYPE_GENERATED?: string }; /** * Return true if c can start an ObjectScript name. * @param c the character to test */ function isStandardObjectScriptNameStart(c: string): boolean { - return isStudioScannerAlpha(c) || c == '%'; + return isStudioScannerAlpha(c) || c == "%"; } - /** * Return true if c can appear after the start of an ObjectScript name. * @param c the character to test @@ -66,7 +59,6 @@ function isStandardObjectScriptNameTail(c: string): boolean { return isStudioScannerAlpha(c) || isUnicodeDigit(c); } - /** * Return true if c is considered a letter by Studio. * @param c the character to test @@ -75,20 +67,18 @@ function isStudioScannerAlpha(c: string): boolean { return isAlpha(c) || c.charCodeAt(0) > 0x80; } - /** * Return true if c is a unicode digit. * @param c the character to test */ function isUnicodeDigit(c: string): boolean { - return c.match('\\d') != null; + return c.match("\\d") != null; } - /** * Return true if c is a letter. * @param c the character to test */ function isAlpha(c: string): boolean { - return c.match('\\w') != null; + return c.match("\\w") != null; } diff --git a/server/src/parse/routineheader/validate.ts b/server/src/parse/routineheader/validate.ts index 527b96c..5991539 100644 --- a/server/src/parse/routineheader/validate.ts +++ b/server/src/parse/routineheader/validate.ts @@ -1,10 +1,7 @@ - import { LineSource } from "./linesource"; import { keywordstype } from "./routineheaderutils"; -import { routineheaderinfotype } from '../../utils/types'; -import { cos_label_attrindex, cos_name_attrindex, cos_number_attrindex } from '../../utils/languageDefinitions'; - - +import { routineheaderinfotype } from "../../utils/types"; +import { cos_label_attrindex, cos_name_attrindex, cos_number_attrindex } from "../../utils/languageDefinitions"; /** * Validate the given keyword and color it as a keyword or as a syntax error, as appropriate. @@ -14,155 +11,137 @@ import { cos_label_attrindex, cos_name_attrindex, cos_number_attrindex } from '. * @param seenkeywords array of keywords already seen (updated here) */ export function validateKeyword(linesource: LineSource, keyword: string, seenkeywords: keywordstype) { - - let syntaxerror; - try { - - const uckeyword = keyword.toUpperCase(); - if (uckeyword !== UCTYPE && uckeyword !== UCLANGUAGEMODE && uckeyword !== UCGENERATED) { - throw Error('Unknown keyword'); - } - - if (uckeyword in seenkeywords) { - throw Error(`${uckeyword[0] + uckeyword.slice(1).toLowerCase()} appears more than once`); - } - - // note that we've seen this keyword - seenkeywords[uckeyword] = ''; - } - catch (error) { - syntaxerror = error; - } - - if (syntaxerror) { - linesource.commitError(syntaxerror); - } - else { - linesource.commitToken(cos_label_attrindex); - } -} - + let syntaxerror; + try { + const uckeyword = keyword.toUpperCase(); + if (uckeyword !== UCTYPE && uckeyword !== UCLANGUAGEMODE && uckeyword !== UCGENERATED) { + throw Error("Unknown keyword"); + } + + if (uckeyword in seenkeywords) { + throw Error(`${uckeyword[0] + uckeyword.slice(1).toLowerCase()} appears more than once`); + } + + // note that we've seen this keyword + seenkeywords[uckeyword] = ""; + } catch (error) { + syntaxerror = error; + } + + if (syntaxerror) { + linesource.commitError(syntaxerror); + } else { + linesource.commitToken(cos_label_attrindex); + } +} /** * Validate the given value and color it as a value or as a syntax error, as appropriate. - * + * * @param linesource with the routine line * @param keyword with the previously-validated keyword * @param value with either the parsed value or undefined if no value */ -export function validateKeywordValue(linesource: LineSource, keyword: string, value: string | undefined, routineheaderinfo: routineheaderinfotype) { - - const uckeyword = keyword.toUpperCase(); - - let syntaxerror; - let attrindex = -1; - - try { - - switch (uckeyword) { - - // TYPE - case UCTYPE: { - - if (typeof value === 'undefined') { - throw Error('Missing value for Type'); - } - - if (!isValidTYPEValue(value)) { - throw Error("Type must be one of MAC, INT, INC, BAS, MVB, or MVI"); - } - - routineheaderinfo.routinetype = value.toUpperCase(); - - attrindex = cos_name_attrindex; - - break; - } - - // LANGUAGEMODE - case UCLANGUAGEMODE: { - - if (typeof value === 'undefined') { - throw Error('Missing value for LanguageMode'); - } - - const valuemode = Number(value); - if (isNaN(valuemode) || valuemode < 0) { - throw Error('LanguageMode must be an integer') - } - - routineheaderinfo.languagemode = valuemode; - - attrindex = cos_number_attrindex; - - break; - } - - // GENERATED - case UCGENERATED: { - - if (typeof value !== 'undefined') { - throw Error('Unexpected value for Generated'); - } - - routineheaderinfo.generated = ''; - - break; - } - - default: { - throw Error('Unknown keyword'); // this shouldn't happen because the keyword should have been validated by the caller - } - } - } - - catch (error) { - syntaxerror = error; - } - - if (typeof value !== 'undefined') { - - if (syntaxerror) { - linesource.commitError(syntaxerror); - } - else { - linesource.commitToken(attrindex); - } - } - else { - - if (syntaxerror) { - throw syntaxerror; - } - } +export function validateKeywordValue( + linesource: LineSource, + keyword: string, + value: string | undefined, + routineheaderinfo: routineheaderinfotype, +) { + const uckeyword = keyword.toUpperCase(); + + let syntaxerror; + let attrindex = -1; + + try { + switch (uckeyword) { + // TYPE + case UCTYPE: { + if (typeof value === "undefined") { + throw Error("Missing value for Type"); + } + + if (!isValidTYPEValue(value)) { + throw Error("Type must be one of MAC, INT, INC, BAS, MVB, or MVI"); + } + + routineheaderinfo.routinetype = value.toUpperCase(); + + attrindex = cos_name_attrindex; + + break; + } + + // LANGUAGEMODE + case UCLANGUAGEMODE: { + if (typeof value === "undefined") { + throw Error("Missing value for LanguageMode"); + } + + const valuemode = Number(value); + if (isNaN(valuemode) || valuemode < 0) { + throw Error("LanguageMode must be an integer"); + } + + routineheaderinfo.languagemode = valuemode; + + attrindex = cos_number_attrindex; + + break; + } + + // GENERATED + case UCGENERATED: { + if (typeof value !== "undefined") { + throw Error("Unexpected value for Generated"); + } + + routineheaderinfo.generated = ""; + + break; + } + + default: { + throw Error("Unknown keyword"); // this shouldn't happen because the keyword should have been validated by the caller + } + } + } catch (error) { + syntaxerror = error; + } + + if (typeof value !== "undefined") { + if (syntaxerror) { + linesource.commitError(syntaxerror); + } else { + linesource.commitToken(attrindex); + } + } else { + if (syntaxerror) { + throw syntaxerror; + } + } } - // -- - // the keywords -const UCTYPE = 'TYPE'; -const UCLANGUAGEMODE = 'LANGUAGEMODE'; -const UCGENERATED = 'GENERATED'; - +const UCTYPE = "TYPE"; +const UCLANGUAGEMODE = "LANGUAGEMODE"; +const UCGENERATED = "GENERATED"; function isValidTYPEValue(value: string): boolean { - - switch (value.toUpperCase()) { - - case 'BAS': - case 'INC': - case 'INT': - case 'MAC': - case 'MVB': - case 'MVI': { - return true; - } - - default: { - return false; - } - } + switch (value.toUpperCase()) { + case "BAS": + case "INC": + case "INT": + case "MAC": + case "MVB": + case "MVI": { + return true; + } + + default: { + return false; + } + } } - diff --git a/server/src/providers/completion.ts b/server/src/providers/completion.ts index e888a81..52c1718 100644 --- a/server/src/providers/completion.ts +++ b/server/src/providers/completion.ts @@ -1,8 +1,43 @@ -import { CompletionItem, CompletionItemKind, CompletionItemTag, CompletionParams, InsertTextFormat, MarkupKind, Position, Range, TextEdit } from 'vscode-languageserver/node'; -import { getServerSpec, getLanguageServerSettings, getMacroContext, makeRESTRequest, normalizeSystemName, getImports, findFullRange, getClassMemberContext, quoteUDLIdentifier, documaticHtmlToMarkdown, determineClassNameParameterClass, storageKeywordsKeyForToken, getParsedDocument, currentClass, normalizeClassname, macroDefToDoc, showInternalForServer } from '../utils/functions'; -import { ServerSpec, QueryData, KeywordDoc, MacroContext, compressedline, LanguageServerConfiguration } from '../utils/types'; -import { documents, corePropertyParams, mppContinue } from '../utils/variables'; -import * as ld from '../utils/languageDefinitions'; +import { + CompletionItem, + CompletionItemKind, + CompletionItemTag, + CompletionParams, + InsertTextFormat, + MarkupKind, + Position, + Range, + TextEdit, +} from "vscode-languageserver/node"; +import { + getServerSpec, + getLanguageServerSettings, + getMacroContext, + makeRESTRequest, + normalizeSystemName, + getImports, + findFullRange, + getClassMemberContext, + quoteUDLIdentifier, + documaticHtmlToMarkdown, + determineClassNameParameterClass, + storageKeywordsKeyForToken, + getParsedDocument, + currentClass, + normalizeClassname, + macroDefToDoc, + showInternalForServer, +} from "../utils/functions"; +import { + ServerSpec, + QueryData, + KeywordDoc, + MacroContext, + compressedline, + LanguageServerConfiguration, +} from "../utils/types"; +import { documents, corePropertyParams, mppContinue } from "../utils/variables"; +import * as ld from "../utils/languageDefinitions"; import structuredSystemVariables from "../documentation/structuredSystemVariables.json"; import systemFunctions from "../documentation/systemFunctions.json"; @@ -21,7 +56,7 @@ import queryKeywords from "../documentation/keywords/Query.json"; import storageKeywords from "../documentation/keywords/Storage.json"; import triggerKeywords from "../documentation/keywords/Trigger.json"; import xdataKeywords from "../documentation/keywords/XData.json"; -import { TextDocument } from 'vscode-languageserver-textdocument'; +import { TextDocument } from "vscode-languageserver-textdocument"; /** * ServerSpec's mapped to the XML assist schema cache for that server. @@ -40,8 +75,8 @@ const sortPrefix = "!!!"; * Mapping between an XML prefix and namespace. */ type PrefixMapping = { - prefix: string, - namespace: string + prefix: string; + namespace: string; }; /** @@ -56,14 +91,13 @@ class Attribute { // The name is the attribute this.name = attrDescriptor; this.moniker = ""; - } - else { + } else { // Split name and moniker on delimiter this.name = attrDescriptor.split("@")[0]; this.moniker = attrDescriptor.split("@")[1]; } } -}; +} /** * XML element. @@ -102,8 +136,7 @@ class Element { const attr = this.attributes.get(attrname); if (attr !== undefined) { return attr.moniker; - } - else { + } else { return ""; } } @@ -117,7 +150,7 @@ class Element { this.addAttribute(attrDescriptor); } } -}; +} /** * The result of a query request. @@ -149,7 +182,7 @@ class SchemaQuery { getElements(): string[] { return this.element.getChildren(); } -}; +} /** * A Studio Assist XML schema. @@ -206,22 +239,17 @@ class Schema { // Set the schema mapping this.pushMapping(prefix, schemaname); - } - else if (inst === "!default-namespace") { + } else if (inst === "!default-namespace") { // Set the schema mapping this.pushMapping("", params); - } - else if (inst === "!default-prefix") { + } else if (inst === "!default-prefix") { defaultPrefix = params; - } - else if (inst === "!checksum") { + } else if (inst === "!checksum") { checksum = params; - } - else { + } else { // Unknown instruction, ignore } - } - else { + } else { // This is an element definition let elementPath: string; let attributes: string = ""; @@ -231,8 +259,7 @@ class Schema { if (baridx !== -1) { elementPath = line.substring(0, baridx); attributes = line.substring(baridx + 1); - } - else { + } else { elementPath = line; } @@ -245,8 +272,7 @@ class Schema { if (elementPath.charAt(0) === "/") { // It's an element this.makeEntry(nsidx, elementPath.slice(1), attributes, 0, this.rootElement.children); - } - else { + } else { // It's a component this.makeEntry(nsidx, elementPath, attributes, 0, this.rootComponent.children); } @@ -256,7 +282,13 @@ class Schema { this.checksum = checksum; } - private makeEntry(nsidx: string, elementPath: string, attributes: string, offset: number, elemCollection: Map) { + private makeEntry( + nsidx: string, + elementPath: string, + attributes: string, + offset: number, + elemCollection: Map, + ) { let elemname: string; let isRef: boolean = false; @@ -273,8 +305,7 @@ class Schema { if (delimidx === -1) { // Not found, that means it's the same namespace elemname = nsidx + ":" + lastelem.slice(1); - } - else { + } else { // Pick out the namespace prefix const prefix = lastelem.substring(1, delimidx - 1); @@ -286,8 +317,7 @@ class Schema { } elemname = nsidx + ":" + lastelem.substring(delimidx + 1); } - } - else { + } else { // It's a regular element elemname = nsidx + ":" + elementPath.slice(offset); } @@ -304,8 +334,7 @@ class Schema { elem = new Element(this, isRef); elem.initialize(attributes); elemCollection.set(elemname, elem); - } - else { + } else { // Build the element name elemname = nsidx + ":" + elementPath.slice(offset, slashidx); @@ -360,8 +389,7 @@ class Schema { internalelemname = internalelemname + entry + ":"; if (delimpos !== -1) { internalelemname = internalelemname + extname.slice(delimpos + 1); - } - else { + } else { internalelemname = internalelemname + extname; } return internalelemname; @@ -399,12 +427,10 @@ class Schema { // The name has no prefix externalelemname = intname.slice(delimpos + 1); return externalelemname; - } - else { + } else { continue; } - } - else { + } else { // It's not the empty prefix externalelemname = mapping.prefix + ":" + intname.slice(delimpos + 1); return externalelemname; @@ -435,8 +461,7 @@ class Schema { let elem: Element | undefined; if (externalPath === "") { elem = this.rootElement; - } - else { + } else { elem = this.findEntry(externalPath, 0, this.rootElement.children); } @@ -468,8 +493,7 @@ class Schema { elem = this.rootComponent.children.get(converted); } } - } - else { + } else { converted = this.convertE2I(externalPath.substring(offset, slashpos)); // Middle piece @@ -479,8 +503,7 @@ class Schema { if (elem.isReference()) { // Re-search from the original offset elem = this.findEntry(externalPath, offset, this.rootComponent.children); - } - else { + } else { // Search from position + 1 elem = this.findEntry(externalPath, slashpos + 1, elem.children); } @@ -488,7 +511,7 @@ class Schema { } return elem; } -}; +} /** * A cache of SASchemas retrieved from an InterSystems server. @@ -504,7 +527,7 @@ class SchemaCache { /** * Get a SASchema object from the cache. - * + * * @param schemaurl The URL of the SASchema to get. */ async getSchema(schemaurl: string): Promise { @@ -515,9 +538,15 @@ class SchemaCache { schema = new Schema(respdata.data.result); this.schemas.set(schemaurl, schema); } - } - else { - const respdata = await makeRESTRequest("GET", 2, "/saschema/" + schemaurl, this.server, undefined, schema.getChecksum()); + } else { + const respdata = await makeRESTRequest( + "GET", + 2, + "/saschema/" + schemaurl, + this.server, + undefined, + schema.getChecksum(), + ); if (respdata !== undefined) { schema = new Schema(respdata.data.result); this.schemas.set(schemaurl, schema); @@ -525,17 +554,23 @@ class SchemaCache { } return schema; } -}; +} /** * Build the list of all full class names for code completion, with import resolution. - * + * * @param doc The TextDocument that we're providing completion suggestions in. * @param parsed The tokenized representation of doc. * @param server The server that doc is associated with. * @param line The line of doc that we're in. */ -async function completionFullClassName(doc: TextDocument, parsed: compressedline[], server: ServerSpec, line: number, settings: LanguageServerConfiguration): Promise { +async function completionFullClassName( + doc: TextDocument, + parsed: compressedline[], + server: ServerSpec, + line: number, + settings: LanguageServerConfiguration, +): Promise { const result: CompletionItem[] = []; // Get the list of imports for resolution @@ -543,9 +578,10 @@ async function completionFullClassName(doc: TextDocument, parsed: compressedline // Get all classes const querydata = { - query: `SELECT dcd.Name, dcd.Deprecated FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?) AS sod, %Dictionary.ClassDefinition AS dcd WHERE sod.Name = dcd.Name||'.cls'${!settings.completion.showDeprecated ? " AND dcd.Deprecated = 0" : "" - }`, - parameters: ["*.cls", 1, 1, 1, 1, 0, settings.completion.showGenerated ? 1 : 0] + query: `SELECT dcd.Name, dcd.Deprecated FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?) AS sod, %Dictionary.ClassDefinition AS dcd WHERE sod.Name = dcd.Name||'.cls'${ + !settings.completion.showDeprecated ? " AND dcd.Deprecated = 0" : "" + }`, + parameters: ["*.cls", 1, 1, 1, 1, 0, settings.completion.showGenerated ? 1 : 0], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -570,10 +606,9 @@ async function completionFullClassName(doc: TextDocument, parsed: compressedline label: displayname, kind: CompletionItemKind.Class, data: ["class", clsobj.Name, doc.uri], - sortText + sortText, }; - } - else { + } else { if (displayname.startsWith("%Library.")) { // Use short form for %Library classes displayname = "%" + displayname.slice(9); @@ -581,7 +616,7 @@ async function completionFullClassName(doc: TextDocument, parsed: compressedline compItem = { label: displayname, kind: CompletionItemKind.Class, - data: ["class", clsobj.Name, doc.uri] + data: ["class", clsobj.Name, doc.uri], }; } if (clsobj.Deprecated) { @@ -591,11 +626,11 @@ async function completionFullClassName(doc: TextDocument, parsed: compressedline } } return result; -}; +} /** * Build the list of all packages for code completion. - * + * * @param server The server that this document is associated with. */ async function completionPackage(server: ServerSpec, settings: LanguageServerConfiguration): Promise { @@ -603,9 +638,10 @@ async function completionPackage(server: ServerSpec, settings: LanguageServerCon // Get all the packages const querydata = { - query: `SELECT DISTINCT $PIECE(dcd.Name,'.',1,$LENGTH(dcd.Name,'.')-1) AS Package FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?) AS sod, %Dictionary.ClassDefinition AS dcd WHERE sod.Name = dcd.Name||'.cls'${!settings.completion.showDeprecated ? " AND dcd.Deprecated = 0" : "" - }`, - parameters: ["*.cls", 1, 1, 1, 1, 0, settings.completion.showGenerated ? 1 : 0] + query: `SELECT DISTINCT $PIECE(dcd.Name,'.',1,$LENGTH(dcd.Name,'.')-1) AS Package FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?) AS sod, %Dictionary.ClassDefinition AS dcd WHERE sod.Name = dcd.Name||'.cls'${ + !settings.completion.showDeprecated ? " AND dcd.Deprecated = 0" : "" + }`, + parameters: ["*.cls", 1, 1, 1, 1, 0, settings.completion.showGenerated ? 1 : 0], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -613,16 +649,16 @@ async function completionPackage(server: ServerSpec, settings: LanguageServerCon result.push({ label: packobj.Package, kind: CompletionItemKind.Module, - data: "package" + data: "package", }); } } return result; -}; +} /** * Build the list of all include files for code completion. - * + * * @param server The server that this document is associated with. */ async function completionInclude(server: ServerSpec): Promise { @@ -631,7 +667,7 @@ async function completionInclude(server: ServerSpec): Promise // Get all inc files const querydata = { query: "SELECT Name FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?)", - parameters: ["*.inc", 1, 1, 1, 1, 0, 0] + parameters: ["*.inc", 1, 1, 1, 1, 0, 0], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -639,29 +675,46 @@ async function completionInclude(server: ServerSpec): Promise result.push({ label: incobj.Name.slice(0, -4), kind: CompletionItemKind.File, - data: "inc" + data: "inc", }); } } return result; -}; +} /** Return a list of globals or routines. Optionally filter using `prefix`. */ async function globalsOrRoutines( - doc: TextDocument, parsed: compressedline[], line: number, token: number, - settings: LanguageServerConfiguration, server: ServerSpec, lineText: string, prefix: string = "" + doc: TextDocument, + parsed: compressedline[], + line: number, + token: number, + settings: LanguageServerConfiguration, + server: ServerSpec, + lineText: string, + prefix: string = "", ): Promise { // Determine if this is a routine or global, and return null if we're in $BITLOGIC - let brk = false, parenLevel = 0, isBitlogic = false, lastCmd = "", isRoutine = false; + let brk = false, + parenLevel = 0, + isBitlogic = false, + lastCmd = "", + isRoutine = false; for (let ln = line; ln >= 0; ln--) { if (!parsed[ln]?.length) continue; - for (let tkn = (ln == line ? token : parsed[ln].length - 1); tkn >= 0; tkn--) { + for (let tkn = ln == line ? token : parsed[ln].length - 1; tkn >= 0; tkn--) { if (parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_delim_attrindex) { const delimtext = doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)); if (delimtext == "(") { parenLevel++; - if (parenLevel > 0 && tkn > 0 && parsed[ln][tkn - 1].l == ld.cos_langindex && parsed[ln][tkn - 1].s == ld.cos_sysf_attrindex) { - const sysf = doc.getText(Range.create(ln, parsed[ln][tkn - 1].p, ln, parsed[ln][tkn - 1].p + parsed[ln][tkn - 1].c)).toLowerCase(); + if ( + parenLevel > 0 && + tkn > 0 && + parsed[ln][tkn - 1].l == ld.cos_langindex && + parsed[ln][tkn - 1].s == ld.cos_sysf_attrindex + ) { + const sysf = doc + .getText(Range.create(ln, parsed[ln][tkn - 1].p, ln, parsed[ln][tkn - 1].p + parsed[ln][tkn - 1].c)) + .toLowerCase(); if (sysf == "$bitlogic") { // Caret inside $BITLOGIC is neither a routine nor global prefix isBitlogic = true; @@ -674,12 +727,13 @@ async function globalsOrRoutines( break; } } - } - else if (delimtext == ")") { + } else if (delimtext == ")") { parenLevel--; } } else if (parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_command_attrindex) { - lastCmd = doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)).toLowerCase(); + lastCmd = doc + .getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) + .toLowerCase(); brk = true; break; } @@ -687,28 +741,35 @@ async function globalsOrRoutines( if (brk) break; } if (isBitlogic) return null; - isRoutine = isRoutine || + isRoutine = + isRoutine || // The character before the caret is part of a label or extrinsic function syntax /[%$\d\p{L}]/u.test(lineText.slice(-2, -1)) || // Routine syntax without a label or extrinsic function syntax can only appear as an argument for a DO or JOB command (["d", "do", "j", "job"].includes(lastCmd) && parenLevel == 0) || // Special case needed since this DO command will be an error token instead of a command token /(^|\s+)do?\s+\^$/i.test(lineText); - const respdata = await makeRESTRequest("POST", 1, "/action/query", server, - isRoutine ? { - query: `SELECT DISTINCT $PIECE(Name,'.',1,$LENGTH(Name,'.')-1) AS Name FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,1,1,1,0,'NOT (Name %PATTERN ''.E1"."0.1"G"1N1".obj"'' AND $LENGTH(Name,''.'') > 3)')`, - parameters: [`${prefix.length ? `${prefix.slice(0, -1)}/` : ""}*.mac,*.int,*.obj`] - } : { - query: "SELECT Name FROM %SYS.GlobalQuery_NameSpaceList(,?,?,,,1,0)", - parameters: [`${prefix}*`, (await showInternalForServer(server)) ? 1 : 0] - } + const respdata = await makeRESTRequest( + "POST", + 1, + "/action/query", + server, + isRoutine + ? { + query: `SELECT DISTINCT $PIECE(Name,'.',1,$LENGTH(Name,'.')-1) AS Name FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,1,1,1,0,'NOT (Name %PATTERN ''.E1"."0.1"G"1N1".obj"'' AND $LENGTH(Name,''.'') > 3)')`, + parameters: [`${prefix.length ? `${prefix.slice(0, -1)}/` : ""}*.mac,*.int,*.obj`], + } + : { + query: "SELECT Name FROM %SYS.GlobalQuery_NameSpaceList(,?,?,,,1,0)", + parameters: [`${prefix}*`, (await showInternalForServer(server)) ? 1 : 0], + }, ); if (Array.isArray(respdata?.data?.result?.content) && respdata.data.result.content.length > 0) { - return respdata.data.result.content.map((item: { Name: string; }) => { + return respdata.data.result.content.map((item: { Name: string }) => { return { label: item.Name.slice(prefix.length), kind: isRoutine ? CompletionItemKind.Function : CompletionItemKind.Variable, - data: isRoutine ? "routine" : "global" + data: isRoutine ? "routine" : "global", }; }); } else { @@ -719,20 +780,31 @@ async function globalsOrRoutines( export async function onCompletion(params: CompletionParams): Promise { let result: CompletionItem[] = []; const doc = documents.get(params.textDocument.uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) { return null; } - if (params.position.line === parsed.length) { return null; } + if (parsed === undefined) { + return null; + } + if (params.position.line === parsed.length) { + return null; + } const server: ServerSpec = await getServerSpec(params.textDocument.uri); const prevline = doc.getText(Range.create(Position.create(params.position.line, 0), params.position)); const prevlineLower = prevline.toLowerCase(); - const classregex = /^class[ ]+%?[\p{L}\d]+(\.{1}[\p{L}\d]+)* +extends[ ]+(\(([%]?[\p{L}\d]+(\.{1}[\p{L}\d]+)*,[ ]*)*)?$/iu; + const classregex = + /^class[ ]+%?[\p{L}\d]+(\.{1}[\p{L}\d]+)* +extends[ ]+(\(([%]?[\p{L}\d]+(\.{1}[\p{L}\d]+)*,[ ]*)*)?$/iu; let firsttwotokens = ""; if (parsed[params.position.line].length >= 2) { - firsttwotokens = doc.getText(Range.create( - params.position.line, parsed[params.position.line][0].p, - params.position.line, parsed[params.position.line][1].p + parsed[params.position.line][1].c - )); + firsttwotokens = doc.getText( + Range.create( + params.position.line, + parsed[params.position.line][0].p, + params.position.line, + parsed[params.position.line][1].p + parsed[params.position.line][1].c, + ), + ); } let thistoken: number = -1; for (let i = 0; i < parsed[params.position.line].length; i++) { @@ -744,13 +816,14 @@ export async function onCompletion(params: CompletionParams): Promise 1 && parsed[ln][0].l == ld.cos_langindex && parsed[ln][0].s == ld.cos_ppc_attrindex) { // This line begins with a preprocessor command - const ppctext = doc.getText(Range.create( - ln, parsed[ln][1].p, - ln, parsed[ln][1].p + parsed[ln][1].c - )).toLowerCase(); + const ppctext = doc + .getText(Range.create(ln, parsed[ln][1].p, ln, parsed[ln][1].p + parsed[ln][1].c)) + .toLowerCase(); if (parsed[ln].length > 3 && ["define", "def1arg"].includes(ppctext)) { // This is a macro definition const macro = doc.getText(Range.create(ln, parsed[ln][2].p, ln, parsed[ln][2].p + parsed[ln][2].c)); @@ -846,22 +915,33 @@ export async function onCompletion(params: CompletionParams): Promise t.l == ld.cls_langindex)) break; } - } - else if (prevline.endsWith("$") && prevline.slice(-2, -1) != "$" && triggerlang === ld.cos_langindex) { + } else if (prevline.endsWith("$") && prevline.slice(-2, -1) != "$" && triggerlang === ld.cos_langindex) { if (prevline.charAt(prevline.length - 2) === "^") { // This is a structured system variable for (const ssv of structuredSystemVariables) { @@ -955,12 +1060,11 @@ export async function onCompletion(params: CompletionParams): Promise 0) { @@ -1041,10 +1148,9 @@ export async function onCompletion(params: CompletionParams): Promise= 0; tkn--) { @@ -1119,21 +1250,20 @@ export async function onCompletion(params: CompletionParams): Promise 0) { @@ -1144,17 +1274,24 @@ export async function onCompletion(params: CompletionParams): PromiseClassType IS NULL OR parent->ClassType != 'datatype')" : "" - }${internalStr}${deprecatedStr}`, - parameters: [membercontext.baseclass] - } + query: `SELECT Name, Description, Origin, Type, Deprecated FROM %Dictionary.CompiledParameter WHERE Parent = ?${ + membercontext.context == "instance" + ? " AND (parent->ClassType IS NULL OR parent->ClassType != 'datatype')" + : "" + }${internalStr}${deprecatedStr}`, + parameters: [membercontext.baseclass], + }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, data); if (Array.isArray(respdata?.data?.result?.content) && respdata.data.result.content.length > 0) { // We got data back @@ -1185,10 +1325,10 @@ export async function onCompletion(params: CompletionParams): PromiseClassType IS NULL OR parent->ClassType != 'datatype')${internalStr}${deprecatedStr}`; data.parameters.push(membercontext.baseclass); // Properties and Parameters - data.query += " UNION ALL %PARALLEL " + + data.query += + " UNION ALL %PARALLEL " + "SELECT Name, Description, Origin, NULL AS FormalSpec, RuntimeType AS Type, 'property' AS MemberType, Deprecated, Aliases " + `FROM %Dictionary.CompiledProperty WHERE Parent = ? AND (parent->ClassType IS NULL OR parent->ClassType != 'datatype')${internalStr}${deprecatedStr} UNION ALL %PARALLEL ` + "SELECT Name, Description, Origin, NULL AS FormalSpec, Type, 'parameter' AS MemberType, Deprecated, NULL AS Aliases " + @@ -1235,7 +1375,8 @@ export async function onCompletion(params: CompletionParams): Promisename||Name AS Name, Description, parent->Origin AS Origin, FormalSpec, ReturnType AS Type, 'method' AS MemberType, Deprecated, NULL AS Aliases " + `FROM %Dictionary.CompiledIndexMethod WHERE parent->Parent = ? AND (parent->parent->ClassType IS NULL OR parent->parent->ClassType != 'datatype')${internalStr}${deprecatedStr} UNION ALL %PARALLEL ` + "SELECT parent->name||Name AS Name, Description, parent->Origin AS Origin, FormalSpec, ReturnType AS Type, 'method' AS MemberType, Deprecated, NULL AS Aliases " + @@ -1248,18 +1389,21 @@ export async function onCompletion(params: CompletionParams): Promisename||Name AS Name, Description, parent->Origin AS Origin, FormalSpec, ReturnType AS Type, 'method' AS MemberType, Deprecated " + `FROM %Dictionary.CompiledIndexMethod WHERE parent->Parent = ? AND ClassMethod = 1${internalStr}${deprecatedStr} UNION ALL %PARALLEL ` + "SELECT parent->name||Name AS Name, Description, parent->Origin AS Origin, FormalSpec, ReturnType AS Type, 'method' AS MemberType, Deprecated " + @@ -1267,7 +1411,7 @@ export async function onCompletion(params: CompletionParams): Promisename||Name AS Name, Description, parent->Origin AS Origin, FormalSpec, ReturnType AS Type, 'method' AS MemberType, Deprecated " + `FROM %Dictionary.CompiledPropertyMethod WHERE parent->Parent = ? AND ClassMethod = 1${internalStr}${deprecatedStr} UNION ALL %PARALLEL ` + "SELECT parent->name||Name AS Name, Description, parent->Origin AS Origin, FormalSpec, ReturnType AS Type, 'method' AS MemberType, Deprecated " + - `FROM %Dictionary.CompiledConstraintMethod WHERE parent->Parent = ? AND ClassMethod = 1${internalStr}${deprecatedStr}` + `FROM %Dictionary.CompiledConstraintMethod WHERE parent->Parent = ? AND ClassMethod = 1${internalStr}${deprecatedStr}`; data.parameters.push(...new Array(4).fill(membercontext.baseclass)); } } @@ -1278,7 +1422,7 @@ export async function onCompletion(params: CompletionParams): Promise { - const quoted = quoteUDLIdentifier(alias, 1); - result.push({ - ...item, - label: quoted, - sortText: memobj.Origin == membercontext.baseclass ? sortPrefix + quoted : quoted, - tags: memobj.Deprecated ? [CompletionItemTag.Deprecated] : undefined + memobj.Aliases.trim() + .split(/\s*,\s*/) + .forEach((alias: string) => { + const quoted = quoteUDLIdentifier(alias, 1); + result.push({ + ...item, + label: quoted, + sortText: memobj.Origin == membercontext.baseclass ? sortPrefix + quoted : quoted, + tags: memobj.Deprecated ? [CompletionItemTag.Deprecated] : undefined, + }); }); - }); } } if (memobj.Origin === membercontext.baseclass) { // Members from the base class should appear first item.sortText = sortPrefix + quotedname; - } - else { + } else { item.sortText = item.label; } if (memobj.Deprecated) { @@ -1363,25 +1505,26 @@ export async function onCompletion(params: CompletionParams): Promise closeParenCount && parenAndCommaRegex.test(prevline) && @@ -1391,7 +1534,13 @@ export async function onCompletion(params: CompletionParams): Promise= 0; tkn--) { - if (parsed[params.position.line][tkn].l == ld.cls_langindex && parsed[params.position.line][tkn].s == ld.cls_delim_attrindex) { - const delimText = doc.getText(Range.create( - params.position.line, - parsed[params.position.line][tkn].p, - params.position.line, - parsed[params.position.line][tkn].p + parsed[params.position.line][tkn].c - )); + if ( + parsed[params.position.line][tkn].l == ld.cls_langindex && + parsed[params.position.line][tkn].s == ld.cls_delim_attrindex + ) { + const delimText = doc.getText( + Range.create( + params.position.line, + parsed[params.position.line][tkn].p, + params.position.line, + parsed[params.position.line][tkn].p + parsed[params.position.line][tkn].c, + ), + ); if (delimText == ")") { openCount++; } else if (delimText == "(") { openCount--; if (openCount == 0) break; } - } else if (parsed[params.position.line][tkn].l == ld.cls_langindex && parsed[params.position.line][tkn].s == ld.cls_cparam_attrindex) { - existingparams.push(doc.getText(Range.create( - params.position.line, - parsed[params.position.line][tkn].p, - params.position.line, - parsed[params.position.line][tkn].p + parsed[params.position.line][tkn].c - ))); + } else if ( + parsed[params.position.line][tkn].l == ld.cls_langindex && + parsed[params.position.line][tkn].s == ld.cls_cparam_attrindex + ) { + existingparams.push( + doc.getText( + Range.create( + params.position.line, + parsed[params.position.line][tkn].p, + params.position.line, + parsed[params.position.line][tkn].p + parsed[params.position.line][tkn].c, + ), + ), + ); } } } // Add elements for core property parameters - const coreParams: CompletionItem[] = corePropertyParams.map(e => { + const coreParams: CompletionItem[] = corePropertyParams.map((e) => { return { label: e.name, kind: CompletionItemKind.Constant, data: "member", documentation: { kind: MarkupKind.Markdown, - value: e.desc + value: e.desc, }, sortText: e.name, - insertText: `${e.name} = ` + insertText: `${e.name} = `, }; }); - result = coreParams.filter(e => !existingparams.includes(e.label)); + result = coreParams.filter((e) => !existingparams.includes(e.label)); const isProperty: boolean = parsed[params.position.line][0].l == ld.cls_langindex && parsed[params.position.line][0].s == ld.cls_keyword_attrindex && - ["property", "relationship"].includes(doc.getText(Range.create( - params.position.line, - parsed[params.position.line][0].p, - params.position.line, - parsed[params.position.line][0].p + parsed[params.position.line][0].c - )).toLowerCase()); + ["property", "relationship"].includes( + doc + .getText( + Range.create( + params.position.line, + parsed[params.position.line][0].p, + params.position.line, + parsed[params.position.line][0].p + parsed[params.position.line][0].c, + ), + ) + .toLowerCase(), + ); // Query the server to get the names and descriptions of all class-specific parameters const data: QueryData = { - query: `SELECT Name, Description, Origin, Type, Deprecated FROM %Dictionary.CompiledParameter WHERE Parent = ?${isProperty ? " OR Parent %INLIST (SELECT $LISTFROMSTRING(PropertyClass) FROM %Dictionary.CompiledClass WHERE Name = ?)" : "" - }${!(await showInternalForServer(server)) ? " AND Internal = 0" : ""}${!settings.completion.showDeprecated ? " AND Deprecated = 0" : ""}`, - parameters: isProperty ? [normalizedcls, currentClass(doc, parsed)] : [normalizedcls] + query: `SELECT Name, Description, Origin, Type, Deprecated FROM %Dictionary.CompiledParameter WHERE Parent = ?${ + isProperty + ? " OR Parent %INLIST (SELECT $LISTFROMSTRING(PropertyClass) FROM %Dictionary.CompiledClass WHERE Name = ?)" + : "" + }${!(await showInternalForServer(server)) ? " AND Internal = 0" : ""}${!settings.completion.showDeprecated ? " AND Deprecated = 0" : ""}`, + parameters: isProperty ? [normalizedcls, currentClass(doc, parsed)] : [normalizedcls], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, data); if (Array.isArray(respdata?.data?.result?.content) && respdata.data.result.content.length > 0) { @@ -1472,10 +1642,10 @@ export async function onCompletion(params: CompletionParams): Promise= 0; k--) { if (parsed[k].length === 0) { continue; } if (parsed[k][0].l == ld.cls_langindex && parsed[k][0].s == ld.cls_keyword_attrindex) { - keywordtype = doc.getText(Range.create( - k, parsed[k][0].p, - k, parsed[k][0].p + parsed[k][0].c - )).toLowerCase(); + keywordtype = doc + .getText(Range.create(k, parsed[k][0].p, k, parsed[k][0].p + parsed[k][0].c)) + .toLowerCase(); break; } } @@ -1553,32 +1739,23 @@ export async function onCompletion(params: CompletionParams): Promise= 0; k--) { if (parsed[k].length === 0) { continue; } if (parsed[k][0].l == ld.cls_langindex && parsed[k][0].s == ld.cls_keyword_attrindex) { - keywordtype = doc.getText(Range.create(k, parsed[k][0].p, k, parsed[k][0].p + parsed[k][0].c)).toLowerCase(); + keywordtype = doc + .getText(Range.create(k, parsed[k][0].p, k, parsed[k][0].p + parsed[k][0].c)) + .toLowerCase(); break; } } @@ -1664,32 +1864,23 @@ export async function onCompletion(params: CompletionParams): Promise 0) { @@ -1760,14 +1955,13 @@ export async function onCompletion(params: CompletionParams): Promise prevline.split(">").length) || - prevline.endsWith("<") || prevline.endsWith('"') + prevline.endsWith("<") || + prevline.endsWith('"') ) { // Get the SchemaCache for this server or create one if it doesn't exist let schemaCache = schemaCaches.get(server); @@ -1845,27 +2036,47 @@ export async function onCompletion(params: CompletionParams): Promise") { + openelem.splice( + openelem.lastIndexOf( + doc.getText( + Range.create( + xmlline, + parsed[xmlline][xmltkn + 1].p, + xmlline, + parsed[xmlline][xmltkn + 1].p + parsed[xmlline][xmltkn + 1].c, + ), + ), + ), + 1, + ); + } else if (tokentext === "/>") { // The previous element has been closed openelem.pop(); } @@ -1890,12 +2101,21 @@ export async function onCompletion(params: CompletionParams): Promise= params.position.character) { continue; } - if (parsed[params.position.line][tkn].l == ld.xml_langindex && parsed[params.position.line][tkn].s == ld.xml_attr_attrindex) { + if ( + parsed[params.position.line][tkn].l == ld.xml_langindex && + parsed[params.position.line][tkn].s == ld.xml_attr_attrindex + ) { // This is an attribute name - usedAttrs.push(doc.getText(Range.create( - params.position.line, parsed[params.position.line][tkn].p, - params.position.line, parsed[params.position.line][tkn].p + parsed[params.position.line][tkn].c - ))); + usedAttrs.push( + doc.getText( + Range.create( + params.position.line, + parsed[params.position.line][tkn].p, + params.position.line, + parsed[params.position.line][tkn].p + parsed[params.position.line][tkn].c, + ), + ), + ); } } @@ -1908,11 +2128,10 @@ export async function onCompletion(params: CompletionParams): Promise", kind: CompletionItemKind.Property, data: "SASchema", - sortText: "zzzzz" + "/" + openelem[openelem.length - 1] + ">" + sortText: "zzzzz" + "/" + openelem[openelem.length - 1] + ">", }); } - } - else { + } else { // Looking for an attribute value enum // Find the name of the attribute that we're looking for values for @@ -1945,12 +2163,19 @@ export async function onCompletion(params: CompletionParams): Promise= params.position.character) { continue; } - if (parsed[params.position.line][tkn].l == ld.xml_langindex && parsed[params.position.line][tkn].s == ld.xml_attr_attrindex) { + if ( + parsed[params.position.line][tkn].l == ld.xml_langindex && + parsed[params.position.line][tkn].s == ld.xml_attr_attrindex + ) { // This is an attribute name - selector = doc.getText(Range.create( - params.position.line, parsed[params.position.line][tkn].p, - params.position.line, parsed[params.position.line][tkn].p + parsed[params.position.line][tkn].c - )); + selector = doc.getText( + Range.create( + params.position.line, + parsed[params.position.line][tkn].p, + params.position.line, + parsed[params.position.line][tkn].p + parsed[params.position.line][tkn].c, + ), + ); break; } } @@ -1965,7 +2190,7 @@ export async function onCompletion(params: CompletionParams): Promise keydoc.name.toUpperCase() == storageObjKey.slice(longestStart.length) + (keydoc) => keydoc.name.toUpperCase() == storageObjKey.slice(longestStart.length), ); if (parentKeyDoc) { result.push({ @@ -2083,65 +2315,63 @@ export async function onCompletion(params: CompletionParams): Promise`, - sortText: "zzzz" // Make sure this entry is last in the list + sortText: "zzzz", // Make sure this entry is last in the list }); } } } - result.push(...keywords.filter((keydoc) => keydoc.name != "Name").map((keydoc) => { - let doctext = keydoc.description; - if (doctext === undefined) { - doctext = ""; - } - if ("constraint" in keydoc && keydoc.constraint instanceof Array) { - if (doctext !== "") { - doctext = doctext + "\n\n"; - } - doctext = doctext.concat("Permitted Values: ", keydoc.constraint.join(", ")); - } - const compitem: CompletionItem = { - label: keydoc.name, - kind: CompletionItemKind.Keyword, - data: "storage", - documentation: { - kind: MarkupKind.PlainText, - value: doctext - } - }; - - const childKeys: KeywordDoc[] = storageKeywords[storageObjKey + keydoc.name.toUpperCase()]; - if (childKeys && childKeys.findIndex((childkey) => childkey.name == "Name") != -1) { - // This element has a name, so it needs to be included as an attribute - if (keydoc.type == "KW_TYPE_SUBNODE") { - compitem.insertText = `${keydoc.name} name="$1">\n$2\n`; - } - else { - compitem.insertText = `${keydoc.name} name="$1">$2`; - } - } - else { - if (keydoc.type == "KW_TYPE_SUBNODE") { - compitem.insertText = `${keydoc.name}>\n$1\n`; - } - else { - if (keydoc.name == "IdFunction") { - compitem.insertText = `${keydoc.name}>` + "${1|increment,sequence|}" + ``; + result.push( + ...keywords + .filter((keydoc) => keydoc.name != "Name") + .map((keydoc) => { + let doctext = keydoc.description; + if (doctext === undefined) { + doctext = ""; } - else if (keydoc.name == "Final") { - compitem.insertText = `${keydoc.name}>1`; + if ("constraint" in keydoc && keydoc.constraint instanceof Array) { + if (doctext !== "") { + doctext = doctext + "\n\n"; + } + doctext = doctext.concat("Permitted Values: ", keydoc.constraint.join(", ")); } - else { - compitem.insertText = `${keydoc.name}>$1`; + const compitem: CompletionItem = { + label: keydoc.name, + kind: CompletionItemKind.Keyword, + data: "storage", + documentation: { + kind: MarkupKind.PlainText, + value: doctext, + }, + }; + + const childKeys: KeywordDoc[] = storageKeywords[storageObjKey + keydoc.name.toUpperCase()]; + if (childKeys && childKeys.findIndex((childkey) => childkey.name == "Name") != -1) { + // This element has a name, so it needs to be included as an attribute + if (keydoc.type == "KW_TYPE_SUBNODE") { + compitem.insertText = `${keydoc.name} name="$1">\n$2\n`; + } else { + compitem.insertText = `${keydoc.name} name="$1">$2`; + } + } else { + if (keydoc.type == "KW_TYPE_SUBNODE") { + compitem.insertText = `${keydoc.name}>\n$1\n`; + } else { + if (keydoc.name == "IdFunction") { + compitem.insertText = `${keydoc.name}>` + "${1|increment,sequence|}" + ``; + } else if (keydoc.name == "Final") { + compitem.insertText = `${keydoc.name}>1`; + } else { + compitem.insertText = `${keydoc.name}>$1`; + } + } } - } - } - compitem.insertTextFormat = InsertTextFormat.Snippet; - return compitem; - })); + compitem.insertTextFormat = InsertTextFormat.Snippet; + return compitem; + }), + ); } } - } - else if (prevline.endsWith("i%") && triggerlang === ld.cos_langindex) { + } else if (prevline.endsWith("i%") && triggerlang === ld.cos_langindex) { // This is instance variable syntax // Find the name of the current class @@ -2154,10 +2384,11 @@ export async function onCompletion(params: CompletionParams): Promise 0) { // We got data back @@ -2170,8 +2401,8 @@ export async function onCompletion(params: CompletionParams): Promise 0) { // The class was found item.documentation = { kind: MarkupKind.Markdown, - value: documaticHtmlToMarkdown(respdata.data.result.content[0].Description) + value: documaticHtmlToMarkdown(respdata.data.result.content[0].Description), }; } - } - else if (Array.isArray(item.data) && item.data[0] === "macro" && !item.documentation) { + } else if (Array.isArray(item.data) && item.data[0] === "macro" && !item.documentation) { // Get the macro definition from the server const server: ServerSpec = await getServerSpec(item.data[1]); const querydata = { @@ -2225,7 +2453,7 @@ export async function onCompletionResolve(item: CompletionItem): Promise 0) { diff --git a/server/src/providers/declaration.ts b/server/src/providers/declaration.ts index 93103c9..1b04f49 100644 --- a/server/src/providers/declaration.ts +++ b/server/src/providers/declaration.ts @@ -1,14 +1,20 @@ -import { Position, TextDocumentPositionParams, Range } from 'vscode-languageserver/node'; -import { findFullRange, getParsedDocument } from '../utils/functions'; -import { documents } from '../utils/variables'; -import * as ld from '../utils/languageDefinitions'; +import { Position, TextDocumentPositionParams, Range } from "vscode-languageserver/node"; +import { findFullRange, getParsedDocument } from "../utils/functions"; +import { documents } from "../utils/variables"; +import * as ld from "../utils/languageDefinitions"; export async function onDeclaration(params: TextDocumentPositionParams) { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) { return null; } - if (doc.languageId !== "objectscript-class") { return null; } + if (doc === undefined) { + return null; + } + if (doc.languageId !== "objectscript-class") { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } for (let i = 0; i < parsed[params.position.line].length; i++) { const symbolstart: number = parsed[params.position.line][i].p; @@ -16,7 +22,10 @@ export async function onDeclaration(params: TextDocumentPositionParams) { if (params.position.character >= symbolstart && params.position.character <= symbolend) { // We found the right symbol in the line - if (parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_param_attrindex) { + if ( + parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_param_attrindex + ) { // This is a parameter let decrange: Range | null = null; @@ -25,15 +34,17 @@ export async function onDeclaration(params: TextDocumentPositionParams) { for (let j = params.position.line; j >= 0; j--) { if (parsed[j].length === 0) { continue; - } - else if (parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { + } else if (parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { // This is the method definition if ( - parsed[j][parsed[j].length - 1].l == ld.cls_langindex && parsed[j][parsed[j].length - 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - Position.create(j, parsed[j][parsed[j].length - 1].p), - Position.create(j, parsed[j][parsed[j].length - 1].p + parsed[j][parsed[j].length - 1].c) - )) === "(" + parsed[j][parsed[j].length - 1].l == ld.cls_langindex && + parsed[j][parsed[j].length - 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + Position.create(j, parsed[j][parsed[j].length - 1].p), + Position.create(j, parsed[j][parsed[j].length - 1].p + parsed[j][parsed[j].length - 1].c), + ), + ) === "(" ) { // This is a multi-line method definition for (let mline = j + 1; mline < parsed.length; mline++) { @@ -44,7 +55,7 @@ export async function onDeclaration(params: TextDocumentPositionParams) { // This is a parameter const paramrange = Range.create( Position.create(mline, parsed[mline][tkn].p), - Position.create(mline, parsed[mline][tkn].p + parsed[mline][tkn].c) + Position.create(mline, parsed[mline][tkn].p + parsed[mline][tkn].c), ); const paramtext = doc.getText(paramrange); if (thisparam === paramtext) { @@ -57,27 +68,31 @@ export async function onDeclaration(params: TextDocumentPositionParams) { if (decrange !== null) { // We found the parameter break; - } - else if ( - parsed[mline][parsed[mline].length - 1].l == ld.cls_langindex && parsed[mline][parsed[mline].length - 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - Position.create(mline, parsed[mline][parsed[mline].length - 1].p), - Position.create(mline, parsed[mline][parsed[mline].length - 1].p + parsed[mline][parsed[mline].length - 1].c) - )) !== "," + } else if ( + parsed[mline][parsed[mline].length - 1].l == ld.cls_langindex && + parsed[mline][parsed[mline].length - 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + Position.create(mline, parsed[mline][parsed[mline].length - 1].p), + Position.create( + mline, + parsed[mline][parsed[mline].length - 1].p + parsed[mline][parsed[mline].length - 1].c, + ), + ), + ) !== "," ) { // We've reached the end of the method definition break; } } - } - else { + } else { // This is a single-line method definition for (let tkn = 0; tkn < parsed[j].length; tkn++) { if (parsed[j][tkn].l == ld.cls_langindex && parsed[j][tkn].s == ld.cls_param_attrindex) { // This is a parameter const paramrange = Range.create( Position.create(j, parsed[j][tkn].p), - Position.create(j, parsed[j][tkn].p + parsed[j][tkn].c) + Position.create(j, parsed[j][tkn].p + parsed[j][tkn].c), ); const paramtext = doc.getText(paramrange); if (thisparam === paramtext) { @@ -95,11 +110,13 @@ export async function onDeclaration(params: TextDocumentPositionParams) { // We found the parameter declaration return { uri: params.textDocument.uri, - range: decrange + range: decrange, }; } - } - else if (parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_localdec_attrindex) { + } else if ( + parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_localdec_attrindex + ) { // This is a declared local variable let decrange: Range | null = null; @@ -108,20 +125,23 @@ export async function onDeclaration(params: TextDocumentPositionParams) { for (let j = params.position.line; j >= 0; j--) { if (parsed[j].length === 0) { continue; - } - else if (parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { + } else if (parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { // This is the definition for the class member that the variable is in break; - } - else if (parsed[j][0].l == ld.cos_langindex && parsed[j][0].s == ld.cos_ppc_attrindex) { + } else if (parsed[j][0].l == ld.cos_langindex && parsed[j][0].s == ld.cos_ppc_attrindex) { // This is a preprocessor command - const command = doc.getText(Range.create(Position.create(j, parsed[j][0].p), Position.create(j, parsed[j][1].p + parsed[j][1].c))); + const command = doc.getText( + Range.create(Position.create(j, parsed[j][0].p), Position.create(j, parsed[j][1].p + parsed[j][1].c)), + ); if (command.toLowerCase() === "#dim") { // This is a #Dim for (let k = 2; k < parsed[j].length; k++) { if (parsed[j][k].s === ld.cos_localdec_attrindex) { // This is a declared local variable - const localdecrange = Range.create(Position.create(j, parsed[j][k].p), Position.create(j, parsed[j][k].p + parsed[j][k].c)); + const localdecrange = Range.create( + Position.create(j, parsed[j][k].p), + Position.create(j, parsed[j][k].p + parsed[j][k].c), + ); const localvar = doc.getText(localdecrange); if (localvar === thisvar) { // This is the #Dim for this variable @@ -141,11 +161,13 @@ export async function onDeclaration(params: TextDocumentPositionParams) { // We found the local variable declaration return { uri: params.textDocument.uri, - range: decrange + range: decrange, }; } - } - else if (parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_localvar_attrindex) { + } else if ( + parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_localvar_attrindex + ) { // This is a public variable let decrange: Range | null = null; @@ -154,13 +176,13 @@ export async function onDeclaration(params: TextDocumentPositionParams) { for (let j = params.position.line; j >= 0; j--) { if (parsed[j].length === 0) { continue; - } - else if (parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { + } else if (parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { // This is the definition for the class member that the variable is in - const keytext = doc.getText(Range.create( - Position.create(j, parsed[j][0].p), - Position.create(j, parsed[j][0].p + parsed[j][0].c) - )).toLowerCase(); + const keytext = doc + .getText( + Range.create(Position.create(j, parsed[j][0].p), Position.create(j, parsed[j][0].p + parsed[j][0].c)), + ) + .toLowerCase(); if (keytext.indexOf("method") !== -1) { // This public variable is in a method so see if it's in the PublicList @@ -173,14 +195,24 @@ export async function onDeclaration(params: TextDocumentPositionParams) { for (let tkn = 1; tkn < parsed[k].length; tkn++) { if (parsed[k][tkn].l == ld.cls_langindex && parsed[k][tkn].s == ld.cls_keyword_attrindex) { // This token is a keyword - prevkey = doc.getText(Range.create( - Position.create(k, parsed[k][tkn].p), - Position.create(k, parsed[k][tkn].p + parsed[k][tkn].c) - )).toLowerCase(); - } - else if (prevkey === "publiclist" && parsed[k][tkn].l == ld.cls_langindex && parsed[k][tkn].s == ld.cls_iden_attrindex) { + prevkey = doc + .getText( + Range.create( + Position.create(k, parsed[k][tkn].p), + Position.create(k, parsed[k][tkn].p + parsed[k][tkn].c), + ), + ) + .toLowerCase(); + } else if ( + prevkey === "publiclist" && + parsed[k][tkn].l == ld.cls_langindex && + parsed[k][tkn].s == ld.cls_iden_attrindex + ) { // This is an identifier in the PublicList - const idenrange = Range.create(Position.create(k, parsed[k][tkn].p), Position.create(k, parsed[k][tkn].p + parsed[k][tkn].c)); + const idenrange = Range.create( + Position.create(k, parsed[k][tkn].p), + Position.create(k, parsed[k][tkn].p + parsed[k][tkn].c), + ); const identext = doc.getText(idenrange); if (identext === thisvar) { // This identifier is the variable that we're looking for @@ -190,11 +222,14 @@ export async function onDeclaration(params: TextDocumentPositionParams) { } } if ( - parsed[k][parsed[k].length - 1].l == ld.cls_langindex && parsed[k][parsed[k].length - 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - Position.create(k, parsed[k][parsed[k].length - 1].p), - Position.create(k, parsed[k][parsed[k].length - 1].p + parsed[k][parsed[k].length - 1].c) - )) === "{" + parsed[k][parsed[k].length - 1].l == ld.cls_langindex && + parsed[k][parsed[k].length - 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + Position.create(k, parsed[k][parsed[k].length - 1].p), + Position.create(k, parsed[k][parsed[k].length - 1].p + parsed[k][parsed[k].length - 1].c), + ), + ) === "{" ) { // The last token on this line is an open curly, so this is the end of the method definition break; @@ -202,16 +237,20 @@ export async function onDeclaration(params: TextDocumentPositionParams) { } } break; - } - else if (parsed[j][0].l == ld.cos_langindex && parsed[j][0].s == ld.cos_ppc_attrindex) { + } else if (parsed[j][0].l == ld.cos_langindex && parsed[j][0].s == ld.cos_ppc_attrindex) { // This is a preprocessor command - const command = doc.getText(Range.create(Position.create(j, parsed[j][0].p), Position.create(j, parsed[j][1].p + parsed[j][1].c))); + const command = doc.getText( + Range.create(Position.create(j, parsed[j][0].p), Position.create(j, parsed[j][1].p + parsed[j][1].c)), + ); if (command.toLowerCase() === "#dim") { // This is a #Dim for (let k = 2; k < parsed[j].length; k++) { if (parsed[j][k].s === ld.cos_localvar_attrindex) { // This is a public variable - const pubrange = Range.create(Position.create(j, parsed[j][k].p), Position.create(j, parsed[j][k].p + parsed[j][k].c)); + const pubrange = Range.create( + Position.create(j, parsed[j][k].p), + Position.create(j, parsed[j][k].p + parsed[j][k].c), + ); const localvar = doc.getText(pubrange); if (localvar === thisvar) { // This is the #Dim for this variable @@ -231,7 +270,7 @@ export async function onDeclaration(params: TextDocumentPositionParams) { // We found the pubic variable declaration return { uri: params.textDocument.uri, - range: decrange + range: decrange, }; } } diff --git a/server/src/providers/definition.ts b/server/src/providers/definition.ts index a26bde7..56703c5 100644 --- a/server/src/providers/definition.ts +++ b/server/src/providers/definition.ts @@ -1,9 +1,26 @@ -import { Position, TextDocumentPositionParams, Range, LocationLink } from 'vscode-languageserver/node'; -import { TextDocument } from 'vscode-languageserver-textdocument'; -import { getServerSpec, findFullRange, normalizeClassname, makeRESTRequest, createDefinitionUri, getMacroContext, isMacroDefinedAbove, quoteUDLIdentifier, getClassMemberContext, determineClassNameParameterClass, getParsedDocument, currentClass, getTextForUri, isClassMember, memberRegex, urlMapAttribute } from '../utils/functions'; -import { ServerSpec, QueryData, compressedline } from '../utils/types'; -import { documents, corePropertyParams, classMemberTypes, mppContinue } from '../utils/variables'; -import * as ld from '../utils/languageDefinitions'; +import { Position, TextDocumentPositionParams, Range, LocationLink } from "vscode-languageserver/node"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import { + getServerSpec, + findFullRange, + normalizeClassname, + makeRESTRequest, + createDefinitionUri, + getMacroContext, + isMacroDefinedAbove, + quoteUDLIdentifier, + getClassMemberContext, + determineClassNameParameterClass, + getParsedDocument, + currentClass, + getTextForUri, + isClassMember, + memberRegex, + urlMapAttribute, +} from "../utils/functions"; +import { ServerSpec, QueryData, compressedline } from "../utils/types"; +import { documents, corePropertyParams, classMemberTypes, mppContinue } from "../utils/variables"; +import * as ld from "../utils/languageDefinitions"; /** * The maximum number of lines to include in the `targetRange` property @@ -13,7 +30,12 @@ const definitionTargetRangeMaxLines: number = 10; /** Return a `LocationLink` for class member `memberName` in class `cls` */ async function classMemberLocationLink( - uri: string, cls: string, memberName: string, memberKeywords: string, memberRange: Range, server: ServerSpec + uri: string, + cls: string, + memberName: string, + memberKeywords: string, + memberRange: Range, + server: ServerSpec, ): Promise { const targetrange = Range.create(0, 0, 0, 0); let targetselrange = Range.create(0, 0, 0, 0); @@ -38,8 +60,7 @@ async function classMemberLocationLink( targetrange.end = Position.create(j, 0); break; } - } - else if (regex.test(classText[j])) { + } else if (regex.test(classText[j])) { // This is the right class member const memberlineidx = classText[j].indexOf(memberName); if (memberlineidx !== -1) { @@ -55,30 +76,38 @@ async function classMemberLocationLink( const trimmed = classText[pvrln].trim(); if (trimmed === "") { targetrange.end.line = pvrln; - } - else if ( - trimmed.slice(0, 3) === "##;" || trimmed.slice(0, 2) === "//" || trimmed.slice(0, 1) === ";" || - trimmed.slice(0, 2) === "#;" || trimmed.slice(0, 2) === "/*" + } else if ( + trimmed.slice(0, 3) === "##;" || + trimmed.slice(0, 2) === "//" || + trimmed.slice(0, 1) === ";" || + trimmed.slice(0, 2) === "#;" || + trimmed.slice(0, 2) === "/*" ) { targetrange.end.line = pvrln; - } - else { + } else { break; } } - return [{ - targetUri: newuri, - targetRange: targetrange, - originSelectionRange: memberRange, - targetSelectionRange: targetselrange - }]; + return [ + { + targetUri: newuri, + targetRange: targetrange, + originSelectionRange: memberRange, + targetSelectionRange: targetselrange, + }, + ]; } } } } /** Return a `LocationLink` for class `cls` */ -async function classLocationLink(uri: string, cls: string, range: Range, server: ServerSpec): Promise { +async function classLocationLink( + uri: string, + cls: string, + range: Range, + server: ServerSpec, +): Promise { // Get the uri of the target class const newuri = await createDefinitionUri(uri, cls, ".cls"); if (newuri != "") { @@ -97,19 +126,26 @@ async function classLocationLink(uri: string, cls: string, range: Range, server: break; } } - return [{ - targetUri: newuri, - targetRange: targetrange, - originSelectionRange: range, - targetSelectionRange: targetselrange - }]; + return [ + { + targetUri: newuri, + targetRange: targetrange, + originSelectionRange: range, + targetSelectionRange: targetselrange, + }, + ]; } } } /** Return a `LocationLink` if `member` is found in the current class */ function findMemberInCurrentClass( - doc: TextDocument, parsed: compressedline[], uri: string, member: string, memberKeywords: string, range: Range + doc: TextDocument, + parsed: compressedline[], + uri: string, + member: string, + memberKeywords: string, + range: Range, ): LocationLink[] | undefined { // Loop through the file contents to find this member const targetrange = Range.create(0, 0, 0, 0); @@ -125,15 +161,19 @@ function findMemberInCurrentClass( break; } if ( - parsed[dln].length > 0 && parsed[dln][0].l === ld.cls_langindex && + parsed[dln].length > 0 && + parsed[dln][0].l === ld.cls_langindex && (parsed[dln][0].s === ld.cls_keyword_attrindex || parsed[dln][0].s === ld.cls_desc_attrindex) ) { // This is the first class member following the one we needed the definition for, so cut off the preview range here targetrange.end = Position.create(dln, 0); break; } - } - else if (parsed[dln].length > 0 && parsed[dln][0].l == ld.cls_langindex && parsed[dln][0].s == ld.cls_keyword_attrindex) { + } else if ( + parsed[dln].length > 0 && + parsed[dln][0].l == ld.cls_langindex && + parsed[dln][0].s == ld.cls_keyword_attrindex + ) { // This line starts with a UDL keyword if (regex.test(doc.getText(Range.create(dln, 0, dln + 1, 0)))) { @@ -150,28 +190,35 @@ function findMemberInCurrentClass( for (let pvrln = targetrange.end.line - 1; pvrln > targetrange.start.line; pvrln--) { if (parsed[pvrln].length === 0) { targetrange.end.line = pvrln; - } - else if (parsed[pvrln][0].l === ld.cos_langindex && (parsed[pvrln][0].s === ld.cos_comment_attrindex || parsed[pvrln][0].s === ld.cos_dcom_attrindex)) { + } else if ( + parsed[pvrln][0].l === ld.cos_langindex && + (parsed[pvrln][0].s === ld.cos_comment_attrindex || parsed[pvrln][0].s === ld.cos_dcom_attrindex) + ) { targetrange.end.line = pvrln; - } - else { + } else { break; } } - return [{ - targetUri: uri, - originSelectionRange: range, - targetSelectionRange: targetselrange, - targetRange: targetrange - }]; + return [ + { + targetUri: uri, + originSelectionRange: range, + targetSelectionRange: targetselrange, + targetRange: targetrange, + }, + ]; } } export async function onDefinition(params: TextDocumentPositionParams) { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } const server: ServerSpec = await getServerSpec(params.textDocument.uri); if (parsed[params.position.line] === undefined) { @@ -184,9 +231,13 @@ export async function onDefinition(params: TextDocumentPositionParams) { if (params.position.character >= symbolstart && params.position.character <= symbolend) { // We found the right symbol in the line - if (((parsed[params.position.line][i].l == ld.cls_langindex && parsed[params.position.line][i].s == ld.cls_clsname_attrindex) || - (parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_clsname_attrindex)) - && doc.getText(Range.create(params.position.line, 0, params.position.line, 6)).toLowerCase() !== "import") { + if ( + ((parsed[params.position.line][i].l == ld.cls_langindex && + parsed[params.position.line][i].s == ld.cls_clsname_attrindex) || + (parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_clsname_attrindex)) && + doc.getText(Range.create(params.position.line, 0, params.position.line, 6)).toLowerCase() !== "import" + ) { // This is a class name // Get the full text of the selection @@ -194,15 +245,18 @@ export async function onDefinition(params: TextDocumentPositionParams) { let word = doc.getText(wordrange); if (word.charAt(0) === ".") { // This might be $SYSTEM.ClassName - const prevseven = doc.getText(Range.create( - params.position.line, wordrange.start.character - 7, - params.position.line, wordrange.start.character - )); + const prevseven = doc.getText( + Range.create( + params.position.line, + wordrange.start.character - 7, + params.position.line, + wordrange.start.character, + ), + ); if (prevseven.toUpperCase() === "$SYSTEM") { // This is $SYSTEM.ClassName word = "%SYSTEM" + word; - } - else { + } else { // This classname is invalid return null; } @@ -217,13 +271,12 @@ export async function onDefinition(params: TextDocumentPositionParams) { if (normalizedname != "") { return classLocationLink(params.textDocument.uri, normalizedname, wordrange, server); } - } - else if ( - parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_macro_attrindex || ( - parsed[params.position.line][i].l == ld.sql_langindex && + } else if ( + (parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_macro_attrindex) || + (parsed[params.position.line][i].l == ld.sql_langindex && parsed[params.position.line][i].s == ld.sql_iden_attrindex && - doc.getText(Range.create(params.position.line, symbolstart, params.position.line, symbolstart + 3)) == "$$$" - ) + doc.getText(Range.create(params.position.line, symbolstart, params.position.line, symbolstart + 3)) == "$$$") ) { // This is a macro @@ -251,21 +304,38 @@ export async function onDefinition(params: TextDocumentPositionParams) { const targetrange = Range.create(macrodefline, 0, macrodefline + 1, 0); if ( - parsed[macrodefline][parsed[macrodefline].length - 1].l == ld.cos_langindex && parsed[macrodefline][parsed[macrodefline].length - 1].s == ld.cos_ppf_attrindex && - mppContinue.test(doc.getText(Range.create( - macrodefline, parsed[macrodefline][parsed[macrodefline].length - 1].p, - macrodefline, parsed[macrodefline][parsed[macrodefline].length - 1].p + parsed[macrodefline][parsed[macrodefline].length - 1].c - ))) + parsed[macrodefline][parsed[macrodefline].length - 1].l == ld.cos_langindex && + parsed[macrodefline][parsed[macrodefline].length - 1].s == ld.cos_ppf_attrindex && + mppContinue.test( + doc.getText( + Range.create( + macrodefline, + parsed[macrodefline][parsed[macrodefline].length - 1].p, + macrodefline, + parsed[macrodefline][parsed[macrodefline].length - 1].p + + parsed[macrodefline][parsed[macrodefline].length - 1].c, + ), + ), + ) ) { // This is a multi-line macro definition so scan down the file to capture the full range of the definition for (let mln = macrodefline + 1; mln < parsed.length; mln++) { - if (!( - parsed[mln][parsed[mln].length - 1].l == ld.cos_langindex && parsed[mln][parsed[mln].length - 1].s == ld.cos_ppf_attrindex && - mppContinue.test(doc.getText(Range.create( - mln, parsed[mln][parsed[mln].length - 1].p, - mln, parsed[mln][parsed[mln].length - 1].p + parsed[mln][parsed[mln].length - 1].c - ))) - )) { + if ( + !( + parsed[mln][parsed[mln].length - 1].l == ld.cos_langindex && + parsed[mln][parsed[mln].length - 1].s == ld.cos_ppf_attrindex && + mppContinue.test( + doc.getText( + Range.create( + mln, + parsed[mln][parsed[mln].length - 1].p, + mln, + parsed[mln][parsed[mln].length - 1].p + parsed[mln][parsed[mln].length - 1].c, + ), + ), + ) + ) + ) { // This is the last line of the macro definition so update the target range targetrange.end = Position.create(mln + 1, 0); break; @@ -273,14 +343,20 @@ export async function onDefinition(params: TextDocumentPositionParams) { } } - return [{ - targetUri: params.textDocument.uri, - targetRange: targetrange, - originSelectionRange: macrorange, - targetSelectionRange: Range.create(macrodefline, parsed[macrodefline][2].p, macrodefline, parsed[macrodefline][2].p + parsed[macrodefline][2].c) - }]; - } - else { + return [ + { + targetUri: params.textDocument.uri, + targetRange: targetrange, + originSelectionRange: macrorange, + targetSelectionRange: Range.create( + macrodefline, + parsed[macrodefline][2].p, + macrodefline, + parsed[macrodefline][2].p + parsed[macrodefline][2].c, + ), + }, + ]; + } else { // The macro is defined in another file // Get the macro location from the server @@ -291,7 +367,7 @@ export async function onDefinition(params: TextDocumentPositionParams) { includes: maccon.includes, includegenerators: maccon.includegenerators, imports: maccon.imports, - mode: maccon.mode + mode: maccon.mode, }; const respdata = await makeRESTRequest("POST", 2, "/action/getmacrolocation", server, inputdata); if (respdata !== undefined && respdata.data.result.content.document !== "") { @@ -301,19 +377,30 @@ export async function onDefinition(params: TextDocumentPositionParams) { const ext = respdata.data.result.content.document.substring(lastdot); const newuri = await createDefinitionUri(params.textDocument.uri, filename, ext); if (newuri !== "") { - return [{ - targetUri: newuri, - targetRange: Range.create(respdata.data.result.content.line, 0, respdata.data.result.content.line + 1, 0), - originSelectionRange: macrorange, - targetSelectionRange: Range.create(respdata.data.result.content.line, 0, respdata.data.result.content.line + 1, 0) - }]; + return [ + { + targetUri: newuri, + targetRange: Range.create( + respdata.data.result.content.line, + 0, + respdata.data.result.content.line + 1, + 0, + ), + originSelectionRange: macrorange, + targetSelectionRange: Range.create( + respdata.data.result.content.line, + 0, + respdata.data.result.content.line + 1, + 0, + ), + }, + ]; } } } - } - else if ( - parsed[params.position.line][i].l == ld.cos_langindex && ( - parsed[params.position.line][i].s == ld.cos_prop_attrindex || + } else if ( + parsed[params.position.line][i].l == ld.cos_langindex && + (parsed[params.position.line][i].s == ld.cos_prop_attrindex || parsed[params.position.line][i].s == ld.cos_method_attrindex || parsed[params.position.line][i].s == ld.cos_attr_attrindex || parsed[params.position.line][i].s == ld.cos_mem_attrindex || @@ -332,8 +419,8 @@ export async function onDefinition(params: TextDocumentPositionParams) { let unquotedname = quoteUDLIdentifier(member, 0); if (unquotedname == "%New") { unquotedname = "%OnNew"; - member = "%OnNew" - }; + member = "%OnNew"; + } // If this is a class file, determine what class we're in let thisclass = ""; @@ -341,7 +428,7 @@ export async function onDefinition(params: TextDocumentPositionParams) { thisclass = currentClass(doc, parsed); } - let membercontext: { baseclass: string; context?: string; }; + let membercontext: { baseclass: string; context?: string }; if (parsed[params.position.line][i].s != ld.cos_instvar_attrindex) { // Find the dot token let dottkn = 0; @@ -357,7 +444,7 @@ export async function onDefinition(params: TextDocumentPositionParams) { } else { membercontext = { baseclass: thisclass, - context: "" + context: "", }; } if (membercontext.baseclass === "") { @@ -366,14 +453,25 @@ export async function onDefinition(params: TextDocumentPositionParams) { } let memberKeywords = - parsed[params.position.line][i].s == ld.cos_prop_attrindex ? "Parameter" : - parsed[params.position.line][i].s == ld.cos_method_attrindex || unquotedname == "%OnNew" ? "Method|ClassMethod|ClientMethod" : - [ld.cos_attr_attrindex, ld.cos_instvar_attrindex].includes(parsed[params.position.line][i].s) ? "Property|Relationship" : - membercontext.baseclass.startsWith("%SYSTEM.") ? "Method|ClassMethod|ClientMethod" : - "Method|ClassMethod|ClientMethod|Property|Relationship"; + parsed[params.position.line][i].s == ld.cos_prop_attrindex + ? "Parameter" + : parsed[params.position.line][i].s == ld.cos_method_attrindex || unquotedname == "%OnNew" + ? "Method|ClassMethod|ClientMethod" + : [ld.cos_attr_attrindex, ld.cos_instvar_attrindex].includes(parsed[params.position.line][i].s) + ? "Property|Relationship" + : membercontext.baseclass.startsWith("%SYSTEM.") + ? "Method|ClassMethod|ClientMethod" + : "Method|ClassMethod|ClientMethod|Property|Relationship"; if (thisclass == membercontext.baseclass) { // The member may be defined in this class - const currentClassLinks = findMemberInCurrentClass(doc, parsed, params.textDocument.uri, member, memberKeywords, memberrange); + const currentClassLinks = findMemberInCurrentClass( + doc, + parsed, + params.textDocument.uri, + member, + memberKeywords, + memberrange, + ); if (currentClassLinks) return currentClassLinks; } // The member is defined in another class @@ -381,29 +479,33 @@ export async function onDefinition(params: TextDocumentPositionParams) { // Query the server to get the origin class of this member using its base class, text and token type const data: QueryData = { query: "", - parameters: [] + parameters: [], }; if (memberKeywords == "Parameter") { // This is a parameter data.query = "SELECT Origin, NULL AS Stub FROM %Dictionary.CompiledParameter WHERE Parent = ? AND name = ?"; data.parameters = [membercontext.baseclass, unquotedname]; - } - else if (memberKeywords == "Method|ClassMethod|ClientMethod") { + } else if (memberKeywords == "Method|ClassMethod|ClientMethod") { // This is a method data.query = "SELECT Origin, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND name = ?"; data.parameters = [membercontext.baseclass, unquotedname]; - } - else if (memberKeywords == "Property|Relationship") { + } else if (memberKeywords == "Property|Relationship") { // This is a property - data.query = "SELECT Name, Origin, NULL AS Stub FROM %Dictionary.CompiledProperty WHERE Parent = ? AND (Name = ? OR ? %INLIST $LISTFROMSTRING($TRANSLATE(Aliases,' ')))"; + data.query = + "SELECT Name, Origin, NULL AS Stub FROM %Dictionary.CompiledProperty WHERE Parent = ? AND (Name = ? OR ? %INLIST $LISTFROMSTRING($TRANSLATE(Aliases,' ')))"; data.parameters = [membercontext.baseclass, unquotedname, unquotedname.replace(/\s+/g, "")]; - } - else { + } else { // This can be a method or property data.query = "SELECT Name, Origin, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND name = ? UNION ALL " + "SELECT Name, Origin, NULL AS Stub FROM %Dictionary.CompiledProperty WHERE Parent = ? AND (Name = ? OR ? %INLIST $LISTFROMSTRING($TRANSLATE(Aliases,' ')))"; - data.parameters = [membercontext.baseclass, unquotedname, membercontext.baseclass, unquotedname, unquotedname.replace(/\s+/g, "")]; + data.parameters = [ + membercontext.baseclass, + unquotedname, + membercontext.baseclass, + unquotedname, + unquotedname.replace(/\s+/g, ""), + ]; } let originclass = ""; let membernameinfile = member; @@ -440,7 +542,7 @@ export async function onDefinition(params: TextDocumentPositionParams) { if (stubQuery) { const stubrespdata = await makeRESTRequest("POST", 1, "/action/query", server, { query: stubQuery, - parameters: [stubMember, membercontext.baseclass] + parameters: [stubMember, membercontext.baseclass], }); if (Array.isArray(stubrespdata?.data?.result?.content) && stubrespdata.data.result.content.length > 0) { // We got data back @@ -456,11 +558,21 @@ export async function onDefinition(params: TextDocumentPositionParams) { membernameinfile = "%New"; } if (originclass !== "") { - return classMemberLocationLink(params.textDocument.uri, originclass, membernameinfile, memberKeywords, memberrange, server); + return classMemberLocationLink( + params.textDocument.uri, + originclass, + membernameinfile, + memberKeywords, + memberrange, + server, + ); } - } - else if ((parsed[params.position.line][i].l == ld.cls_langindex && parsed[params.position.line][i].s == ld.cls_rtnname_attrindex) || - (parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_rtnname_attrindex)) { + } else if ( + (parsed[params.position.line][i].l == ld.cls_langindex && + parsed[params.position.line][i].s == ld.cls_rtnname_attrindex) || + (parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_rtnname_attrindex) + ) { // This is a routine name // Get the full text of the selection @@ -469,76 +581,102 @@ export async function onDefinition(params: TextDocumentPositionParams) { // Determine if this is an include file let isinc = false; - if (parsed[params.position.line][i].l == ld.cls_langindex && parsed[params.position.line][i].s == ld.cls_rtnname_attrindex) { + if ( + parsed[params.position.line][i].l == ld.cls_langindex && + parsed[params.position.line][i].s == ld.cls_rtnname_attrindex + ) { isinc = true; - } - else { + } else { if ( parsed[params.position.line][i - 1].l == ld.cos_langindex && parsed[params.position.line][i - 1].s == ld.cos_ppc_attrindex && - doc.getText( - Range.create( - Position.create(params.position.line, parsed[params.position.line][i - 1].p), - Position.create(params.position.line, parsed[params.position.line][i - 1].p + parsed[params.position.line][i - 1].c) + doc + .getText( + Range.create( + Position.create(params.position.line, parsed[params.position.line][i - 1].p), + Position.create( + params.position.line, + parsed[params.position.line][i - 1].p + parsed[params.position.line][i - 1].c, + ), + ), ) - ).toLowerCase() === "include" + .toLowerCase() === "include" ) { - isinc = true + isinc = true; } } if (isinc) { const newuri = await createDefinitionUri(params.textDocument.uri, word, ".inc"); if (newuri !== "") { - return [{ - targetUri: newuri, - targetRange: Range.create(Position.create(0, 0), Position.create(1, 0)), - originSelectionRange: wordrange, - targetSelectionRange: Range.create(Position.create(0, 8), Position.create(1, 0)) - }]; + return [ + { + targetUri: newuri, + targetRange: Range.create(Position.create(0, 0), Position.create(1, 0)), + originSelectionRange: wordrange, + targetSelectionRange: Range.create(Position.create(0, 8), Position.create(1, 0)), + }, + ]; } - } - else { + } else { // Check if this routine is a MAC or INT const respdata = await makeRESTRequest("POST", 1, "/action/index", server, [word + ".int"]); - if (respdata !== undefined && respdata.data.result.content.length > 0 && respdata.data.result.content[0].status === "") { - if (respdata.data.result.content[0].others.length > 0 && respdata.data.result.content[0].others[0].slice(-3) === "mac") { + if ( + respdata !== undefined && + respdata.data.result.content.length > 0 && + respdata.data.result.content[0].status === "" + ) { + if ( + respdata.data.result.content[0].others.length > 0 && + respdata.data.result.content[0].others[0].slice(-3) === "mac" + ) { // This is a MAC routine const newuri = await createDefinitionUri(params.textDocument.uri, word, ".mac"); if (newuri !== "") { - return [{ - targetUri: newuri, - targetRange: Range.create(Position.create(0, 0), Position.create(1, 0)), - originSelectionRange: wordrange, - targetSelectionRange: Range.create(Position.create(0, 8), Position.create(1, 0)) - }]; + return [ + { + targetUri: newuri, + targetRange: Range.create(Position.create(0, 0), Position.create(1, 0)), + originSelectionRange: wordrange, + targetSelectionRange: Range.create(Position.create(0, 8), Position.create(1, 0)), + }, + ]; } - } - else { + } else { // This is an INT routine const newuri = await createDefinitionUri(params.textDocument.uri, word, ".int"); if (newuri !== "") { - return [{ - targetUri: newuri, - targetRange: Range.create(Position.create(0, 0), Position.create(1, 0)), - originSelectionRange: wordrange, - targetSelectionRange: Range.create(Position.create(0, 8), Position.create(1, 0)) - }]; + return [ + { + targetUri: newuri, + targetRange: Range.create(Position.create(0, 0), Position.create(1, 0)), + originSelectionRange: wordrange, + targetSelectionRange: Range.create(Position.create(0, 8), Position.create(1, 0)), + }, + ]; } } } } - } - else if ((parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_label_attrindex) || - (parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_extrfn_attrindex)) { + } else if ( + (parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_label_attrindex) || + (parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_extrfn_attrindex) + ) { // This is a routine label // Get the range and text of the label let labelrange: Range; if (parsed[params.position.line][i].s == ld.cos_extrfn_attrindex) { // This is the $$ before the label - labelrange = findFullRange(params.position.line, parsed, i + 1, parsed[params.position.line][i + 1].p, parsed[params.position.line][i + 1].p + parsed[params.position.line][i + 1].c); - } - else { + labelrange = findFullRange( + params.position.line, + parsed, + i + 1, + parsed[params.position.line][i + 1].p, + parsed[params.position.line][i + 1].p + parsed[params.position.line][i + 1].c, + ); + } else { // This is the label labelrange = findFullRange(params.position.line, parsed, i, symbolstart, symbolend); } @@ -547,8 +685,7 @@ export async function onDefinition(params: TextDocumentPositionParams) { // Now that we got the label text, add the $$ to the front of the range if (parsed[params.position.line][i].s == ld.cos_extrfn_attrindex) { labelrange.start.character = labelrange.start.character - 2; - } - else if (i !== 0 && parsed[params.position.line][i - 1].s == ld.cos_extrfn_attrindex) { + } else if (i !== 0 && parsed[params.position.line][i - 1].s == ld.cos_extrfn_attrindex) { labelrange.start.character = labelrange.start.character - 2; } @@ -561,21 +698,32 @@ export async function onDefinition(params: TextDocumentPositionParams) { if ( labelidx + 2 < parsed[params.position.line].length && parsed[params.position.line][labelidx + 1].s == ld.cos_delim_attrindex && - doc.getText(Range.create( - Position.create(params.position.line, parsed[params.position.line][labelidx + 1].p), - Position.create(params.position.line, parsed[params.position.line][labelidx + 1].p + parsed[params.position.line][labelidx + 1].c) - )) === "^" + doc.getText( + Range.create( + Position.create(params.position.line, parsed[params.position.line][labelidx + 1].p), + Position.create( + params.position.line, + parsed[params.position.line][labelidx + 1].p + parsed[params.position.line][labelidx + 1].c, + ), + ), + ) === "^" ) { // The token following the label is a caret, so this label has a routine name for (let j = labelidx + 2; j < parsed[params.position.line].length; j++) { - if (parsed[params.position.line][j].l == ld.cos_langindex && parsed[params.position.line][j].s == ld.cos_rtnname_attrindex) { + if ( + parsed[params.position.line][j].l == ld.cos_langindex && + parsed[params.position.line][j].s == ld.cos_rtnname_attrindex + ) { // This is the routine name routine = doc.getText( Range.create( Position.create(params.position.line, parsed[params.position.line][j].p), - Position.create(params.position.line, parsed[params.position.line][j].p + parsed[params.position.line][j].c) - ) + Position.create( + params.position.line, + parsed[params.position.line][j].p + parsed[params.position.line][j].c, + ), + ), ); break; } @@ -585,7 +733,9 @@ export async function onDefinition(params: TextDocumentPositionParams) { // If the current file is a routine, get its name let currentroutine = ""; if (doc.languageId === "objectscript" || doc.languageId === "objectscript-int") { - currentroutine = doc.getText(Range.create(Position.create(0, parsed[0][1].p), Position.create(0, parsed[0][1].p + parsed[0][1].c))); + currentroutine = doc.getText( + Range.create(Position.create(0, parsed[0][1].p), Position.create(0, parsed[0][1].p + parsed[0][1].c)), + ); } if (routine !== "" && routine !== currentroutine) { @@ -593,9 +743,16 @@ export async function onDefinition(params: TextDocumentPositionParams) { // Check if this routine is a MAC or INT const indexrespdata = await makeRESTRequest("POST", 1, "/action/index", server, [routine + ".int"]); - if (indexrespdata !== undefined && indexrespdata.data.result.content.length > 0 && indexrespdata.data.result.content[0].status === "") { + if ( + indexrespdata !== undefined && + indexrespdata.data.result.content.length > 0 && + indexrespdata.data.result.content[0].status === "" + ) { let ext = ".int"; - if (indexrespdata.data.result.content[0].others.length > 0 && indexrespdata.data.result.content[0].others[0].slice(-3) === "mac") { + if ( + indexrespdata.data.result.content[0].others.length > 0 && + indexrespdata.data.result.content[0].others[0].slice(-3) === "mac" + ) { // This is a MAC routine ext = ".mac"; } @@ -620,25 +777,24 @@ export async function onDefinition(params: TextDocumentPositionParams) { } const firstcharcode = rtnText[k].charCodeAt(0); if ( - (firstcharcode > 47 && firstcharcode < 58) || (firstcharcode > 64 && firstcharcode < 91) || - (firstcharcode > 96 && firstcharcode < 123) || (firstcharcode === 37) + (firstcharcode > 47 && firstcharcode < 58) || + (firstcharcode > 64 && firstcharcode < 91) || + (firstcharcode > 96 && firstcharcode < 123) || + firstcharcode === 37 ) { // This is the first label following the one we needed the definition for, so cut off the preview range here targetrange.end = Position.create(k, 0); break; } - } - else if ( + } else if ( rtnText[k].startsWith(label) && - ( - rtnText[k].trimEnd().length == label.length || // The label is the whole line + (rtnText[k].trimEnd().length == label.length || // The label is the whole line / |\t|\(/.test(rtnText[k].charAt(label.length)) || // The label is followed by space, tab or ( // The label is followed by a comment rtnText[k].slice(label.length).startsWith(";") || rtnText[k].slice(label.length).startsWith("##;") || rtnText[k].slice(label.length).startsWith("//") || - rtnText[k].slice(label.length).startsWith("/*") - ) + rtnText[k].slice(label.length).startsWith("/*")) ) { // This is the label definition targetselrange = Range.create(k, 0, k, label.length); @@ -651,28 +807,30 @@ export async function onDefinition(params: TextDocumentPositionParams) { const trimmed = rtnText[pvrln].trim(); if (trimmed === "") { targetrange.end.line = pvrln; - } - else if ( - trimmed.slice(0, 3) === "##;" || trimmed.slice(0, 2) === "//" || trimmed.slice(0, 1) === ";" || - trimmed.slice(0, 2) === "#;" || trimmed.slice(0, 2) === "/*" + } else if ( + trimmed.slice(0, 3) === "##;" || + trimmed.slice(0, 2) === "//" || + trimmed.slice(0, 1) === ";" || + trimmed.slice(0, 2) === "#;" || + trimmed.slice(0, 2) === "/*" ) { targetrange.end.line = pvrln; - } - else { + } else { break; } } - return [{ - targetUri: newuri, - targetRange: targetrange, - originSelectionRange: labelrange, - targetSelectionRange: targetselrange - }]; + return [ + { + targetUri: newuri, + targetRange: targetrange, + originSelectionRange: labelrange, + targetSelectionRange: targetselrange, + }, + ]; } } } - } - else { + } else { // This label is in the current routine let targetselrange = Range.create(Position.create(0, 0), Position.create(0, 0)); @@ -688,17 +846,24 @@ export async function onDefinition(params: TextDocumentPositionParams) { break; } if ( - parsed[line].length > 0 && parsed[line][0].l == ld.cos_langindex && - parsed[line][0].s == ld.cos_label_attrindex && parsed[line][0].p == 0 + parsed[line].length > 0 && + parsed[line][0].l == ld.cos_langindex && + parsed[line][0].s == ld.cos_label_attrindex && + parsed[line][0].p == 0 ) { // This is a label if (linect > 0) { // This is the first label following the one we needed the definition for, so cut off the preview range here targetrange.end = Position.create(line, 0); break; - } - else { - const firstwordrange = findFullRange(line, parsed, 0, parsed[line][0].p, parsed[line][0].p + parsed[line][0].c); + } else { + const firstwordrange = findFullRange( + line, + parsed, + 0, + parsed[line][0].p, + parsed[line][0].p + parsed[line][0].c, + ); const firstwordtext = doc.getText(firstwordrange); if (firstwordtext == label) { // This is the correct label @@ -713,23 +878,28 @@ export async function onDefinition(params: TextDocumentPositionParams) { for (let pvrln = targetrange.end.line - 1; pvrln > targetrange.start.line; pvrln--) { if (parsed[pvrln].length === 0) { targetrange.end.line = pvrln; - } - else if (parsed[pvrln][0].l === ld.cos_langindex && (parsed[pvrln][0].s === ld.cos_comment_attrindex || parsed[pvrln][0].s === ld.cos_dcom_attrindex)) { + } else if ( + parsed[pvrln][0].l === ld.cos_langindex && + (parsed[pvrln][0].s === ld.cos_comment_attrindex || parsed[pvrln][0].s === ld.cos_dcom_attrindex) + ) { targetrange.end.line = pvrln; - } - else { + } else { break; } } - return [{ - targetUri: params.textDocument.uri, - targetRange: targetrange, - originSelectionRange: labelrange, - targetSelectionRange: targetselrange - }]; + return [ + { + targetUri: params.textDocument.uri, + targetRange: targetrange, + originSelectionRange: labelrange, + targetSelectionRange: targetselrange, + }, + ]; } - } - else if (parsed[params.position.line][i].l == ld.sql_langindex && parsed[params.position.line][i].s == ld.sql_iden_attrindex) { + } else if ( + parsed[params.position.line][i].l == ld.sql_langindex && + parsed[params.position.line][i].s == ld.sql_iden_attrindex + ) { // This is a SQL identifier // Get the full text of the selection @@ -740,20 +910,21 @@ export async function onDefinition(params: TextDocumentPositionParams) { let keytext: string = ""; for (let ln = params.position.line; ln >= 0; ln--) { if (!parsed[ln]?.length) continue; - for (let tk = (parsed[ln].length - 1); tk >= 0; tk--) { + for (let tk = parsed[ln].length - 1; tk >= 0; tk--) { if (ln === params.position.line && parsed[ln][tk].p >= idenrange.start.character) { // Start looking when we pass the full range of the selected identifier continue; } if ( parsed[ln][tk].l == ld.sql_langindex && - (parsed[ln][tk].s == ld.sql_skey_attrindex || parsed[ln][tk].s == ld.sql_qkey_attrindex || parsed[ln][tk].s == ld.sql_ekey_attrindex) + (parsed[ln][tk].s == ld.sql_skey_attrindex || + parsed[ln][tk].s == ld.sql_qkey_attrindex || + parsed[ln][tk].s == ld.sql_ekey_attrindex) ) { // This is a keyword - const tmpkeytext = doc.getText(Range.create( - ln, parsed[ln][tk].p, - ln, parsed[ln][tk].p + parsed[ln][tk].c - )).toLowerCase(); + const tmpkeytext = doc + .getText(Range.create(ln, parsed[ln][tk].p, ln, parsed[ln][tk].p + parsed[ln][tk].c)) + .toLowerCase(); if (tmpkeytext !== "as") { // Found the correct keyword keytext = tmpkeytext; @@ -768,9 +939,13 @@ export async function onDefinition(params: TextDocumentPositionParams) { } if ( - (keytext === "join" || keytext === "from" || keytext === "into" || - keytext === "lock" || keytext === "unlock" || keytext === "table" || - keytext === "update") + keytext === "join" || + keytext === "from" || + keytext === "into" || + keytext === "lock" || + keytext === "unlock" || + keytext === "table" || + keytext === "update" ) { // This identifier is a table name @@ -787,36 +962,49 @@ export async function onDefinition(params: TextDocumentPositionParams) { // Query the server to get the origin class of this property const data: QueryData = { query: "SELECT Origin FROM %Dictionary.CompiledProperty WHERE Parent = ? AND name = ?", - parameters: [normalizedname, propname] + parameters: [normalizedname, propname], }; const queryrespdata = await makeRESTRequest("POST", 1, "/action/query", server, data); if (queryrespdata !== undefined) { - if (Array.isArray(queryrespdata?.data?.result?.content) && queryrespdata.data.result.content.length > 0) { + if ( + Array.isArray(queryrespdata?.data?.result?.content) && + queryrespdata.data.result.content.length > 0 + ) { // We got data back // Get the uri of the origin class const originclass = queryrespdata.data.result.content[0].Origin; - return classMemberLocationLink(params.textDocument.uri, originclass, propname, "Property|Relationship", idenrange, server); - } - else { + return classMemberLocationLink( + params.textDocument.uri, + originclass, + propname, + "Property|Relationship", + idenrange, + server, + ); + } else { // Query completed successfully but we got back no data. // This likely means that the base class hasn't been compiled yet or the member had the wrong token type. return null; } } } - } - else { + } else { // This table is a class // Normalize the class name if there are imports - const normalizedname = await normalizeClassname(doc, parsed, iden.replace(/_/g, "."), server, params.position.line); + const normalizedname = await normalizeClassname( + doc, + parsed, + iden.replace(/_/g, "."), + server, + params.position.line, + ); if (normalizedname != "") { return classLocationLink(params.textDocument.uri, normalizedname, idenrange, server); } } - } - else if (keytext === "call" && iden.indexOf("_") !== -1) { + } else if (keytext === "call" && iden.indexOf("_") !== -1) { // This identifier is a Query or ClassMethod being invoked as a SqlProc const clsname = iden.slice(0, iden.lastIndexOf("_")).replace(/_/g, "."); @@ -830,7 +1018,7 @@ export async function onDefinition(params: TextDocumentPositionParams) { querystr = querystr.concat("SELECT Origin FROM %Dictionary.CompiledQuery WHERE Parent = ? AND name = ?"); const data: QueryData = { query: querystr, - parameters: [normalizedname, procname, normalizedname, procname] + parameters: [normalizedname, procname, normalizedname, procname], }; const queryrespdata = await makeRESTRequest("POST", 1, "/action/query", server, data); if (Array.isArray(queryrespdata?.data?.result?.content) && queryrespdata.data.result.content.length > 0) { @@ -838,50 +1026,73 @@ export async function onDefinition(params: TextDocumentPositionParams) { // Get the uri of the origin class const originclass = queryrespdata.data.result.content[0].Origin; - return classMemberLocationLink(params.textDocument.uri, originclass, procname, "ClassMethod|Query", idenrange, server); + return classMemberLocationLink( + params.textDocument.uri, + originclass, + procname, + "ClassMethod|Query", + idenrange, + server, + ); } } - } - else { + } else { // This identifier is a property - if ((iden.split(".").length - 1) > 0) { + if (iden.split(".").length - 1 > 0) { // We won't resolve properties that don't contain the table name const tblname = iden.slice(0, iden.lastIndexOf(".")); const propname = iden.slice(iden.lastIndexOf(".") + 1); if (tblname.lastIndexOf("_") > tblname.lastIndexOf(".")) { // This table is projected from a multi-dimensional property, so we can't provide any info - } - else { + } else { // Normalize the class name if there are imports - const normalizedname = await normalizeClassname(doc, parsed, tblname.replace(/_/g, "."), server, params.position.line); + const normalizedname = await normalizeClassname( + doc, + parsed, + tblname.replace(/_/g, "."), + server, + params.position.line, + ); if (normalizedname !== "") { // Query the server to get the origin class of this property const data: QueryData = { query: "SELECT Origin FROM %Dictionary.CompiledProperty WHERE Parent = ? AND name = ?", - parameters: [normalizedname, propname] + parameters: [normalizedname, propname], }; const queryrespdata = await makeRESTRequest("POST", 1, "/action/query", server, data); - if (Array.isArray(queryrespdata?.data?.result?.content) && queryrespdata.data.result.content.length > 0) { + if ( + Array.isArray(queryrespdata?.data?.result?.content) && + queryrespdata.data.result.content.length > 0 + ) { // We got data back // Get the uri of the origin class const originclass = queryrespdata.data.result.content[0].Origin; - return classMemberLocationLink(params.textDocument.uri, originclass, propname, "Property|Relationship", idenrange, server); + return classMemberLocationLink( + params.textDocument.uri, + originclass, + propname, + "Property|Relationship", + idenrange, + server, + ); } } } } } - } - else if (parsed[params.position.line][i].l == ld.cls_langindex && parsed[params.position.line][i].s == ld.cls_cparam_attrindex) { + } else if ( + parsed[params.position.line][i].l == ld.cls_langindex && + parsed[params.position.line][i].s == ld.cls_cparam_attrindex + ) { // This is a class name parameter // Get the full text of the selection const paramrange = findFullRange(params.position.line, parsed, i, symbolstart, symbolend); const param = doc.getText(paramrange); - if (corePropertyParams.some(e => e.name == param)) { + if (corePropertyParams.some((e) => e.name == param)) { // This is a core Property data type parameter, so don't return anything return null; } @@ -898,26 +1109,40 @@ export async function onDefinition(params: TextDocumentPositionParams) { if (thisclass == normalizedcls) { // The parameter may be defined in this class - const currentClassLinks = findMemberInCurrentClass(doc, parsed, params.textDocument.uri, param, "Parameter", paramrange); + const currentClassLinks = findMemberInCurrentClass( + doc, + parsed, + params.textDocument.uri, + param, + "Parameter", + paramrange, + ); if (currentClassLinks) return currentClassLinks; } // The parameter is defined in another class const queryrespdata = await makeRESTRequest("POST", 1, "/action/query", server, { - query: "SELECT Origin FROM %Dictionary.CompiledParameter WHERE Name = ? AND (Parent = ? OR " + + query: + "SELECT Origin FROM %Dictionary.CompiledParameter WHERE Name = ? AND (Parent = ? OR " + "Parent %INLIST (SELECT $LISTFROMSTRING(PropertyClass) FROM %Dictionary.CompiledClass WHERE Name = ?))", - parameters: [param, normalizedcls, thisclass] + parameters: [param, normalizedcls, thisclass], }); if (Array.isArray(queryrespdata?.data?.result?.content) && queryrespdata.data.result.content.length > 0) { // We got data back const originclass = queryrespdata.data.result.content[0].Origin; if (originclass !== "") { - return classMemberLocationLink(params.textDocument.uri, originclass, param, "Parameter", paramrange, server); + return classMemberLocationLink( + params.textDocument.uri, + originclass, + param, + "Parameter", + paramrange, + server, + ); } } - } - else if ( + } else if ( parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_super_attrindex && doc.languageId == "objectscript-class" @@ -948,21 +1173,23 @@ export async function onDefinition(params: TextDocumentPositionParams) { if ( parsed[ln][0].l == ld.cls_langindex && parsed[ln][0].s == ld.cls_keyword_attrindex && - doc.getText(Range.create(ln, parsed[ln][0].p, ln, parsed[ln][0].p + parsed[ln][0].c)).toLowerCase() == "class" + doc.getText(Range.create(ln, parsed[ln][0].p, ln, parsed[ln][0].p + parsed[ln][0].c)).toLowerCase() == + "class" ) { // This is the class definition line - let seenExtends = false, seenInheritance = false; + let seenExtends = false, + seenInheritance = false; for (let j = 1; j < parsed[ln].length; j++) { - if ( - seenExtends && - parsed[ln][j].l == ld.cls_langindex && - parsed[ln][j].s == ld.cls_clsname_attrindex - ) { + if (seenExtends && parsed[ln][j].l == ld.cls_langindex && parsed[ln][j].s == ld.cls_clsname_attrindex) { // This is a piece of a subclass if (!superclasses.length) superclasses.push(""); - superclasses[superclasses.length - 1] += doc.getText(Range.create(ln, parsed[ln][j].p, ln, parsed[ln][j].p + parsed[ln][j].c)); + superclasses[superclasses.length - 1] += doc.getText( + Range.create(ln, parsed[ln][j].p, ln, parsed[ln][j].p + parsed[ln][j].c), + ); } else if (parsed[ln][j].l == ld.cls_langindex && parsed[ln][j].s == ld.cls_keyword_attrindex) { - const clsKeyword = doc.getText(Range.create(ln, parsed[ln][j].p, ln, parsed[ln][j].p + parsed[ln][j].c)).toLowerCase(); + const clsKeyword = doc + .getText(Range.create(ln, parsed[ln][j].p, ln, parsed[ln][j].p + parsed[ln][j].c)) + .toLowerCase(); if (clsKeyword == "extends") { seenExtends = true; } else if (clsKeyword == "inheritance") { @@ -984,7 +1211,10 @@ export async function onDefinition(params: TextDocumentPositionParams) { parsed[ln][j].l == ld.cls_langindex && parsed[ln][j].s == ld.cls_str_attrindex ) { - rightInheritance = doc.getText(Range.create(ln, parsed[ln][j].p, ln, parsed[ln][j].p + parsed[ln][j].c)).toLowerCase() == "right"; + rightInheritance = + doc + .getText(Range.create(ln, parsed[ln][j].p, ln, parsed[ln][j].p + parsed[ln][j].c)) + .toLowerCase() == "right"; break; } } @@ -997,21 +1227,30 @@ export async function onDefinition(params: TextDocumentPositionParams) { if (superclasses.length && thisMethod) { // Get the list of allsuperclasses that have this method const queryrespdata = await makeRESTRequest("POST", 1, "/action/query", server, { - query: "SELECT Origin, Parent AS Super FROM %Dictionary.CompiledMethod WHERE Name = ? AND Parent %INLIST $LISTFROMSTRING(?)", - parameters: [quoteUDLIdentifier(thisMethod, 0), superclasses.join(",")] + query: + "SELECT Origin, Parent AS Super FROM %Dictionary.CompiledMethod WHERE Name = ? AND Parent %INLIST $LISTFROMSTRING(?)", + parameters: [quoteUDLIdentifier(thisMethod, 0), superclasses.join(",")], }); if (Array.isArray(queryrespdata?.data?.result?.content) && queryrespdata.data.result.content.length) { // Determine which class ##super is in if (rightInheritance) superclasses.reverse(); - originclass = queryrespdata.data.result.content.sort((a, b) => superclasses.indexOf(a.Super) - superclasses.indexOf(b.Super))[0].Origin; + originclass = queryrespdata.data.result.content.sort( + (a, b) => superclasses.indexOf(a.Super) - superclasses.indexOf(b.Super), + )[0].Origin; } if (originclass) { const superRange = Range.create(params.position.line, symbolstart, params.position.line, symbolend); - return classMemberLocationLink(params.textDocument.uri, originclass, thisMethod, "Method|ClassMethod", superRange, server); + return classMemberLocationLink( + params.textDocument.uri, + originclass, + thisMethod, + "Method|ClassMethod", + superRange, + server, + ); } } - } - else if ( + } else if ( parsed[params.position.line][i].l == ld.xml_langindex && parsed[params.position.line][i].s == ld.xml_str_attrindex && doc.languageId == "objectscript-class" @@ -1037,7 +1276,14 @@ export async function onDefinition(params: TextDocumentPositionParams) { if (cls == "") cls = thisClass; if (cls == thisClass) { // The method may be defined in this class - const currentClassLinks = findMemberInCurrentClass(doc, parsed, params.textDocument.uri, method, "ClassMethod", strRange); + const currentClassLinks = findMemberInCurrentClass( + doc, + parsed, + params.textDocument.uri, + method, + "ClassMethod", + strRange, + ); if (currentClassLinks) return currentClassLinks; } // The method is defined in another class @@ -1045,14 +1291,21 @@ export async function onDefinition(params: TextDocumentPositionParams) { // Use the same query as above even though we don't // need Stub so we don't create another cached query query: "SELECT Origin, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND name = ?", - parameters: [cls, method] + parameters: [cls, method], }); if (Array.isArray(queryrespdata?.data?.result?.content) && queryrespdata.data.result.content.length > 0) { // We got data back const originclass = queryrespdata.data.result.content[0].Origin; if (originclass !== "") { - return classMemberLocationLink(params.textDocument.uri, originclass, method, "ClassMethod", strRange, server); + return classMemberLocationLink( + params.textDocument.uri, + originclass, + method, + "ClassMethod", + strRange, + server, + ); } } } diff --git a/server/src/providers/diagnostic.ts b/server/src/providers/diagnostic.ts index f9194e5..f599bd1 100644 --- a/server/src/providers/diagnostic.ts +++ b/server/src/providers/diagnostic.ts @@ -6,8 +6,8 @@ import { DiagnosticTag, DocumentDiagnosticParams, DocumentDiagnosticReport, - DocumentDiagnosticReportKind -} from 'vscode-languageserver'; + DocumentDiagnosticReportKind, +} from "vscode-languageserver"; import { findFullRange, @@ -18,18 +18,18 @@ import { makeRESTRequest, normalizeClassname, quoteUDLIdentifier, - isClassMember -} from '../utils/functions'; -import { zutilFunctions, lexerLanguages, documents } from '../utils/variables'; -import { ServerSpec, StudioOpenDialogFile, QueryData } from '../utils/types'; -import * as ld from '../utils/languageDefinitions'; + isClassMember, +} from "../utils/functions"; +import { zutilFunctions, lexerLanguages, documents } from "../utils/variables"; +import { ServerSpec, StudioOpenDialogFile, QueryData } from "../utils/types"; +import * as ld from "../utils/languageDefinitions"; import parameterTypes from "../documentation/parameterTypes.json"; import sqlReservedWords from "../documentation/sqlReservedWords.json"; /** * Helper method that appends `range` to value of `key` in `map` * if it exists, or creates a new entry for `key` in `map` if it doesn't. - * + * * @param map Map between ClassMember objects and Ranges in a document. * @param key The key in `map`. * @param range The Range to add to `map`.get(`key`). @@ -38,18 +38,17 @@ function addRangeToMapVal(map: Map, key: string, range: Range) let ranges = map.get(key); if (ranges === undefined) { ranges = [range]; - } - else { + } else { ranges.push(range); } map.set(key, ranges); -}; +} const syntaxError = "Syntax error"; /** Normalize the description for this error token */ function normalizeErrorDesc(e?: string): string { - return !e || e.includes("HRESULT") ? syntaxError : e[0].toUpperCase() + e.slice(1).replace(/'/g, "\""); + return !e || e.includes("HRESULT") ? syntaxError : e[0].toUpperCase() + e.slice(1).replace(/'/g, '"'); } /** @@ -67,7 +66,9 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise { - return !(settings.diagnostics.suppressSyntaxErrors).includes(lexerLanguages.find(ll => ll.index == language)?.moniker); + return !(settings.diagnostics.suppressSyntaxErrors).includes( + lexerLanguages.find((ll) => ll.index == language)?.moniker, + ); }; let files: StudioOpenDialogFile[] = []; @@ -78,25 +79,57 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 3)", "*.inc", 1, 1, 1, 1, 0, 0] + parameters: [ + "*.mac,*.int,*.obj", + 1, + 1, + 1, + 1, + 1, + 0, + 'NOT (Name %PATTERN \'.E1"."0.1"G"1N1".obj"\' AND $LENGTH(Name,\'.\') > 3)', + "*.inc", + 1, + 1, + 1, + 1, + 0, + 0, + ], }; - } - else if (!settings.diagnostics.routines && (settings.diagnostics.classes || settings.diagnostics.deprecation)) { + } else if (!settings.diagnostics.routines && (settings.diagnostics.classes || settings.diagnostics.deprecation)) { // Get all classes querydata = { query: "SELECT Name||'.cls' AS Name FROM %Dictionary.ClassDefinition", - parameters: [] + parameters: [], }; - } - else { + } else { // Get all routines querydata = { - query: "SELECT DISTINCT BY ($PIECE(Name,'.',1,$LENGTH(Name,'.')-1)) Name FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?,?) " + + query: + "SELECT DISTINCT BY ($PIECE(Name,'.',1,$LENGTH(Name,'.')-1)) Name FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?,?) " + "UNION ALL %PARALLEL SELECT Name FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?)", - parameters: ["*.mac,*.int,*.obj", 1, 1, 1, 1, 1, 0, "NOT (Name %PATTERN '.E1\".\"0.1\"G\"1N1\".obj\"' AND $LENGTH(Name,'.') > 3)", "*.inc", 1, 1, 1, 1, 0, 0] + parameters: [ + "*.mac,*.int,*.obj", + 1, + 1, + 1, + 1, + 1, + 0, + 'NOT (Name %PATTERN \'.E1"."0.1"G"1N1".obj"\' AND $LENGTH(Name,\'.\') > 3)', + "*.inc", + 1, + 1, + 1, + 1, + 0, + 0, + ], }; } @@ -105,9 +138,10 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 0; } } @@ -164,15 +208,16 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 0 && parsed[0].length > 0 && - + parsed.length > 0 && + parsed[0].length > 0 && // The first character was parsed as a COS command - parsed[0][0].l == ld.cos_langindex && parsed[0][0].s == ld.cos_command_attrindex && - + parsed[0][0].l == ld.cos_langindex && + parsed[0][0].s == ld.cos_command_attrindex && // The document begins with "ROUTINE" (case-insensitive) - doc.getText(Range.create(Position.create(0, parsed[0][0].p), Position.create(0, parsed[0][0].p + parsed[0][0].c))).toLowerCase() === "routine"; + doc + .getText(Range.create(Position.create(0, parsed[0][0].p), Position.create(0, parsed[0][0].p + parsed[0][0].c))) + .toLowerCase() === "routine"; if (!firstlineisroutine && ["objectscript", "objectscript-int", "objectscript-macros"].includes(doc.languageId)) { // The ROUTINE header is required @@ -180,7 +225,7 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 0 && parsed[0][t - 1].s == 0 && + t > 0 && + parsed[0][t - 1].s == 0 && diagnostics.length && [syntaxError, diagnostics[diagnostics.length - 1].message].includes(errorDesc) ) { // This error token is a continuation of the same underlying error diagnostics[diagnostics.length - 1].range.end = Position.create(0, parsed[0][t].p + parsed[0][t].c); - } - else { + } else { // This is a token for a new error diagnostics.push({ severity: DiagnosticSeverity.Error, range: Range.create(0, parsed[0][t].p, 0, parsed[0][t].p + parsed[0][t].c), message: errorDesc, - source: 'InterSystems Language Server' + source: "InterSystems Language Server", }); } } @@ -249,13 +294,14 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise (ifZeroStartPos.line + 1)) diagnostics.push({ - severity: DiagnosticSeverity.Hint, - range: Range.create(ifZeroStartPos, Position.create(i, 0)), - message: "Unused code detected", - tags: [DiagnosticTag.Unnecessary], - source: 'InterSystems Language Server' - }); + if (i > ifZeroStartPos.line + 1) + diagnostics.push({ + severity: DiagnosticSeverity.Hint, + range: Range.create(ifZeroStartPos, Position.create(i, 0)), + message: "Unused code detected", + tags: [DiagnosticTag.Unnecessary], + source: "InterSystems Language Server", + }); ifZeroStartPos = undefined; continue; // No more diagnostics on this line } @@ -280,17 +326,16 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 3 && doc.getText(Range.create(i, parsed[i][1].p, i, parsed[i][1].p + parsed[i][1].c)) == "DEFAULTGLOBAL") { + doc.getText( + Range.create( + i, + parsed[i][parsed[i].length - 1].p, + i, + parsed[i][parsed[i].length - 1].p + parsed[i][parsed[i].length - 1].c, + ), + ) == ";"; + if ( + isPersistent && + parsed[i].length > 3 && + doc.getText(Range.create(i, parsed[i][1].p, i, parsed[i][1].p + parsed[i][1].c)) == "DEFAULTGLOBAL" + ) { // Check if the provided value is a valid global name, with leading caret let inKeywords = false; for (let tkn = 2; tkn < parsed[i].length; tkn++) { @@ -420,17 +483,25 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 3 && - parsed[i][2].l == ld.cls_langindex && parsed[i][2].s === ld.cls_keyword_attrindex && + parsed[i][2].l == ld.cls_langindex && + parsed[i][2].s === ld.cls_keyword_attrindex && doc.getText(Range.create(i, parsed[i][2].p, i, parsed[i][2].p + parsed[i][2].c)).toLowerCase() === "as" ) { // This Parameter has a type @@ -453,10 +525,9 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 4) { + } else if (parsed[i].length > 4) { // The type is valid // See if this Parameter has a value @@ -467,7 +538,8 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise closingtkn && - doc.getText(Range.create(i, parsed[i][closingtkn + 1].p, i, parsed[i][closingtkn + 1].p + parsed[i][closingtkn + 1].c)) === "=" + closingtkn && + parsed[i].length > closingtkn && + doc.getText( + Range.create( + i, + parsed[i][closingtkn + 1].p, + i, + parsed[i][closingtkn + 1].p + parsed[i][closingtkn + 1].c, + ), + ) === "=" ) { // There is a value following the = valuetkn = closingtkn + 2; } - } - else if (delimtext === "=") { + } else if (delimtext === "=") { // The value follows this delimiter valuetkn = 5; - } - else { + } else { // Delimiter is a ; so there isn't a value to evaluate } if (valuetkn && parsed[i].length > valuetkn + 1) { const valueEndTkn = parsed[i].length - (endsWithSemi ? 2 : 1); - const valtext = doc.getText(Range.create(i, parsed[i][valuetkn].p, i, parsed[i][valuetkn].p + parsed[i][valuetkn].c)); - const valrange = Range.create(i, parsed[i][valuetkn].p, i, parsed[i][valueEndTkn].p + parsed[i][valueEndTkn].c); + const valtext = doc.getText( + Range.create(i, parsed[i][valuetkn].p, i, parsed[i][valuetkn].p + parsed[i][valuetkn].c), + ); + const valrange = Range.create( + i, + parsed[i][valuetkn].p, + i, + parsed[i][valueEndTkn].p + parsed[i][valueEndTkn].c, + ); if ( - (thistypedoc.name === "STRING" && (parsed[i][valuetkn].l !== ld.cls_langindex || parsed[i][valuetkn].s !== ld.cls_str_attrindex)) || - (thistypedoc.name === "COSEXPRESSION" && (parsed[i][valuetkn].l !== ld.cls_langindex || parsed[i][valuetkn].s !== ld.cls_str_attrindex)) || - (thistypedoc.name === "CLASSNAME" && (parsed[i][valuetkn].l !== ld.cls_langindex || parsed[i][valuetkn].s !== ld.cls_str_attrindex)) || - (thistypedoc.name === "INTEGER" && (parsed[i][valuetkn].l !== ld.cls_langindex || parsed[i][valuetkn].s !== ld.cls_num_attrindex)) || - (thistypedoc.name === "BOOLEAN" && (parsed[i][valuetkn].l !== ld.cls_langindex || parsed[i][valuetkn].s !== ld.cls_num_attrindex || (valtext !== "1" && valtext !== "0"))) + (thistypedoc.name === "STRING" && + (parsed[i][valuetkn].l !== ld.cls_langindex || parsed[i][valuetkn].s !== ld.cls_str_attrindex)) || + (thistypedoc.name === "COSEXPRESSION" && + (parsed[i][valuetkn].l !== ld.cls_langindex || parsed[i][valuetkn].s !== ld.cls_str_attrindex)) || + (thistypedoc.name === "CLASSNAME" && + (parsed[i][valuetkn].l !== ld.cls_langindex || parsed[i][valuetkn].s !== ld.cls_str_attrindex)) || + (thistypedoc.name === "INTEGER" && + (parsed[i][valuetkn].l !== ld.cls_langindex || parsed[i][valuetkn].s !== ld.cls_num_attrindex)) || + (thistypedoc.name === "BOOLEAN" && + (parsed[i][valuetkn].l !== ld.cls_langindex || + parsed[i][valuetkn].s !== ld.cls_num_attrindex || + (valtext !== "1" && valtext !== "0"))) ) { // Allow curly brace syntax for all types but COSEXPRESSION if (thistypedoc.name == "COSEXPRESSION" || !valtext.startsWith("{")) { @@ -508,24 +600,23 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise file.Name === classname + ".cls"); + const filtered = files.filter((file) => file.Name === classname + ".cls"); if (filtered.length !== 1 && !classname.startsWith("%SYSTEM.")) { diagnostics.push({ severity: DiagnosticSeverity.Warning, range: valrange, message: `Class "${classname}" does not exist in namespace "${baseNs}"`, - source: 'InterSystems Language Server' + source: "InterSystems Language Server", }); } } @@ -544,23 +635,26 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 0 && settings.diagnostics.classes && lastpkgend != pkgrange.end.character && - !files.some(f => f.Name.startsWith(pkg + ".") && f.Name.endsWith(".cls")) + files.length > 0 && + settings.diagnostics.classes && + lastpkgend != pkgrange.end.character && + !files.some((f) => f.Name.startsWith(pkg + ".") && f.Name.endsWith(".cls")) ) { // This package does not exist diagnostics.push({ severity: DiagnosticSeverity.Error, range: pkgrange, message: `Package "${pkg}" does not exist in namespace "${baseNs}"`, - source: 'InterSystems Language Server' + source: "InterSystems Language Server", }); } lastpkgend = pkgrange.end.character; @@ -610,22 +712,21 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 0 && ((parsed[i][j].l == ld.cls_langindex && parsed[i][j].s == ld.cls_clsname_attrindex) || (parsed[i][j].l == ld.cos_langindex && parsed[i][j].s == ld.cos_clsname_attrindex)) && - (settings.diagnostics.classes || settings.diagnostics.deprecation) && !ifZeroStartPos + (settings.diagnostics.classes || settings.diagnostics.deprecation) && + !ifZeroStartPos ) { // This is a class name // Don't validate a class name that follows the "Class" keyword if (j !== 0 && parsed[i][j - 1].l == ld.cls_langindex && parsed[i][j - 1].s == ld.cls_keyword_attrindex) { // The previous token is a UDL keyword - const prevkeytext = doc.getText(Range.create( - i, parsed[i][j - 1].p, - i, parsed[i][j - 1].p + parsed[i][j - 1].c - )).toLowerCase(); + const prevkeytext = doc + .getText(Range.create(i, parsed[i][j - 1].p, i, parsed[i][j - 1].p + parsed[i][j - 1].c)) + .toLowerCase(); if (prevkeytext === "class") { continue; } @@ -636,10 +737,7 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 0) { // The class couldn't be resolved with the imports @@ -673,14 +779,13 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise file.Name === normalizedname + ".cls"); + const filtered = files.filter((file) => file.Name === normalizedname + ".cls"); if (filtered.length !== 1) { // Exempt %SYSTEM classes because some of them don't have ^oddDEF entries if (settings.diagnostics.classes && !word.startsWith("%SYSTEM.")) { @@ -688,12 +793,11 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 0 && ((parsed[i][j].l == ld.cls_langindex && parsed[i][j].s == ld.cls_rtnname_attrindex) || (parsed[i][j].l == ld.cos_langindex && parsed[i][j].s == ld.cos_rtnname_attrindex)) && - settings.diagnostics.routines && !ifZeroStartPos + settings.diagnostics.routines && + !ifZeroStartPos ) { // This is a routine name @@ -735,10 +843,9 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise file.Name == (word + ".inc"))) { + if (!files.some((file) => file.Name == word + ".inc")) { diagnostics.push({ severity: DiagnosticSeverity.Error, range: wordrange, message: `Include file "${word}" does not exist in namespace "${baseNs}"`, - source: 'InterSystems Language Server' + source: "InterSystems Language Server", }); } - } else if (word.length && /^%?[\d.\p{L}]*$/u.test(word)) { // Need validation to avoid creating a bad regex + } else if (word.length && /^%?[\d.\p{L}]*$/u.test(word)) { + // Need validation to avoid creating a bad regex const regex = new RegExp(`^${word.replace(/\./g, ".")}\\.(mac|int|obj)$`); - if (!files.some(file => regex.test(file.Name))) { + if (!files.some((file) => regex.test(file.Name))) { diagnostics.push({ severity: DiagnosticSeverity.Error, range: wordrange, message: `Routine "${word}" does not exist in namespace "${baseNs}"`, - source: 'InterSystems Language Server' + source: "InterSystems Language Server", }); } } @@ -770,12 +878,13 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 0 && - parsed[i][j].l == ld.cos_langindex && ( - parsed[i][j].s == ld.cos_prop_attrindex || parsed[i][j].s == ld.cos_method_attrindex || - parsed[i][j].s == ld.cos_attr_attrindex || parsed[i][j].s == ld.cos_mem_attrindex) && + parsed[i][j].l == ld.cos_langindex && + (parsed[i][j].s == ld.cos_prop_attrindex || + parsed[i][j].s == ld.cos_method_attrindex || + parsed[i][j].s == ld.cos_attr_attrindex || + parsed[i][j].s == ld.cos_mem_attrindex) && settings.diagnostics.deprecation ) { // This is a class member (property/parameter/method) @@ -806,37 +915,36 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise= 0; tkn--) { + for (let tkn = ln == i ? j : parsed[ln].length - 1; tkn >= 0; tkn--) { if (parsed[ln][tkn].l != ld.cos_langindex || parsed[ln][tkn].s == ld.cos_zcom_attrindex) { brk = true; break; } if (parsed[ln][tkn].s == ld.cos_command_attrindex) { - if (["s", "set"].includes(doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)).toLowerCase())) { + if ( + ["s", "set"].includes( + doc + .getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) + .toLowerCase(), + ) + ) { isSet = true; if ( - tkn < parsed[ln].length - 1 && parsed[ln][tkn + 1].l == ld.cos_langindex && parsed[ln][tkn + 1].s === ld.cos_delim_attrindex && - doc.getText(Range.create(ln, parsed[ln][tkn + 1].p, ln, parsed[ln][tkn + 1].p + parsed[ln][tkn + 1].c)) == ":" + tkn < parsed[ln].length - 1 && + parsed[ln][tkn + 1].l == ld.cos_langindex && + parsed[ln][tkn + 1].s === ld.cos_delim_attrindex && + doc.getText( + Range.create(ln, parsed[ln][tkn + 1].p, ln, parsed[ln][tkn + 1].p + parsed[ln][tkn + 1].c), + ) == ":" ) { hasPc = true; } @@ -971,8 +1093,7 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 2 && ["property", "relationship"].includes( - doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)).toLowerCase() + } else if ( + j == 0 && + parsed[i][j].l == ld.cls_langindex && + parsed[i][j].s == ld.cls_keyword_attrindex && + isPersistent && + parsed[i].length > 2 && + ["property", "relationship"].includes( + doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)).toLowerCase(), ) ) { // This is the start of a UDL Property definition @@ -1062,29 +1186,34 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise 0 || parameters.size > 0 || properties.size > 0 || classes.size > 0)) { + if ( + settings.diagnostics.deprecation && + (methods.size > 0 || parameters.size > 0 || properties.size > 0 || classes.size > 0) + ) { // Query the database for all Deprecated members or classes that we're referencing // Build the query const querydata: QueryData = { - query: "SELECT Name, Parent AS Class, 'method' AS MemberType FROM %Dictionary.CompiledMethod WHERE Deprecated = 1 AND Parent %INLIST $LISTFROMSTRING(?) UNION ALL %PARALLEL " + + query: + "SELECT Name, Parent AS Class, 'method' AS MemberType FROM %Dictionary.CompiledMethod WHERE Deprecated = 1 AND Parent %INLIST $LISTFROMSTRING(?) UNION ALL %PARALLEL " + "SELECT Name, Parent AS Class, 'parameter' AS MemberType FROM %Dictionary.CompiledParameter WHERE Deprecated = 1 AND Parent %INLIST $LISTFROMSTRING(?) UNION ALL %PARALLEL " + "SELECT Name, Parent AS Class, 'property' AS MemberType FROM %Dictionary.CompiledProperty WHERE Deprecated = 1 AND Parent %INLIST $LISTFROMSTRING(?) UNION ALL %PARALLEL " + "SELECT Name, NULL AS Class, 'class' AS MemberType FROM %Dictionary.CompiledClass WHERE Deprecated = 1 AND Name %INLIST $LISTFROMSTRING(?)", parameters: [ - [...new Set([...methods.keys()].map(elem => { return elem.split(":::")[0] }))].join(","), - [...new Set([...parameters.keys()].map(elem => { return elem.split(":::")[0] }))].join(","), - [...new Set([...properties.keys()].map(elem => { return elem.split(":::")[0] }))].join(","), - [...classes.keys()].join(",") - ] + [ + ...new Set( + [...methods.keys()].map((elem) => { + return elem.split(":::")[0]; + }), + ), + ].join(","), + [ + ...new Set( + [...parameters.keys()].map((elem) => { + return elem.split(":::")[0]; + }), + ), + ].join(","), + [ + ...new Set( + [...properties.keys()].map((elem) => { + return elem.split(":::")[0]; + }), + ), + ].join(","), + [...classes.keys()].join(","), + ], }; // Make the request @@ -1194,14 +1345,11 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise otherNsDocs.delete(`${namespace}:::${(e.Name.endsWith(".int") || e.Name.endsWith(".obj")) ? `${e.Name.slice(0, -3)}mac` : e.Name}`)); + respdata.data.result.content.forEach((e) => + otherNsDocs.delete( + `${namespace}:::${e.Name.endsWith(".int") || e.Name.endsWith(".obj") ? `${e.Name.slice(0, -3)}mac` : e.Name}`, + ), + ); otherNsDocs.forEach((v, k) => { const [ns, doc] = k.split(":::"); if (ns == namespace) { - v.forEach((range) => diagnostics.push({ - severity: DiagnosticSeverity.Error, - range, - message: `${doc.endsWith("cls") ? "Class" : doc.endsWith("inc") ? "Include file" : "Routine"} "${doc.slice(0, -4)}" does not exist in namespace "${ns}"`, - source: 'InterSystems Language Server' - })); + v.forEach((range) => + diagnostics.push({ + severity: DiagnosticSeverity.Error, + range, + message: `${doc.endsWith("cls") ? "Class" : doc.endsWith("inc") ? "Include file" : "Routine"} "${doc.slice(0, -4)}" does not exist in namespace "${ns}"`, + source: "InterSystems Language Server", + }), + ); } }); } @@ -1294,6 +1448,6 @@ export async function onDiagnostics(params: DocumentDiagnosticParams): Promise { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) { return null; } - if (doc.languageId !== "objectscript-class") { return null; } + if (doc === undefined) { + return null; + } + if (doc.languageId !== "objectscript-class") { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } const result: DocumentLink[] = []; // Loop through the class and look for documentation comments const classregex = /(?:([^<>/#]+)<\/class>)|(?:##class\(([^<>()]+)\))/gi; - const memberregex = new RegExp("(?:([^<>/]+))|(?:([^<>/]+))|(?:([^<>/]+))", "gi"); + const memberregex = new RegExp( + "(?:([^<>/]+))|(?:([^<>/]+))|(?:([^<>/]+))", + "gi", + ); for (let line = 0; line < parsed.length; line++) { if ( parsed[line].length > 0 && @@ -33,8 +42,8 @@ export async function onDocumentLinks(params: DocumentLinkParams): Promise HTML tag linkRange = Range.create(line, matcharr.index + 10, line, matcharr.index + 10 + matcharr[2].length); commandArgs[1] = "property"; commandArgs[2] = matcharr[2]; - } - else { + } else { // This is a HTML tag linkRange = Range.create(line, matcharr.index + 7, line, matcharr.index + 7 + matcharr[3].length); commandArgs[1] = "query"; @@ -61,7 +68,7 @@ export async function onDocumentLinks(params: DocumentLinkParams): Promise { const doc = documents.get(link.data.uri); - if (doc === undefined) { return link; } + if (doc === undefined) { + return link; + } const parsed = await getParsedDocument(link.data.uri); - if (parsed === undefined) { return link; } + if (parsed === undefined) { + return link; + } const server: ServerSpec = await getServerSpec(link.data.uri); // Normalize the class name if there are imports diff --git a/server/src/providers/documentSymbol.ts b/server/src/providers/documentSymbol.ts index 445814c..0c5f921 100644 --- a/server/src/providers/documentSymbol.ts +++ b/server/src/providers/documentSymbol.ts @@ -1,12 +1,23 @@ -import { DocumentSymbol, DocumentSymbolParams, Position, SymbolKind, Range, SymbolTag } from 'vscode-languageserver/node'; -import { TextDocument } from 'vscode-languageserver-textdocument'; -import { findFullRange, getParsedDocument, isClassMember, labelIsProcedureBlock, prevToken } from '../utils/functions'; -import { documents, mppContinue } from '../utils/variables'; -import * as ld from '../utils/languageDefinitions'; -import { compressedline } from '../utils/types'; +import { + DocumentSymbol, + DocumentSymbolParams, + Position, + SymbolKind, + Range, + SymbolTag, +} from "vscode-languageserver/node"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import { findFullRange, getParsedDocument, isClassMember, labelIsProcedureBlock, prevToken } from "../utils/functions"; +import { documents, mppContinue } from "../utils/variables"; +import * as ld from "../utils/languageDefinitions"; +import { compressedline } from "../utils/types"; /** Loop through the class from this line until the next class member or the end of the class */ -function processMember(doc: TextDocument, parsed: compressedline[], line: number): { deprecated: boolean, lastNonEmpty: number } { +function processMember( + doc: TextDocument, + parsed: compressedline[], + line: number, +): { deprecated: boolean; lastNonEmpty: number } { let lastNonEmpty = line; let deprecated; for (let nl = line; nl < parsed.length; nl++) { @@ -16,21 +27,35 @@ function processMember(doc: TextDocument, parsed: compressedline[], line: number break; } if (parsed[nl][0].s === ld.cls_keyword_attrindex) { - const nextkeytext = doc.getText(Range.create(nl, parsed[nl][0].p, nl, parsed[nl][0].p + parsed[nl][0].c)).toLowerCase(); + const nextkeytext = doc + .getText(Range.create(nl, parsed[nl][0].p, nl, parsed[nl][0].p + parsed[nl][0].c)) + .toLowerCase(); if (isClassMember(nextkeytext)) { break; } } } if (deprecated == undefined) { - const depTkn = parsed[nl].findIndex((e) => - e.l == ld.cls_langindex && e.s == ld.cls_keyword_attrindex && doc.getText(Range.create(nl, e.p, nl, e.p + e.c)).toLowerCase() == "deprecated" + const depTkn = parsed[nl].findIndex( + (e) => + e.l == ld.cls_langindex && + e.s == ld.cls_keyword_attrindex && + doc.getText(Range.create(nl, e.p, nl, e.p + e.c)).toLowerCase() == "deprecated", ); if (depTkn != -1) { const previous = prevToken(parsed, nl, depTkn); - deprecated = previous && doc.getText( - Range.create(previous[0], parsed[previous[0]][previous[1]].p, previous[0], parsed[previous[0]][previous[1]].p + parsed[previous[0]][previous[1]].c) - ).toLowerCase() != "not"; + deprecated = + previous && + doc + .getText( + Range.create( + previous[0], + parsed[previous[0]][previous[1]].p, + previous[0], + parsed[previous[0]][previous[1]].p + parsed[previous[0]][previous[1]].c, + ), + ) + .toLowerCase() != "not"; } } lastNonEmpty = nl; @@ -41,9 +66,13 @@ function processMember(doc: TextDocument, parsed: compressedline[], line: number export async function onDocumentSymbol(params: DocumentSymbolParams) { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } const result: DocumentSymbol[] = []; if (doc.languageId === "objectscript-class") { @@ -53,15 +82,21 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { name: "", kind: SymbolKind.Class, range: Range.create(0, 0, 0, 0), - selectionRange: Range.create(0, 0, 0, 0) + selectionRange: Range.create(0, 0, 0, 0), }; const members: DocumentSymbol[] = []; for (let line = 0; line < parsed.length; line++) { if (!parsed[line]?.length) continue; - if (parsed[line][0].l === ld.cls_langindex && parsed[line][0].s === ld.cls_keyword_attrindex && parsed[line].length > 1) { + if ( + parsed[line][0].l === ld.cls_langindex && + parsed[line][0].s === ld.cls_keyword_attrindex && + parsed[line].length > 1 + ) { // This line starts with a UDL keyword - const keywordtext = doc.getText(Range.create(line, parsed[line][0].p, line, parsed[line][0].p + parsed[line][0].c)); + const keywordtext = doc.getText( + Range.create(line, parsed[line][0].p, line, parsed[line][0].p + parsed[line][0].c), + ); const keywordtextlower = keywordtext.toLowerCase(); if (keywordtextlower === "class") { // This is the class definition @@ -77,16 +112,23 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { // Update the DocumentSymbol object cls.selectionRange = findFullRange(line, parsed, 1, parsed[line][1].p, parsed[line][1].p + parsed[line][1].c); cls.name = doc.getText(cls.selectionRange); - cls.range = Range.create(line, 0, lastnonempty, parsed[lastnonempty][parsed[lastnonempty].length - 1].p + parsed[lastnonempty][parsed[lastnonempty].length - 1].c); + cls.range = Range.create( + line, + 0, + lastnonempty, + parsed[lastnonempty][parsed[lastnonempty].length - 1].p + + parsed[lastnonempty][parsed[lastnonempty].length - 1].c, + ); // Determine if this class is Deprecated const { deprecated } = processMember(doc, parsed, line); if (deprecated) cls.tags = [SymbolTag.Deprecated]; - } - else if (isClassMember(keywordtextlower)) { + } else if (isClassMember(keywordtextlower)) { // This is a class member definition - const memName = doc.getText(Range.create(line, parsed[line][1].p, line, parsed[line][1].p + parsed[line][1].c)); + const memName = doc.getText( + Range.create(line, parsed[line][1].p, line, parsed[line][1].p + parsed[line][1].c), + ); if (memName.trim() == "") continue; // Loop through the file from this line to find the next class member @@ -106,26 +148,44 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { let firstnondoc = line - 1; for (let nl = line - 1; nl >= 0; nl--) { firstnondoc = nl; - if (!parsed[nl]?.length || (parsed[nl][0].l === ld.cls_langindex && parsed[nl][0].s !== ld.cls_desc_attrindex)) break; + if ( + !parsed[nl]?.length || + (parsed[nl][0].l === ld.cls_langindex && parsed[nl][0].s !== ld.cls_desc_attrindex) + ) + break; } members.push({ name: memName, - kind: - ["method", "classmethod", "clientmethod"].includes(keywordtextlower) ? SymbolKind.Method : - keywordtextlower == "query" ? SymbolKind.Function : - keywordtextlower == "trigger" ? SymbolKind.Event : - keywordtextlower == "parameter" ? SymbolKind.Constant : - keywordtextlower == "index" ? SymbolKind.Array : - keywordtextlower == "foreignkey" ? SymbolKind.Key : - keywordtextlower == "xdata" ? SymbolKind.Struct : - keywordtextlower == "storage" ? SymbolKind.Object : - keywordtextlower == "projection" ? SymbolKind.Interface : - SymbolKind.Property, // Property and Relationship - range: Range.create(firstnondoc + 1, 0, lastNonEmpty, parsed[lastNonEmpty][parsed[lastNonEmpty].length - 1].p + parsed[lastNonEmpty][parsed[lastNonEmpty].length - 1].c), + kind: ["method", "classmethod", "clientmethod"].includes(keywordtextlower) + ? SymbolKind.Method + : keywordtextlower == "query" + ? SymbolKind.Function + : keywordtextlower == "trigger" + ? SymbolKind.Event + : keywordtextlower == "parameter" + ? SymbolKind.Constant + : keywordtextlower == "index" + ? SymbolKind.Array + : keywordtextlower == "foreignkey" + ? SymbolKind.Key + : keywordtextlower == "xdata" + ? SymbolKind.Struct + : keywordtextlower == "storage" + ? SymbolKind.Object + : keywordtextlower == "projection" + ? SymbolKind.Interface + : SymbolKind.Property, // Property and Relationship + range: Range.create( + firstnondoc + 1, + 0, + lastNonEmpty, + parsed[lastNonEmpty][parsed[lastNonEmpty].length - 1].p + + parsed[lastNonEmpty][parsed[lastNonEmpty].length - 1].c, + ), selectionRange: Range.create(line, parsed[line][1].p, line, parsed[line][1].p + parsed[line][1].c), tags: deprecated ? [SymbolTag.Deprecated] : undefined, - detail: keywordtext + detail: keywordtext, }); } } @@ -134,8 +194,7 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { cls.children = members; result.push(cls); } - } - else if (doc.languageId === "objectscript-macros") { + } else if (doc.languageId === "objectscript-macros") { // Loop through the file and look for macro definitions let prevdoccomments = 0; @@ -150,62 +209,100 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { if (parsed[line][0].l === ld.cos_langindex && parsed[line][0].s === ld.cos_dcom_attrindex) { // This line contains a documentation (///) comment prevdoccomments++; - } - else { + } else { prevdoccomments = 0; } } continue; } - const secondtokentext = doc.getText(Range.create(line, parsed[line][1].p, line, parsed[line][1].p + parsed[line][1].c)).toLowerCase(); - if (parsed[line][1].l === ld.cos_langindex && parsed[line][1].s === ld.cos_ppc_attrindex && (secondtokentext === "define" || secondtokentext === "def1arg")) { + const secondtokentext = doc + .getText(Range.create(line, parsed[line][1].p, line, parsed[line][1].p + parsed[line][1].c)) + .toLowerCase(); + if ( + parsed[line][1].l === ld.cos_langindex && + parsed[line][1].s === ld.cos_ppc_attrindex && + (secondtokentext === "define" || secondtokentext === "def1arg") + ) { // This line contains a macro definition if ( - parsed[line][parsed[line].length - 1].l === ld.cos_langindex && parsed[line][parsed[line].length - 1].s === ld.cos_ppf_attrindex && - mppContinue.test(doc.getText(Range.create( - line, parsed[line][parsed[line].length - 1].p, - line, parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c - ))) + parsed[line][parsed[line].length - 1].l === ld.cos_langindex && + parsed[line][parsed[line].length - 1].s === ld.cos_ppf_attrindex && + mppContinue.test( + doc.getText( + Range.create( + line, + parsed[line][parsed[line].length - 1].p, + line, + parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c, + ), + ), + ) ) { // This is the start of a multi-line macro definition multilinestart = line; - } - else { + } else { // This is a single line macro definition - const fullrange: Range = Range.create(line - prevdoccomments, 0, line, parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c); + const fullrange: Range = Range.create( + line - prevdoccomments, + 0, + line, + parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c, + ); prevdoccomments = 0; result.push({ name: doc.getText(Range.create(line, parsed[line][2].p, line, parsed[line][2].p + parsed[line][2].c)), kind: SymbolKind.Constant, range: fullrange, - selectionRange: Range.create(line, parsed[line][2].p, line, parsed[line][2].p + parsed[line][2].c) + selectionRange: Range.create(line, parsed[line][2].p, line, parsed[line][2].p + parsed[line][2].c), }); } - } - else if ( + } else if ( multilinestart != -1 && !( - parsed[line][parsed[line].length - 1].l == ld.cos_langindex && parsed[line][parsed[line].length - 1].s == ld.cos_ppf_attrindex && - mppContinue.test(doc.getText(Range.create( - line, parsed[line][parsed[line].length - 1].p, - line, parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c - ))) + parsed[line][parsed[line].length - 1].l == ld.cos_langindex && + parsed[line][parsed[line].length - 1].s == ld.cos_ppf_attrindex && + mppContinue.test( + doc.getText( + Range.create( + line, + parsed[line][parsed[line].length - 1].p, + line, + parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c, + ), + ), + ) ) ) { // This is the end of a multi-line macro definition prevdoccomments = 0; result.push({ - name: doc.getText(Range.create(multilinestart, parsed[multilinestart][2].p, multilinestart, parsed[multilinestart][2].p + parsed[multilinestart][2].c)), + name: doc.getText( + Range.create( + multilinestart, + parsed[multilinestart][2].p, + multilinestart, + parsed[multilinestart][2].p + parsed[multilinestart][2].c, + ), + ), kind: SymbolKind.Constant, - range: Range.create(multilinestart - prevdoccomments, 0, line, parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c), - selectionRange: Range.create(multilinestart, parsed[multilinestart][2].p, multilinestart, parsed[multilinestart][2].p + parsed[multilinestart][2].c) + range: Range.create( + multilinestart - prevdoccomments, + 0, + line, + parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c, + ), + selectionRange: Range.create( + multilinestart, + parsed[multilinestart][2].p, + multilinestart, + parsed[multilinestart][2].p + parsed[multilinestart][2].c, + ), }); multilinestart = -1; } } - } - else if (doc.languageId === "objectscript" || doc.languageId === "objectscript-int") { + } else if (doc.languageId === "objectscript" || doc.languageId === "objectscript-int") { // Loop through the file and look for labels for (let line = 0; line < parsed.length; line++) { @@ -219,12 +316,11 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { const labelrange = findFullRange(line, parsed, 0, parsed[line][0].p, parsed[line][0].p + parsed[line][0].c); const label = doc.getText(labelrange); - const inProcedureBlock = ( + const inProcedureBlock = result.length > 0 && Array.isArray(result[result.length - 1].children) && labelrange.start.line >= result[result.length - 1].range.start.line && - labelrange.start.line <= result[result.length - 1].range.end.line - ); + labelrange.start.line <= result[result.length - 1].range.end.line; let firstbrace: [number, number] | undefined = undefined; if (!inProcedureBlock) { @@ -237,8 +333,12 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { // Loop through the file from the first brace until we hit the last closing brace let openbrace = 0; let brk = false; - for (let nl = (parsed[firstbrace[0]].length - 1 == firstbrace[1] ? firstbrace[0] + 1 : firstbrace[0]); nl < parsed.length; nl++) { - for (let nt = (nl == firstbrace[0] ? firstbrace[1] + 1 : 0); nt < parsed[nl].length; nt++) { + for ( + let nl = parsed[firstbrace[0]].length - 1 == firstbrace[1] ? firstbrace[0] + 1 : firstbrace[0]; + nl < parsed.length; + nl++ + ) { + for (let nt = nl == firstbrace[0] ? firstbrace[1] + 1 : 0; nt < parsed[nl].length; nt++) { if (parsed[nl][nt].l == ld.cos_langindex && parsed[nl][nt].s == ld.cos_brace_attrindex) { const brace = doc.getText(Range.create(nl, parsed[nl][nt].p, nl, parsed[nl][nt].p + parsed[nl][nt].c)); if (brace == "{") { @@ -248,8 +348,7 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { endLine = nl; brk = true; break; - } - else { + } else { openbrace--; } } @@ -259,8 +358,7 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { break; } } - } - else { + } else { // Loop through the file from this line to find the next label for (let nl = line + 1; nl < parsed.length; nl++) { if (!parsed[nl]?.length) { @@ -278,23 +376,33 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { result[result.length - 1].children?.push({ name: label, kind: SymbolKind.Method, - range: Range.create(labelrange.start, Position.create(endLine, parsed[endLine][parsed[endLine].length - 1].p + parsed[endLine][parsed[endLine].length - 1].c)), - selectionRange: labelrange + range: Range.create( + labelrange.start, + Position.create( + endLine, + parsed[endLine][parsed[endLine].length - 1].p + parsed[endLine][parsed[endLine].length - 1].c, + ), + ), + selectionRange: labelrange, }); - } - else { + } else { result.push({ name: label, kind: SymbolKind.Method, - range: Range.create(labelrange.start, Position.create(endLine, parsed[endLine][parsed[endLine].length - 1].p + parsed[endLine][parsed[endLine].length - 1].c)), + range: Range.create( + labelrange.start, + Position.create( + endLine, + parsed[endLine][parsed[endLine].length - 1].p + parsed[endLine][parsed[endLine].length - 1].c, + ), + ), selectionRange: labelrange, - children: firstbrace != undefined ? [] : undefined + children: firstbrace != undefined ? [] : undefined, }); } } } - } - else if (doc.languageId === "objectscript-csp") { + } else if (doc.languageId === "objectscript-csp") { // Loop through the file and look for HTML script tags let symbolopen: boolean = false; @@ -303,16 +411,18 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { for (let tkn = 0; tkn < parsed[line].length; tkn++) { if ( tkn < parsed[line].length - 1 && - parsed[line][tkn].l == ld.html_langindex && parsed[line][tkn].s == ld.html_delim_attrindex && - parsed[line][tkn + 1].l == ld.html_langindex && parsed[line][tkn + 1].s == ld.html_tag_attrindex && - doc.getText(Range.create( - line, parsed[line][tkn].p, - line, parsed[line][tkn].p + parsed[line][tkn].c - )) === "<" && - doc.getText(Range.create( - line, parsed[line][tkn + 1].p, - line, parsed[line][tkn + 1].p + parsed[line][tkn + 1].c - )).toLowerCase() === "script" && !symbolopen + parsed[line][tkn].l == ld.html_langindex && + parsed[line][tkn].s == ld.html_delim_attrindex && + parsed[line][tkn + 1].l == ld.html_langindex && + parsed[line][tkn + 1].s == ld.html_tag_attrindex && + doc.getText(Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c)) === + "<" && + doc + .getText( + Range.create(line, parsed[line][tkn + 1].p, line, parsed[line][tkn + 1].p + parsed[line][tkn + 1].c), + ) + .toLowerCase() === "script" && + !symbolopen ) { // This line contains an HTML open script tag so create a new symbol if we can @@ -327,14 +437,15 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { if (parsed[line][stkn].l == ld.html_langindex && parsed[line][stkn].s == ld.html_name_attrindex) { // This is an attribute - const attrtext: string = doc.getText(Range.create( - line, parsed[line][stkn].p, - line, parsed[line][stkn].p + parsed[line][stkn].c - )).toLowerCase(); + const attrtext: string = doc + .getText(Range.create(line, parsed[line][stkn].p, line, parsed[line][stkn].p + parsed[line][stkn].c)) + .toLowerCase(); if (parsed[line].length > stkn + 2) { const valrange: Range = Range.create( - line, parsed[line][stkn + 2].p, - line, parsed[line][stkn + 2].p + parsed[line][stkn + 2].c + line, + parsed[line][stkn + 2].p, + line, + parsed[line][stkn + 2].p + parsed[line][stkn + 2].c, ); let valtext: string = doc.getText(valrange); if (valtext.startsWith('"') && valtext.endsWith('"')) { @@ -344,12 +455,10 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { if (attrtext === "language") { lang = valtext; - } - else if (attrtext === "method") { + } else if (attrtext === "method") { method = valtext; methodrange = valrange; - } - else if (attrtext === "name") { + } else if (attrtext === "name") { name = valtext; namerange = valrange; } @@ -367,34 +476,41 @@ export async function onDocumentSymbol(params: DocumentSymbolParams) { let detail: string = "ObjectScript"; if (lang.toLowerCase() === "basic") { detail = "Basic"; - } - else if (lang.toLowerCase() === "sql" || lang.toLowerCase() === "esql") { + } else if (lang.toLowerCase() === "sql" || lang.toLowerCase() === "esql") { detail = "SQL"; } result.push({ - name: (method !== "" ? method : name), + name: method !== "" ? method : name, kind: SymbolKind.Method, detail: detail, - selectionRange: (method !== "" ? methodrange : namerange), + selectionRange: method !== "" ? methodrange : namerange, // We will update range.end later - range: Range.create(startpos, startpos) + range: Range.create(startpos, startpos), }); symbolopen = true; } } if ( tkn < parsed[line].length - 3 && - parsed[line][tkn].l == ld.html_langindex && parsed[line][tkn].s == ld.html_delim_attrindex && - parsed[line][tkn + 1].l == ld.html_langindex && parsed[line][tkn + 1].s == ld.html_delim_attrindex && - parsed[line][tkn + 2].l == ld.html_langindex && parsed[line][tkn + 2].s == ld.html_tag_attrindex && - doc.getText(Range.create( - line, parsed[line][tkn + 2].p, - line, parsed[line][tkn + 2].p + parsed[line][tkn + 2].c - )).toLowerCase() === "script" && symbolopen + parsed[line][tkn].l == ld.html_langindex && + parsed[line][tkn].s == ld.html_delim_attrindex && + parsed[line][tkn + 1].l == ld.html_langindex && + parsed[line][tkn + 1].s == ld.html_delim_attrindex && + parsed[line][tkn + 2].l == ld.html_langindex && + parsed[line][tkn + 2].s == ld.html_tag_attrindex && + doc + .getText( + Range.create(line, parsed[line][tkn + 2].p, line, parsed[line][tkn + 2].p + parsed[line][tkn + 2].c), + ) + .toLowerCase() === "script" && + symbolopen ) { // This line starts with a HTML close script tag so close the open symbol - result[result.length - 1].range.end = Position.create(line, parsed[line][tkn + 3].p + parsed[line][tkn + 3].c); + result[result.length - 1].range.end = Position.create( + line, + parsed[line][tkn + 3].p + parsed[line][tkn + 3].c, + ); symbolopen = false; } } diff --git a/server/src/providers/evaluatableExpression.ts b/server/src/providers/evaluatableExpression.ts index a5dbca1..3d95da4 100644 --- a/server/src/providers/evaluatableExpression.ts +++ b/server/src/providers/evaluatableExpression.ts @@ -1,9 +1,9 @@ -import { Position, Range } from 'vscode-languageserver/node'; -import { TextDocument } from 'vscode-languageserver-textdocument'; -import { compressedline } from '../utils/types'; -import * as ld from '../utils/languageDefinitions'; -import { documents } from '../utils/variables'; -import { getParsedDocument } from '../utils/functions'; +import { Position, Range } from "vscode-languageserver/node"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import { compressedline } from "../utils/types"; +import * as ld from "../utils/languageDefinitions"; +import { documents } from "../utils/variables"; +import { getParsedDocument } from "../utils/functions"; /** * An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime. @@ -13,38 +13,41 @@ import { getParsedDocument } from '../utils/functions'; * In this case the range is still used to highlight the range in the document. */ type EvaluatableExpression = { - /* * The range is used to extract the evaluatable expression from the underlying document and to highlight it. */ - range: Range, + range: Range; /* * If specified the expression overrides the extracted expression. */ - expression?: string - -} + expression?: string; +}; /** * The parameter literal for the `intersystems/debugger/evaluatableExpression` request. */ type EvaluatableExpressionParams = { - uri: string, - position: Position + uri: string; + position: Position; }; /** * Return the [line,offset] of the closing parenthesis token for this argument or subscript list. * Arguments `line` and `tkn` should correspond to the token following the open parenthesis. - * + * * @param doc The TextDocument. * @param parsed The tokenized representation of `doc`. * @param line The line that the token is on. * @param tkn The offset within `line` that the token is located. * @returns The [line,offset] of the closing parenthesis token, or null if it wasn't found. */ -function findClosingParenToken(doc: TextDocument, parsed: compressedline[], line: number, tkn: number): [number, number] | null { +function findClosingParenToken( + doc: TextDocument, + parsed: compressedline[], + line: number, + tkn: number, +): [number, number] | null { let openparen = 0; let closingparen = 0; let resultline = -1; @@ -58,10 +61,9 @@ function findClosingParenToken(doc: TextDocument, parsed: compressedline[], line } if (parsed[i][j].l === ld.cos_langindex && parsed[i][j].s === ld.cos_delim_attrindex) { // This is a COS delimiter - const tokentext = doc.getText(Range.create( - Position.create(i, parsed[i][j].p), - Position.create(i, parsed[i][j].p + parsed[i][j].c) - )); + const tokentext = doc.getText( + Range.create(Position.create(i, parsed[i][j].p), Position.create(i, parsed[i][j].p + parsed[i][j].c)), + ); if (tokentext === "(") { openparen++; } @@ -84,8 +86,7 @@ function findClosingParenToken(doc: TextDocument, parsed: compressedline[], line if (resultline !== -1) { return [resultline, resulttkn]; - } - else { + } else { return null; } } @@ -93,7 +94,7 @@ function findClosingParenToken(doc: TextDocument, parsed: compressedline[], line /** * Check if the token trailing `parsed[line,tkn]` is an open parenthesis and if so, * determine the line and offet of the first token following that open parenthesis. - * + * * @param doc The TextDocument. * @param parsed The tokenized representation of `doc`. * @param line The line that the token is on. @@ -102,26 +103,31 @@ function findClosingParenToken(doc: TextDocument, parsed: compressedline[], line * following the open parenthesis, or [`false`,-1,-1] if the trailing token is * not an open parenthesis. */ -function isTrailingTokenOpenParen(doc: TextDocument, parsed: compressedline[], line: number, tkn: number): [boolean, number, number] { - +function isTrailingTokenOpenParen( + doc: TextDocument, + parsed: compressedline[], + line: number, + tkn: number, +): [boolean, number, number] { let result: [boolean, number, number] = [false, -1, -1]; if ( tkn !== parsed[line].length - 1 && parsed[line][tkn + 1].l === ld.cos_langindex && parsed[line][tkn + 1].s === ld.cos_delim_attrindex && - doc.getText(Range.create( - Position.create(line, parsed[line][tkn + 1].p), - Position.create(line, parsed[line][tkn + 1].p + parsed[line][tkn + 1].c) - )) === "(" + doc.getText( + Range.create( + Position.create(line, parsed[line][tkn + 1].p), + Position.create(line, parsed[line][tkn + 1].p + parsed[line][tkn + 1].c), + ), + ) === "(" ) { // The trailing token is an open parenthesis, so capture the subscript list if (tkn + 1 === parsed[line].length - 1) { result = [true, line + 1, 0]; - } - else { - result = [true, line, tkn + 2] + } else { + result = [true, line, tkn + 2]; } } @@ -130,7 +136,7 @@ function isTrailingTokenOpenParen(doc: TextDocument, parsed: compressedline[], l /** * Check if the token trailing `parsed[line,tkn]` is an object dot operator. - * + * * @param parsed The tokenized representation of the TextDocument. * @param line The line that the token is on. * @param tkn The offset within `line` that the token is located. @@ -151,7 +157,7 @@ function isTrailingTokenDot(parsed: compressedline[], line: number, tkn: number) /** * Return the position of the ##class. * Arguments `line` and `tkn` should correspond to the token preceding the closing parenthesis. - * + * * @param parsed The tokenized representation of the TextDocument. * @param line The line that the token is on. * @param tkn The offset within `line` that the token is located. @@ -182,8 +188,7 @@ function findClassSyntax(parsed: compressedline[], line: number, tkn: number): P if (resultline !== -1) { return Position.create(resultline, resultchar); - } - else { + } else { return null; } } @@ -191,7 +196,7 @@ function findClassSyntax(parsed: compressedline[], line: number, tkn: number): P /** * Return the position of the caret for a global. * Arguments `line` and `tkn` should correspond to the token preceding the extended reference closing delimiter. - * + * * @param doc The TextDocument. * @param parsed The tokenized representation of `doc`. * @param line The line that the token is on. @@ -210,10 +215,11 @@ function findCaret(doc: TextDocument, parsed: compressedline[], line: number, tk } if (parsed[i][j].l === ld.cos_langindex && parsed[i][j].s === ld.cos_delim_attrindex) { // This is a COS delimiter - const tokenfirstchar = doc.getText(Range.create( - Position.create(i, parsed[i][j].p), - Position.create(i, parsed[i][j].p + parsed[i][j].c) - )).charAt(0); + const tokenfirstchar = doc + .getText( + Range.create(Position.create(i, parsed[i][j].p), Position.create(i, parsed[i][j].p + parsed[i][j].c)), + ) + .charAt(0); if (tokenfirstchar === "^") { // We found the caret for the global @@ -231,22 +237,26 @@ function findCaret(doc: TextDocument, parsed: compressedline[], line: number, tk if (resultline !== -1) { return Position.create(resultline, resultchar); - } - else { + } else { return null; } } /** * Return the start position of the expression that class parameter token `parsed[line,tkn]` is a part of. - * + * * @param doc The TextDocument. * @param parsed The tokenized representation of `doc`. * @param line The line that the token is on. * @param tkn The offset within `line` that the token is located. * @returns The start position of the expression, or null if it can't be determined. */ -function findClassParameterStart(doc: TextDocument, parsed: compressedline[], line: number, tkn: number): Position | null { +function findClassParameterStart( + doc: TextDocument, + parsed: compressedline[], + line: number, + tkn: number, +): Position | null { let result: Position | null; // Check that the preceding token is a dot @@ -254,40 +264,42 @@ function findClassParameterStart(doc: TextDocument, parsed: compressedline[], li if (parsed[line][tkn - 1].c === 2) { // This is a double dot, which means we've reached the beginning of the expression result = Position.create(line, parsed[line][tkn - 1].p); - } - else { + } else { // This is a single dot, so check the token preceding it if ( parsed[line][tkn - 2].l === ld.cos_langindex && parsed[line][tkn - 2].s === ld.cos_delim_attrindex && - doc.getText(Range.create( - Position.create(line, parsed[line][tkn - 2].p), - Position.create(line, parsed[line][tkn - 2].p + parsed[line][tkn - 2].c) - )) === ")" + doc.getText( + Range.create( + Position.create(line, parsed[line][tkn - 2].p), + Position.create(line, parsed[line][tkn - 2].p + parsed[line][tkn - 2].c), + ), + ) === ")" ) { // The preceding token is a closing parenthesis // This is ##class() syntax, so find the start of it result = findClassSyntax(parsed, line, tkn - 3); - } - else if ( + } else if ( parsed[line][tkn - 2].l === ld.cos_langindex && parsed[line][tkn - 2].s === ld.cos_sysv_attrindex && - doc.getText(Range.create( - Position.create(line, parsed[line][tkn - 2].p), - Position.create(line, parsed[line][tkn - 2].p + parsed[line][tkn - 2].c) - )).toLowerCase() === "$this" + doc + .getText( + Range.create( + Position.create(line, parsed[line][tkn - 2].p), + Position.create(line, parsed[line][tkn - 2].p + parsed[line][tkn - 2].c), + ), + ) + .toLowerCase() === "$this" ) { // The preceding token is $THIS result = Position.create(line, parsed[line][tkn - 2].p); - } - else { + } else { // The preceding token is something else result = null; } } - } - else { + } else { // A dot must precede a class parameter result = null; } @@ -295,8 +307,12 @@ function findClassParameterStart(doc: TextDocument, parsed: compressedline[], li return result; } -function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], line: number, tkn: number): EvaluatableExpression | null { - +function findEvaluatableExpression( + doc: TextDocument, + parsed: compressedline[], + line: number, + tkn: number, +): EvaluatableExpression | null { let result: EvaluatableExpression | null = null; try { if (parsed[line][tkn].l !== ld.cos_langindex) { @@ -319,13 +335,15 @@ function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], tkn > 0 && parsed[line][tkn - 1].l === ld.cos_langindex && parsed[line][tkn - 1].s === ld.cos_delim_attrindex && - doc.getText(Range.create( - Position.create(line, parsed[line][tkn - 1].p), - Position.create(line, parsed[line][tkn - 1].p + parsed[line][tkn - 1].c) - )) === ")" + doc.getText( + Range.create( + Position.create(line, parsed[line][tkn - 1].p), + Position.create(line, parsed[line][tkn - 1].p + parsed[line][tkn - 1].c), + ), + ) === ")" ) { // A variable has ##class() casting syntax in front of it, so it's part of a method casting expression - // Return null because we don't support evaluating methods + // Return null because we don't support evaluating methods resultstart = null; } @@ -339,9 +357,11 @@ function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], if (closingparen === null) { // Couldn't find the closing parenthesis resultend = null; - } - else { - resultend = Position.create(closingparen[0], parsed[closingparen[0]][closingparen[1]].p + parsed[closingparen[0]][closingparen[1]].c); + } else { + resultend = Position.create( + closingparen[0], + parsed[closingparen[0]][closingparen[1]].p + parsed[closingparen[0]][closingparen[1]].c, + ); } } // Check if the trailing token is a dot @@ -351,8 +371,7 @@ function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], resultend = null; } } - } - else if (parsed[line][tkn].s === ld.cos_global_attrindex) { + } else if (parsed[line][tkn].s === ld.cos_global_attrindex) { // This is a global // Check if the preceding token is a ] or | @@ -363,17 +382,18 @@ function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], parsed[line][tkn - 1].s === ld.cos_delim_attrindex ) { // The preceding token is a COS delimiter - const prectokentext = doc.getText(Range.create( - Position.create(line, parsed[line][tkn - 1].p), - Position.create(line, parsed[line][tkn - 1].p + parsed[line][tkn - 1].c) - )); + const prectokentext = doc.getText( + Range.create( + Position.create(line, parsed[line][tkn - 1].p), + Position.create(line, parsed[line][tkn - 1].p + parsed[line][tkn - 1].c), + ), + ); if (prectokentext === "]" || prectokentext === "|") { // The preceding token is an extended reference closing delimiter if (tkn - 1 === 0) { resultstart = findCaret(doc, parsed, line - 1, parsed[line - 1].length - 1); - } - else { + } else { resultstart = findCaret(doc, parsed, line, tkn - 2); } } @@ -389,14 +409,15 @@ function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], if (closingparen === null) { // Couldn't find the closing parenthesis resultend = null; - } - else { - resultend = Position.create(closingparen[0], parsed[closingparen[0]][closingparen[1]].p + parsed[closingparen[0]][closingparen[1]].c); + } else { + resultend = Position.create( + closingparen[0], + parsed[closingparen[0]][closingparen[1]].p + parsed[closingparen[0]][closingparen[1]].c, + ); } } } - } - else if (parsed[line][tkn].s === ld.cos_sysv_attrindex || parsed[line][tkn].s === ld.cos_uknzvar_attrindex) { + } else if (parsed[line][tkn].s === ld.cos_sysv_attrindex || parsed[line][tkn].s === ld.cos_uknzvar_attrindex) { // This is a system variable resultstart = Position.create(line, parsed[line][tkn].p); @@ -419,8 +440,7 @@ function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], // Return null because we don't support evaluating methods or properties resultend = null; } - } - else if (parsed[line][tkn].s == ld.cos_prop_attrindex) { + } else if (parsed[line][tkn].s == ld.cos_prop_attrindex) { // This is a class parameter // Class parameters are parsed as two tokens (#, then the name) so check which one this is @@ -428,8 +448,7 @@ function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], // This is the second token resultstart = findClassParameterStart(doc, parsed, line, tkn - 1); resultend = Position.create(line, parsed[line][tkn].p + parsed[line][tkn].c); - } - else { + } else { // This is the first token resultstart = findClassParameterStart(doc, parsed, line, tkn); resultend = Position.create(line, parsed[line][tkn + 1].p + parsed[line][tkn + 1].c); @@ -443,13 +462,12 @@ function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], // The expression is on multiple lines, so strip out the newline characters result = { range: exprrange, - expression: doc.getText(exprrange).replace(/\r?\n|\r/g, "") + expression: doc.getText(exprrange).replace(/\r?\n|\r/g, ""), }; - } - else { + } else { // The expression is on one line result = { - range: exprrange + range: exprrange, }; } } @@ -463,11 +481,17 @@ function findEvaluatableExpression(doc: TextDocument, parsed: compressedline[], return result; } -export async function evaluatableExpression(params: EvaluatableExpressionParams): Promise { +export async function evaluatableExpression( + params: EvaluatableExpressionParams, +): Promise { const doc = documents.get(params.uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } let tkn: number = -1; for (let i = 0; i < parsed[params.position.line].length; i++) { @@ -482,8 +506,7 @@ export async function evaluatableExpression(params: EvaluatableExpressionParams) if (tkn !== -1) { return findEvaluatableExpression(doc, parsed, params.position.line, tkn); - } - else { + } else { return null; } } diff --git a/server/src/providers/foldingRange.ts b/server/src/providers/foldingRange.ts index 7c02b46..1e3395d 100644 --- a/server/src/providers/foldingRange.ts +++ b/server/src/providers/foldingRange.ts @@ -1,16 +1,20 @@ -import { FoldingRange, FoldingRangeKind, FoldingRangeParams, Range } from 'vscode-languageserver/node'; -import { documents, mppContinue } from '../utils/variables'; -import * as ld from '../utils/languageDefinitions'; -import { getParsedDocument } from '../utils/functions'; +import { FoldingRange, FoldingRangeKind, FoldingRangeParams, Range } from "vscode-languageserver/node"; +import { documents, mppContinue } from "../utils/variables"; +import * as ld from "../utils/languageDefinitions"; +import { getParsedDocument } from "../utils/functions"; const cosRegionRegex = new RegExp("^(?://|#;) *#(?:end){0,1}region(?: +.*){0,1}$"); const clsRegionRegex = new RegExp("^// *#(?:end){0,1}region(?: +.*){0,1}$"); export async function onFoldingRanges(params: FoldingRangeParams) { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } /** * Returns `true` if the given line is the opening of a class member range. * This is needed for the rare case where the open curly brace is not the @@ -23,7 +27,9 @@ export async function onFoldingRanges(params: FoldingRangeParams) { const lineText = doc.getText(Range.create(line, 0, line + 1, 0)); if (lineText.trimEnd().endsWith("{")) { const openCurlyIdx = lineText.lastIndexOf("{"); - return (parsed[line] ?? []).some((t) => openCurlyIdx == t.p && t.l == ld.cls_langindex && t.s == ld.cls_delim_attrindex); + return (parsed[line] ?? []).some( + (t) => openCurlyIdx == t.p && t.l == ld.cls_langindex && t.s == ld.cls_delim_attrindex, + ); } return false; }; @@ -54,28 +60,38 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } continue; } - const firsttokentext = doc.getText(Range.create(line, parsed[line][0].p, line, parsed[line][0].p + parsed[line][0].c)); - const lineFromFirstToken = doc.getText(Range.create(line, parsed[line][0].p, line, parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c)); + const firsttokentext = doc.getText( + Range.create(line, parsed[line][0].p, line, parsed[line][0].p + parsed[line][0].c), + ); + const lineFromFirstToken = doc.getText( + Range.create( + line, + parsed[line][0].p, + line, + parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c, + ), + ); if ( (parsed[line][0].l === ld.cls_langindex && parsed[line][0].s === ld.cls_desc_attrindex) || (parsed[line][0].l === ld.cos_langindex && parsed[line][0].s === ld.cos_dcom_attrindex) ) { // This line is a UDL description or COS documentation comment - if (openranges.length === 0 || (openranges.length > 0 && openranges[openranges.length - 1].kind !== FoldingRangeKind.Comment)) { + if ( + openranges.length === 0 || + (openranges.length > 0 && openranges[openranges.length - 1].kind !== FoldingRangeKind.Comment) + ) { // Start a new range openranges.push({ startLine: line, endLine: line, - kind: FoldingRangeKind.Comment + kind: FoldingRangeKind.Comment, }); - } - else { + } else { // Extend the existing range openranges[openranges.length - 1].endLine = line; } - } - else { + } else { // This line isn't a UDL description or COS documentation comment if (openranges.length > 0 && openranges[openranges.length - 1].kind === FoldingRangeKind.Comment) { @@ -87,13 +103,22 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } if (inMultiLineMacro) { // Check if the last token is a ##Continue - if (!( - parsed[line][parsed[line].length - 1].l == ld.cos_langindex && parsed[line][parsed[line].length - 1].s == ld.cos_ppf_attrindex && - mppContinue.test(doc.getText(Range.create( - line, parsed[line][parsed[line].length - 1].p, - line, parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c - ))) - )) { + if ( + !( + parsed[line][parsed[line].length - 1].l == ld.cos_langindex && + parsed[line][parsed[line].length - 1].s == ld.cos_ppf_attrindex && + mppContinue.test( + doc.getText( + Range.create( + line, + parsed[line][parsed[line].length - 1].p, + line, + parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c, + ), + ), + ) + ) + ) { // This is the end of a multi-line macro let prevrange = openranges.length - 1; for (let rge = openranges.length - 1; rge >= 0; rge--) { @@ -111,21 +136,32 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } } if ( - ( - parsed[line][parsed[line].length - 1].l == ld.cls_langindex && parsed[line][parsed[line].length - 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - line, parsed[line][parsed[line].length - 1].p, - line, parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c - )) == "{" - ) || isClassMemberOpen(line) + (parsed[line][parsed[line].length - 1].l == ld.cls_langindex && + parsed[line][parsed[line].length - 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + line, + parsed[line][parsed[line].length - 1].p, + line, + parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c, + ), + ) == "{") || + isClassMemberOpen(line) ) { // This line ends with a UDL open curly if ( - (parsed[line].length === 1 && parsed[line - 1][0].l == ld.cls_langindex && parsed[line - 1][0].s == ld.cls_keyword_attrindex && - doc.getText(Range.create(line - 1, parsed[line - 1][0].p, line - 1, parsed[line - 1][0].p + parsed[line - 1][0].c)).toLowerCase() === "class") - || - (parsed[line].length > 1 && parsed[line][0].l == ld.cls_langindex && parsed[line][0].s == ld.cls_keyword_attrindex && + (parsed[line].length === 1 && + parsed[line - 1][0].l == ld.cls_langindex && + parsed[line - 1][0].s == ld.cls_keyword_attrindex && + doc + .getText( + Range.create(line - 1, parsed[line - 1][0].p, line - 1, parsed[line - 1][0].p + parsed[line - 1][0].c), + ) + .toLowerCase() === "class") || + (parsed[line].length > 1 && + parsed[line][0].l == ld.cls_langindex && + parsed[line][0].s == ld.cls_keyword_attrindex && firsttokentext.toLowerCase() === "class") ) { // This is the open curly for a class, so don't create a folding range for it @@ -136,7 +172,7 @@ export async function onFoldingRanges(params: FoldingRangeParams) { openranges.push({ startLine: line, endLine: line, - kind: "isc-member" + kind: "isc-member", }); // Scan forward in the file and look for the next line that starts with a UDL keyword or close brace @@ -148,22 +184,15 @@ export async function onFoldingRanges(params: FoldingRangeParams) { parsed[nl][0].l === ld.cls_langindex && (parsed[nl][0].s === ld.cls_keyword_attrindex || (parsed[nl][0].s === ld.cls_delim_attrindex && - doc.getText(Range.create( - nl, parsed[nl][0].p, - nl, parsed[nl][0].p + parsed[nl][0].c - )) === "}")) + doc.getText(Range.create(nl, parsed[nl][0].p, nl, parsed[nl][0].p + parsed[nl][0].c)) === "}")) ) { // Close the member range if ( parsed[nl][0].s === ld.cls_delim_attrindex && - doc.getText(Range.create( - nl, parsed[nl][0].p, - nl, parsed[nl][0].p + parsed[nl][0].c - )) === "}" + doc.getText(Range.create(nl, parsed[nl][0].p, nl, parsed[nl][0].p + parsed[nl][0].c)) === "}" ) { openranges[openranges.length - 1].endLine = nl - 1; - } - else { + } else { openranges[openranges.length - 1].endLine = nl; } if (openranges[openranges.length - 1].startLine < openranges[openranges.length - 1].endLine) { @@ -175,8 +204,11 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } } if ( - parsed[line][0].l == ld.cos_langindex && parsed[line][0].s == ld.cos_label_attrindex && parsed[line][0].p == 0 && - firsttokentext != routinename && (doc.languageId == "objectscript" || doc.languageId == "objectscript-int") + parsed[line][0].l == ld.cos_langindex && + parsed[line][0].s == ld.cos_label_attrindex && + parsed[line][0].p == 0 && + firsttokentext != routinename && + (doc.languageId == "objectscript" || doc.languageId == "objectscript-int") ) { // This line starts with a routine label @@ -184,7 +216,9 @@ export async function onFoldingRanges(params: FoldingRangeParams) { let foundopencurly = false; for (let tkn = 1; tkn < parsed[line].length; tkn++) { if (parsed[line][tkn].l === ld.cos_langindex && parsed[line][tkn].s === ld.cos_brace_attrindex) { - const bracetext = doc.getText(Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c)); + const bracetext = doc.getText( + Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c), + ); if (bracetext === "{") { foundopencurly = true; break; @@ -199,7 +233,7 @@ export async function onFoldingRanges(params: FoldingRangeParams) { openranges.push({ startLine: line, endLine: line, - kind: "isc-member" + kind: "isc-member", }); // Loop through the file from this line to find the next label @@ -209,11 +243,17 @@ export async function onFoldingRanges(params: FoldingRangeParams) { precedingcomments = 0; continue; } - if (parsed[nl][0].l === ld.cos_langindex && (parsed[nl][0].s === ld.cos_comment_attrindex || parsed[nl][0].s === ld.cos_dcom_attrindex)) { + if ( + parsed[nl][0].l === ld.cos_langindex && + (parsed[nl][0].s === ld.cos_comment_attrindex || parsed[nl][0].s === ld.cos_dcom_attrindex) + ) { // Don't fold comments that immediately precede the next label precedingcomments++; - } - else if (parsed[nl][0].l == ld.cos_langindex && parsed[nl][0].s == ld.cos_label_attrindex && parsed[nl][0].p == 0) { + } else if ( + parsed[nl][0].l == ld.cos_langindex && + parsed[nl][0].s == ld.cos_label_attrindex && + parsed[nl][0].p == 0 + ) { // This is the next label openranges[openranges.length - 1].endLine = nl - precedingcomments - 1; if (openranges[openranges.length - 1].startLine < openranges[openranges.length - 1].endLine) { @@ -221,8 +261,7 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } openranges.pop(); break; - } - else { + } else { precedingcomments = 0; } } @@ -236,7 +275,8 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } } if ( - parsed[line][0].l === ld.cos_langindex && parsed[line][0].s === ld.cos_command_attrindex && + parsed[line][0].l === ld.cos_langindex && + parsed[line][0].s === ld.cos_command_attrindex && firsttokentext.toLowerCase() === "routine" ) { // This is the ROUTINE header line @@ -244,21 +284,24 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } if ( parsed[line].length >= 2 && - (parsed[line][0].l == ld.cos_langindex && parsed[line][0].s == ld.cos_ppc_attrindex) && - (parsed[line][1].l == ld.cos_langindex && parsed[line][1].s == ld.cos_ppc_attrindex) + parsed[line][0].l == ld.cos_langindex && + parsed[line][0].s == ld.cos_ppc_attrindex && + parsed[line][1].l == ld.cos_langindex && + parsed[line][1].s == ld.cos_ppc_attrindex ) { // This line starts with a COS preprocessor command - const ppc = doc.getText(Range.create(line, parsed[line][0].p, line, parsed[line][1].p + parsed[line][1].c)).toLowerCase(); + const ppc = doc + .getText(Range.create(line, parsed[line][0].p, line, parsed[line][1].p + parsed[line][1].c)) + .toLowerCase(); if (ppc === "#if" || ppc === "#ifdef" || ppc === "#ifndef" || ppc === "#ifundef") { // These preprocessor commands always open a new range openranges.push({ startLine: line, endLine: line, - kind: "isc-ppc" + kind: "isc-ppc", }); - } - else if (ppc === "#elseif" || ppc === "#elif" || ppc === "#else") { + } else if (ppc === "#elseif" || ppc === "#elif" || ppc === "#else") { // These preprocessor commands always open a new range and close the previous one let prevrange = openranges.length - 1; for (let rge = openranges.length - 1; rge >= 0; rge--) { @@ -275,10 +318,9 @@ export async function onFoldingRanges(params: FoldingRangeParams) { openranges.push({ startLine: line, endLine: line, - kind: "isc-ppc" + kind: "isc-ppc", }); - } - else if (ppc === "#endif") { + } else if (ppc === "#endif") { // #EndIf always closes the previous range let prevrange = openranges.length - 1; for (let rge = openranges.length - 1; rge >= 0; rge--) { @@ -292,46 +334,49 @@ export async function onFoldingRanges(params: FoldingRangeParams) { result.push(openranges[prevrange]); } openranges.splice(prevrange, 1); - } - else if (ppc === "#define" || ppc === "#def1arg") { + } else if (ppc === "#define" || ppc === "#def1arg") { // Check if the last token is a ##Continue if ( - parsed[line][parsed[line].length - 1].l == ld.cos_langindex && parsed[line][parsed[line].length - 1].s == ld.cos_ppf_attrindex && - mppContinue.test(doc.getText(Range.create( - line, parsed[line][parsed[line].length - 1].p, - line, parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c - ))) + parsed[line][parsed[line].length - 1].l == ld.cos_langindex && + parsed[line][parsed[line].length - 1].s == ld.cos_ppf_attrindex && + mppContinue.test( + doc.getText( + Range.create( + line, + parsed[line][parsed[line].length - 1].p, + line, + parsed[line][parsed[line].length - 1].p + parsed[line][parsed[line].length - 1].c, + ), + ), + ) ) { // This is the start of a multi-line macro definition openranges.push({ startLine: line, endLine: line, - kind: "isc-mlmacro" + kind: "isc-mlmacro", }); inMultiLineMacro = true; } } - } - else if (parsed[line][0].l == ld.xml_langindex) { + } else if (parsed[line][0].l == ld.xml_langindex) { // This is a line of XML // Loop through the line of XML tokens and look for tag delimiters for (let xmltkn = 0; xmltkn < parsed[line].length; xmltkn++) { if (parsed[line][xmltkn].l == ld.xml_langindex && parsed[line][xmltkn].s == ld.xml_tagdelim_attrindex) { - // This is a tag delimiter - const tokentext = doc.getText(Range.create( - line, parsed[line][xmltkn].p, - line, parsed[line][xmltkn].p + parsed[line][xmltkn].c - )); + // This is a tag delimiter + const tokentext = doc.getText( + Range.create(line, parsed[line][xmltkn].p, line, parsed[line][xmltkn].p + parsed[line][xmltkn].c), + ); if (tokentext === "<") { // Open a new XML range openranges.push({ startLine: line, endLine: line, - kind: "isc-xml" + kind: "isc-xml", }); - } - else if (tokentext === "") { + } else if (tokentext === "") { // Close the most recent XML range let prevrange = openranges.length - 1; for (let rge = openranges.length - 1; rge >= 0; rge--) { @@ -348,8 +393,11 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } } } - } - else if (parsed[line].length > 1 && parsed[line][0].l == ld.cls_langindex && parsed[line][0].s == ld.cls_delim_attrindex) { + } else if ( + parsed[line].length > 1 && + parsed[line][0].l == ld.cls_langindex && + parsed[line][0].s == ld.cls_delim_attrindex + ) { // This line starts with a UDL delimiter const firsttwochars = doc.getText(Range.create(line, 0, line, 2)); @@ -367,17 +415,20 @@ export async function onFoldingRanges(params: FoldingRangeParams) { result.push(openranges[prevrange]); } openranges.splice(prevrange, 1); - } - else if (firsttwochars.slice(0, 1) === "<") { + } else if (firsttwochars.slice(0, 1) === "<") { // This is an XML open tag // Only create a Storage range for it if it's not closed on this line let closed = 0; for (let stkn = 1; stkn < parsed[line].length; stkn++) { if ( stkn !== parsed[line].length - 1 && - parsed[line][stkn].l == ld.cls_langindex && parsed[line][stkn].s == ld.cls_delim_attrindex && - parsed[line][stkn + 1].l == ld.cls_langindex && parsed[line][stkn + 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create(line, parsed[line][stkn].p, line, parsed[line][stkn + 1].p + parsed[line][stkn + 1].c)) === "= 0; rge--) { @@ -430,8 +478,7 @@ export async function onFoldingRanges(params: FoldingRangeParams) { openranges.splice(prevrange, 1); } } - } - else if (parsed[line][0].l == ld.cls_langindex && parsed[line][0].s == ld.cls_keyword_attrindex) { + } else if (parsed[line][0].l == ld.cls_langindex && parsed[line][0].s == ld.cls_keyword_attrindex) { // This line starts with a UDL keyword const keytext = firsttokentext.toLowerCase(); @@ -440,18 +487,21 @@ export async function onFoldingRanges(params: FoldingRangeParams) { for (let k = 3; k < parsed[line].length; k++) { if (parsed[line][k].l == ld.cls_langindex && parsed[line][k].s == ld.cls_keyword_attrindex) { // This is a UDL trailing keyword - const keytext = doc.getText(Range.create( - line, parsed[line][k].p, - line, parsed[line][k].p + parsed[line][k].c - )).toLowerCase(); + const keytext = doc + .getText(Range.create(line, parsed[line][k].p, line, parsed[line][k].p + parsed[line][k].c)) + .toLowerCase(); if (keytext === "mimetype") { // The MimeType keyword is present if (parsed[line][k + 2] !== undefined) { // An MimeType is specified - const mimetype = doc.getText(Range.create( - line, parsed[line][k + 2].p + 1, - line, parsed[line][k + 2].p + parsed[line][k + 2].c - 1 - )); + const mimetype = doc.getText( + Range.create( + line, + parsed[line][k + 2].p + 1, + line, + parsed[line][k + 2].p + parsed[line][k + 2].c - 1, + ), + ); if (mimetype === "application/json") { // This is the start of an XData block containing JSON inJSONXData = true; @@ -461,28 +511,30 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } } } - } - else if (inJSONXData && keytext !== "xdata") { + } else if (inJSONXData && keytext !== "xdata") { // We've reached the next class member inJSONXData = false; } - } - else if (inJSONXData) { + } else if (inJSONXData) { // We're in a JSON XData block so look for opening/closing curly braces and brackets for (let tkn = 0; tkn < parsed[line].length; tkn++) { - if (parsed[line][tkn].l === ld.javascript_langindex && parsed[line][tkn].s === ld.javascript_delim_attrindex) { + if ( + parsed[line][tkn].l === ld.javascript_langindex && + parsed[line][tkn].s === ld.javascript_delim_attrindex + ) { // This is a JSON bracket - const jb = doc.getText(Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c)); + const jb = doc.getText( + Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c), + ); if (jb === "[" || jb === "{") { // Create a new JSON range openranges.push({ startLine: line, endLine: line, - kind: "isc-json" + kind: "isc-json", }); - } - else if (jb === "]" || jb === "}") { + } else if (jb === "]" || jb === "}") { // Close the most recent JSON range let prevrange = openranges.length - 1; for (let rge = openranges.length - 1; rge >= 0; rge--) { @@ -499,21 +551,21 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } } } - } - else { + } else { for (let tkn = 0; tkn < parsed[line].length; tkn++) { if (parsed[line][tkn].l === ld.cos_langindex && parsed[line][tkn].s === ld.cos_jsonb_attrindex) { // This is a JSON bracket - const jb = doc.getText(Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c)); + const jb = doc.getText( + Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c), + ); if (jb === "[" || jb === "{") { // Create a new JSON range openranges.push({ startLine: line, endLine: line, - kind: "isc-json" + kind: "isc-json", }); - } - else { + } else { // Close the most recent JSON range let prevrange = openranges.length - 1; for (let rge = openranges.length - 1; rge >= 0; rge--) { @@ -529,13 +581,17 @@ export async function onFoldingRanges(params: FoldingRangeParams) { openranges.splice(prevrange, 1); } } - if (tkn + 1 > dottedDoLevel && parsed[line][tkn].l === ld.cos_langindex && parsed[line][tkn].s === ld.cos_dots_attrindex) { + if ( + tkn + 1 > dottedDoLevel && + parsed[line][tkn].l === ld.cos_langindex && + parsed[line][tkn].s === ld.cos_dots_attrindex + ) { // This is the start of a dotted Do dottedDoLevel++; openranges.push({ startLine: line - 1, endLine: line - 1, - kind: "isc-dotteddo" + kind: "isc-dotteddo", }); } if (tkn === 0 && dottedDoLevel > 0) { @@ -558,8 +614,7 @@ export async function onFoldingRanges(params: FoldingRangeParams) { dottedDoLevel--; } } - } - else { + } else { // At least one dotted Do level is closed for (let level = dottedDoLevel - 1; level >= 0; level--) { if (level > parsed[line].length - 1) { @@ -575,8 +630,10 @@ export async function onFoldingRanges(params: FoldingRangeParams) { result.push(openranges[prevrange]); openranges.splice(prevrange, 1); dottedDoLevel--; - } - else if (parsed[line][level].l !== ld.cos_langindex || parsed[line][level].s !== ld.cos_dots_attrindex) { + } else if ( + parsed[line][level].l !== ld.cos_langindex || + parsed[line][level].s !== ld.cos_dots_attrindex + ) { // This dotted Do level is closed let prevrange = openranges.length - 1; for (let rge = openranges.length - 1; rge >= 0; rge--) { @@ -598,7 +655,7 @@ export async function onFoldingRanges(params: FoldingRangeParams) { openranges.push({ startLine: line, endLine: line, - kind: "isc-embedded" + kind: "isc-embedded", }); } if (parsed[line][tkn].l === ld.cos_langindex && parsed[line][tkn].s === ld.cos_embc_attrindex) { @@ -621,16 +678,17 @@ export async function onFoldingRanges(params: FoldingRangeParams) { // Done with special processing, so loop again to find all ObjectScript braces, UDL parentheses and HTML script tags for (let tkn = 0; tkn < parsed[line].length; tkn++) { if (parsed[line][tkn].l === ld.cos_langindex && parsed[line][tkn].s === ld.cos_brace_attrindex) { - const bracetext = doc.getText(Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c)); + const bracetext = doc.getText( + Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c), + ); if (bracetext === "{") { // Open a new ObjectScript code block range openranges.push({ startLine: line, endLine: line, - kind: "isc-cosblock" + kind: "isc-cosblock", }); - } - else { + } else { // Close the most recent ObjectScript code block range let prevrange = openranges.length - 1; for (let rge = openranges.length - 1; rge >= 0; rge--) { @@ -645,18 +703,18 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } openranges.splice(prevrange, 1); } - } - else if (parsed[line][tkn].l === ld.cls_langindex && parsed[line][tkn].s === ld.cls_delim_attrindex) { - const delimtext = doc.getText(Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c)); + } else if (parsed[line][tkn].l === ld.cls_langindex && parsed[line][tkn].s === ld.cls_delim_attrindex) { + const delimtext = doc.getText( + Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c), + ); if (delimtext === "(") { // Open a new UDL parentheses range openranges.push({ startLine: line, endLine: line, - kind: "isc-udlparen" + kind: "isc-udlparen", }); - } - else if (delimtext === ")") { + } else if (delimtext === ")") { // Close the most recent UDL parentheses range let prevrange = openranges.length - 1; for (let rge = openranges.length - 1; rge >= 0; rge--) { @@ -671,36 +729,39 @@ export async function onFoldingRanges(params: FoldingRangeParams) { } openranges.splice(prevrange, 1); } - } - else if ( + } else if ( tkn < parsed[line].length - 1 && - parsed[line][tkn].l == ld.html_langindex && parsed[line][tkn].s == ld.html_delim_attrindex && - parsed[line][tkn + 1].l == ld.html_langindex && parsed[line][tkn + 1].s == ld.html_tag_attrindex && - doc.getText(Range.create( - line, parsed[line][tkn].p, - line, parsed[line][tkn].p + parsed[line][tkn].c - )) === "<" && - doc.getText(Range.create( - line, parsed[line][tkn + 1].p, - line, parsed[line][tkn + 1].p + parsed[line][tkn + 1].c - )).toLowerCase() === "script" + parsed[line][tkn].l == ld.html_langindex && + parsed[line][tkn].s == ld.html_delim_attrindex && + parsed[line][tkn + 1].l == ld.html_langindex && + parsed[line][tkn + 1].s == ld.html_tag_attrindex && + doc.getText(Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c)) === + "<" && + doc + .getText( + Range.create(line, parsed[line][tkn + 1].p, line, parsed[line][tkn + 1].p + parsed[line][tkn + 1].c), + ) + .toLowerCase() === "script" ) { // Open a new HTML script tag range openranges.push({ startLine: line, endLine: line, - kind: "isc-htmlscript" + kind: "isc-htmlscript", }); - } - else if ( + } else if ( tkn < parsed[line].length - 3 && - parsed[line][tkn].l == ld.html_langindex && parsed[line][tkn].s == ld.html_delim_attrindex && - parsed[line][tkn + 1].l == ld.html_langindex && parsed[line][tkn + 1].s == ld.html_delim_attrindex && - parsed[line][tkn + 2].l == ld.html_langindex && parsed[line][tkn + 2].s == ld.html_tag_attrindex && - doc.getText(Range.create( - line, parsed[line][tkn + 2].p, - line, parsed[line][tkn + 2].p + parsed[line][tkn + 2].c - )).toLowerCase() === "script" + parsed[line][tkn].l == ld.html_langindex && + parsed[line][tkn].s == ld.html_delim_attrindex && + parsed[line][tkn + 1].l == ld.html_langindex && + parsed[line][tkn + 1].s == ld.html_delim_attrindex && + parsed[line][tkn + 2].l == ld.html_langindex && + parsed[line][tkn + 2].s == ld.html_tag_attrindex && + doc + .getText( + Range.create(line, parsed[line][tkn + 2].p, line, parsed[line][tkn + 2].p + parsed[line][tkn + 2].c), + ) + .toLowerCase() === "script" ) { // Close the most recent HTML script tag range let prevrange = openranges.length - 1; @@ -715,16 +776,17 @@ export async function onFoldingRanges(params: FoldingRangeParams) { result.push(openranges[prevrange]); } openranges.splice(prevrange, 1); - } - else if (parsed[line][tkn].l == ld.cos_langindex && parsed[line][tkn].s == ld.cos_comment_attrindex) { - const commentText = doc.getText(Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c)).trim(); + } else if (parsed[line][tkn].l == ld.cos_langindex && parsed[line][tkn].s == ld.cos_comment_attrindex) { + const commentText = doc + .getText(Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c)) + .trim(); const inCComment = openranges.length && openranges[openranges.length - 1].kind == "isc-ccomment"; if (!inCComment && commentText.slice(0, 2) == "/*") { // Open a new C-style comment range openranges.push({ startLine: line, endLine: line, - kind: "isc-ccomment" + kind: "isc-ccomment", }); } else if (inCComment && commentText.slice(-2) == "*/") { // Close the most recent C-style comment range diff --git a/server/src/providers/formatting.ts b/server/src/providers/formatting.ts index 13f6471..d4fccad 100644 --- a/server/src/providers/formatting.ts +++ b/server/src/providers/formatting.ts @@ -1,9 +1,23 @@ -import { DocumentUri } from 'vscode-languageserver-textdocument'; -import { DocumentFormattingParams, DocumentRangeFormattingParams, Position, TextEdit, Range } from 'vscode-languageserver/node'; -import { findFullRange, getLanguageServerSettings, getParsedDocument, getServerSpec, haltOrHang, makeRESTRequest, normalizeClassname } from '../utils/functions'; -import { CommandDoc, StudioOpenDialogFile, ServerSpec } from '../utils/types'; -import { documents } from '../utils/variables'; -import * as ld from '../utils/languageDefinitions'; +import { DocumentUri } from "vscode-languageserver-textdocument"; +import { + DocumentFormattingParams, + DocumentRangeFormattingParams, + Position, + TextEdit, + Range, +} from "vscode-languageserver/node"; +import { + findFullRange, + getLanguageServerSettings, + getParsedDocument, + getServerSpec, + haltOrHang, + makeRESTRequest, + normalizeClassname, +} from "../utils/functions"; +import { CommandDoc, StudioOpenDialogFile, ServerSpec } from "../utils/types"; +import { documents } from "../utils/variables"; +import * as ld from "../utils/languageDefinitions"; import commands from "../documentation/commands.json"; import structuredSystemVariables from "../documentation/structuredSystemVariables.json"; import systemFunctions from "../documentation/systemFunctions.json"; @@ -11,16 +25,20 @@ import systemVariables from "../documentation/systemVariables.json"; /** * Run the formatter on `range` of document `uri`. - * + * * @param uri The uri of the TextDocument to format. * @param range The range within `uri` to format. */ async function formatText(uri: DocumentUri, range?: Range): Promise { const result: TextEdit[] = []; const doc = documents.get(uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } const settings = await getLanguageServerSettings(uri); const server: ServerSpec = await getServerSpec(doc.uri); @@ -36,7 +54,12 @@ async function formatText(uri: DocumentUri, range?: Range): Promise range.end.character) { + } else if (line === range.end.line && parsed[line][token].p > range.end.character) { break; } if (parsed[line][token].l == ld.cos_langindex && parsed[line][token].s == ld.cos_command_attrindex) { // This is an ObjectScript command - const commandrange = Range.create(Position.create(line, parsed[line][token].p), Position.create(line, parsed[line][token].p + parsed[line][token].c)); + const commandrange = Range.create( + Position.create(line, parsed[line][token].p), + Position.create(line, parsed[line][token].p + parsed[line][token].c), + ); const commandtext = doc.getText(commandrange); let commanddoc: CommandDoc | undefined; if (commandtext.toUpperCase() === "H") { // This is "halt" or "hang" commanddoc = haltOrHang(doc, parsed, line, token); - } - else { - commanddoc = commands.find((el) => el.label === commandtext.toUpperCase() || el.alias.includes(commandtext.toUpperCase())); + } else { + commanddoc = commands.find( + (el) => el.label === commandtext.toUpperCase() || el.alias.includes(commandtext.toUpperCase()), + ); } if (commanddoc !== undefined) { let idealcommandtext: string; if (settings.formatting.commands.length === "short" && commanddoc.alias.length === 2) { idealcommandtext = commanddoc.alias[1]; - } - else { + } else { idealcommandtext = commanddoc.label; } if (settings.formatting.commands.case === "lower") { idealcommandtext = idealcommandtext.toLowerCase(); - } - else if (settings.formatting.commands.case === "word") { + } else if (settings.formatting.commands.case === "word") { if (idealcommandtext === "ELSEIF") { idealcommandtext = "ElseIf"; - } - else if (idealcommandtext.charAt(0) === "Z") { + } else if (idealcommandtext.charAt(0) === "Z") { if (idealcommandtext.charAt(1) === "Z") { idealcommandtext = idealcommandtext.slice(0, 3) + idealcommandtext.slice(3).toLowerCase(); - } - else { + } else { idealcommandtext = idealcommandtext.slice(0, 2) + idealcommandtext.slice(2).toLowerCase(); } - } - else { + } else { idealcommandtext = idealcommandtext.slice(0, 1) + idealcommandtext.slice(1).toLowerCase(); } } @@ -154,166 +185,207 @@ async function formatText(uri: DocumentUri, range?: Range): Promise el.label === sysftext.toUpperCase() || el.alias.includes(sysftext.toUpperCase())); + const sysfdoc = systemFunctions.find( + (el) => el.label === sysftext.toUpperCase() || el.alias.includes(sysftext.toUpperCase()), + ); if (sysfdoc !== undefined) { let idealsysftext: string; if (settings.formatting.system.length === "short" && sysfdoc.alias.length === 2) { idealsysftext = sysfdoc.alias[1]; - } - else { + } else { idealsysftext = sysfdoc.label; } if (settings.formatting.system.case === "lower") { idealsysftext = idealsysftext.toLowerCase(); - } - else if (settings.formatting.system.case === "word") { - if (idealsysftext === "$BITCOUNT") { idealsysftext = "$BitCount"; } - else if (idealsysftext === "$BITFIND") { idealsysftext = "$BitFind"; } - else if (idealsysftext === "$BITLOGIC") { idealsysftext = "$BitLogic"; } - else if (idealsysftext === "$CLASSMETHOD") { idealsysftext = "$ClassMethod"; } - else if (idealsysftext === "$CLASSNAME") { idealsysftext = "$ClassName"; } - else if (idealsysftext === "$FNUMBER") { idealsysftext = "$FNumber"; } - else if (idealsysftext === "$INUMBER") { idealsysftext = "$INumber"; } - else if (idealsysftext === "$ISOBJECT") { idealsysftext = "$IsObject"; } - else if (idealsysftext === "$ISVALIDNUM") { idealsysftext = "$IsValidNum"; } - else if (idealsysftext === "$ISVALIDDOUBLE") { idealsysftext = "$IsValidDouble"; } - else if (idealsysftext === "$ISVECTOR") { idealsysftext = "$IsVector"; } - else if (idealsysftext === "$LISTBUILD") { idealsysftext = "$ListBuild"; } - else if (idealsysftext === "$LISTDATA") { idealsysftext = "$ListData"; } - else if (idealsysftext === "$LISTFIND") { idealsysftext = "$ListFind"; } - else if (idealsysftext === "$LISTFROMSTRING") { idealsysftext = "$ListFromString"; } - else if (idealsysftext === "$LISTGET") { idealsysftext = "$ListGet"; } - else if (idealsysftext === "$LISTLENGTH") { idealsysftext = "$ListLength"; } - else if (idealsysftext === "$LISTNEXT") { idealsysftext = "$ListNext"; } - else if (idealsysftext === "$LISTSAME") { idealsysftext = "$ListSame"; } - else if (idealsysftext === "$LISTTOSTRING") { idealsysftext = "$ListToString"; } - else if (idealsysftext === "$LISTUPDATE") { idealsysftext = "$ListUpdate"; } - else if (idealsysftext === "$LISTVALID") { idealsysftext = "$ListValid"; } - else if (idealsysftext === "$NCONVERT") { idealsysftext = "$NConvert"; } - else if (idealsysftext === "$PREFETCHOFF") { idealsysftext = "$PrefetchOff"; } - else if (idealsysftext === "$PREFETCHON") { idealsysftext = "$PrefetchOn"; } - else if (idealsysftext === "$QLENGTH") { idealsysftext = "$QLength"; } - else if (idealsysftext === "$QSUBSCRIPT") { idealsysftext = "$QSubscript"; } - else if (idealsysftext === "$SCONVERT") { idealsysftext = "$SConvert"; } - else if (idealsysftext === "$SORTBEGIN") { idealsysftext = "$SortBegin"; } - else if (idealsysftext === "$SORTEND") { idealsysftext = "$SortEnd"; } - else if (idealsysftext === "$VECTORDEFINED") { idealsysftext = "$VectorDefined"; } - else if (idealsysftext.charAt(1) === "W") { + } else if (settings.formatting.system.case === "word") { + if (idealsysftext === "$BITCOUNT") { + idealsysftext = "$BitCount"; + } else if (idealsysftext === "$BITFIND") { + idealsysftext = "$BitFind"; + } else if (idealsysftext === "$BITLOGIC") { + idealsysftext = "$BitLogic"; + } else if (idealsysftext === "$CLASSMETHOD") { + idealsysftext = "$ClassMethod"; + } else if (idealsysftext === "$CLASSNAME") { + idealsysftext = "$ClassName"; + } else if (idealsysftext === "$FNUMBER") { + idealsysftext = "$FNumber"; + } else if (idealsysftext === "$INUMBER") { + idealsysftext = "$INumber"; + } else if (idealsysftext === "$ISOBJECT") { + idealsysftext = "$IsObject"; + } else if (idealsysftext === "$ISVALIDNUM") { + idealsysftext = "$IsValidNum"; + } else if (idealsysftext === "$ISVALIDDOUBLE") { + idealsysftext = "$IsValidDouble"; + } else if (idealsysftext === "$ISVECTOR") { + idealsysftext = "$IsVector"; + } else if (idealsysftext === "$LISTBUILD") { + idealsysftext = "$ListBuild"; + } else if (idealsysftext === "$LISTDATA") { + idealsysftext = "$ListData"; + } else if (idealsysftext === "$LISTFIND") { + idealsysftext = "$ListFind"; + } else if (idealsysftext === "$LISTFROMSTRING") { + idealsysftext = "$ListFromString"; + } else if (idealsysftext === "$LISTGET") { + idealsysftext = "$ListGet"; + } else if (idealsysftext === "$LISTLENGTH") { + idealsysftext = "$ListLength"; + } else if (idealsysftext === "$LISTNEXT") { + idealsysftext = "$ListNext"; + } else if (idealsysftext === "$LISTSAME") { + idealsysftext = "$ListSame"; + } else if (idealsysftext === "$LISTTOSTRING") { + idealsysftext = "$ListToString"; + } else if (idealsysftext === "$LISTUPDATE") { + idealsysftext = "$ListUpdate"; + } else if (idealsysftext === "$LISTVALID") { + idealsysftext = "$ListValid"; + } else if (idealsysftext === "$NCONVERT") { + idealsysftext = "$NConvert"; + } else if (idealsysftext === "$PREFETCHOFF") { + idealsysftext = "$PrefetchOff"; + } else if (idealsysftext === "$PREFETCHON") { + idealsysftext = "$PrefetchOn"; + } else if (idealsysftext === "$QLENGTH") { + idealsysftext = "$QLength"; + } else if (idealsysftext === "$QSUBSCRIPT") { + idealsysftext = "$QSubscript"; + } else if (idealsysftext === "$SCONVERT") { + idealsysftext = "$SConvert"; + } else if (idealsysftext === "$SORTBEGIN") { + idealsysftext = "$SortBegin"; + } else if (idealsysftext === "$SORTEND") { + idealsysftext = "$SortEnd"; + } else if (idealsysftext === "$VECTORDEFINED") { + idealsysftext = "$VectorDefined"; + } else if (idealsysftext.charAt(1) === "W") { idealsysftext = idealsysftext.slice(0, 3) + idealsysftext.slice(3).toLowerCase(); - } - else if (idealsysftext.charAt(1) === "Z" && idealsysftext.charAt(2) !== "O" && idealsysftext.charAt(2) !== "F") { + } else if ( + idealsysftext.charAt(1) === "Z" && + idealsysftext.charAt(2) !== "O" && + idealsysftext.charAt(2) !== "F" + ) { idealsysftext = idealsysftext.slice(0, 3) + idealsysftext.slice(3).toLowerCase(); - } - else { + } else { idealsysftext = idealsysftext.slice(0, 2) + idealsysftext.slice(2).toLowerCase(); } + } else { + /* empty */ } - else { /* empty */ } if (sysftext !== idealsysftext) { // Replace old text with the new text result.push({ range: sysfrange, - newText: idealsysftext + newText: idealsysftext, }); } } - } - else if (parsed[line][token].l == ld.cos_langindex && parsed[line][token].s == ld.cos_ssysv_attrindex) { + } else if (parsed[line][token].l == ld.cos_langindex && parsed[line][token].s == ld.cos_ssysv_attrindex) { // This is a structured system variable - const ssysvrange = Range.create(Position.create(line, parsed[line][token].p), Position.create(line, parsed[line][token].p + parsed[line][token].c)); + const ssysvrange = Range.create( + Position.create(line, parsed[line][token].p), + Position.create(line, parsed[line][token].p + parsed[line][token].c), + ); const ssysvtext = doc.getText(ssysvrange); if (ssysvtext !== "^$") { if (ssysvtext.indexOf("^$") === -1) { - const ssysvdoc = structuredSystemVariables.find((el) => el.label === "^$" + ssysvtext.toUpperCase() || el.alias.includes("^$" + ssysvtext.toUpperCase())); + const ssysvdoc = structuredSystemVariables.find( + (el) => el.label === "^$" + ssysvtext.toUpperCase() || el.alias.includes("^$" + ssysvtext.toUpperCase()), + ); if (ssysvdoc !== undefined) { let idealssysvtext: string; if (settings.formatting.system.length === "short" && ssysvdoc.alias.length === 2) { idealssysvtext = ssysvdoc.alias[1].slice(2); - } - else { + } else { idealssysvtext = ssysvdoc.label.slice(2); } if (settings.formatting.system.case === "lower") { idealssysvtext = idealssysvtext.toLowerCase(); - } - else if (settings.formatting.system.case === "word") { + } else if (settings.formatting.system.case === "word") { idealssysvtext = idealssysvtext.slice(0, 1) + idealssysvtext.slice(1).toLowerCase(); } if (ssysvtext !== idealssysvtext) { // Replace old text with the new text result.push({ range: ssysvrange, - newText: idealssysvtext + newText: idealssysvtext, }); } } - } - else { - const ssysvdoc = structuredSystemVariables.find((el) => el.label === ssysvtext.toUpperCase() || el.alias.includes(ssysvtext.toUpperCase())); + } else { + const ssysvdoc = structuredSystemVariables.find( + (el) => el.label === ssysvtext.toUpperCase() || el.alias.includes(ssysvtext.toUpperCase()), + ); if (ssysvdoc !== undefined) { let idealssysvtext: string; if (settings.formatting.system.length === "short" && ssysvdoc.alias.length === 2) { idealssysvtext = ssysvdoc.alias[1]; - } - else { + } else { idealssysvtext = ssysvdoc.label; } if (settings.formatting.system.case === "lower") { idealssysvtext = idealssysvtext.toLowerCase(); - } - else if (settings.formatting.system.case === "word") { + } else if (settings.formatting.system.case === "word") { idealssysvtext = idealssysvtext.slice(0, 3) + idealssysvtext.slice(3).toLowerCase(); } if (ssysvtext !== idealssysvtext) { // Replace old text with the new text result.push({ range: ssysvrange, - newText: idealssysvtext + newText: idealssysvtext, }); } } } } - } - else if (parsed[line][token].l == ld.cos_langindex && parsed[line][token].s == ld.cos_sysv_attrindex) { + } else if (parsed[line][token].l == ld.cos_langindex && parsed[line][token].s == ld.cos_sysv_attrindex) { // This is a system variable - const sysvrange = Range.create(Position.create(line, parsed[line][token].p), Position.create(line, parsed[line][token].p + parsed[line][token].c)); + const sysvrange = Range.create( + Position.create(line, parsed[line][token].p), + Position.create(line, parsed[line][token].p + parsed[line][token].c), + ); const sysvtext = doc.getText(sysvrange); - const sysvdoc = systemVariables.find((el) => el.label === sysvtext.toUpperCase() || el.alias.includes(sysvtext.toUpperCase())); + const sysvdoc = systemVariables.find( + (el) => el.label === sysvtext.toUpperCase() || el.alias.includes(sysvtext.toUpperCase()), + ); if (sysvdoc !== undefined) { let idealsysvtext: string; if (settings.formatting.system.length === "short" && sysvdoc.alias.length === 2) { - if (sysvtext.toUpperCase() === "$SYSTEM" && parsed[line][token + 1].l == ld.cos_langindex && parsed[line][token + 1].s == ld.cos_clsname_attrindex) { + if ( + sysvtext.toUpperCase() === "$SYSTEM" && + parsed[line][token + 1].l == ld.cos_langindex && + parsed[line][token + 1].s == ld.cos_clsname_attrindex + ) { // $SYSTEM is being used as part of a class name and can't be shortened idealsysvtext = sysvdoc.label; - } - else { + } else { idealsysvtext = sysvdoc.alias[1]; } - } - else { + } else { idealsysvtext = sysvdoc.label; } if (settings.formatting.system.case === "lower") { idealsysvtext = idealsysvtext.toLowerCase(); - } - else if (settings.formatting.system.case === "word") { + } else if (settings.formatting.system.case === "word") { if (idealsysvtext.charAt(1) === "Z") { idealsysvtext = idealsysvtext.slice(0, 3) + idealsysvtext.slice(3).toLowerCase(); - } - else { + } else { idealsysvtext = idealsysvtext.slice(0, 2) + idealsysvtext.slice(2).toLowerCase(); } } @@ -321,88 +393,113 @@ async function formatText(uri: DocumentUri, range?: Range): Promise 0 && ((parsed[line][token].l == ld.cls_langindex && parsed[line][token].s == ld.cls_clsname_attrindex) || (parsed[line][token].l == ld.cos_langindex && parsed[line][token].s == ld.cos_clsname_attrindex)) && - (token == 0 || (token > 0 && (parsed[line][token - 1].l != parsed[line][token].l || parsed[line][token - 1].s != parsed[line][token].s))) && + (token == 0 || + (token > 0 && + (parsed[line][token - 1].l != parsed[line][token].l || + parsed[line][token - 1].s != parsed[line][token].s))) && settings.formatting.expandClassNames ) { // This is the first token of a class name // Don't format a class name that follows the "Class" keyword - if (token !== 0 && parsed[line][token - 1].l == ld.cls_langindex && parsed[line][token - 1].s == ld.cls_keyword_attrindex) { + if ( + token !== 0 && + parsed[line][token - 1].l == ld.cls_langindex && + parsed[line][token - 1].s == ld.cls_keyword_attrindex + ) { // The previous token is a UDL keyword - const prevkeytext = doc.getText(Range.create( - Position.create(line, parsed[line][token - 1].p), - Position.create(line, parsed[line][token - 1].p + parsed[line][token - 1].c) - )).toLowerCase(); + const prevkeytext = doc + .getText( + Range.create( + Position.create(line, parsed[line][token - 1].p), + Position.create(line, parsed[line][token - 1].p + parsed[line][token - 1].c), + ), + ) + .toLowerCase(); if (prevkeytext === "class") { continue; } } // Don't format package names in the Import line if ( - token !== 0 && parsed[line][0].l == ld.cls_langindex && parsed[line][0].s == ld.cls_keyword_attrindex && - doc.getText(Range.create( - Position.create(line, parsed[line][0].p), - Position.create(line, parsed[line][0].p + parsed[line][0].c) - )).toLowerCase() == "import" + token !== 0 && + parsed[line][0].l == ld.cls_langindex && + parsed[line][0].s == ld.cls_keyword_attrindex && + doc + .getText( + Range.create( + Position.create(line, parsed[line][0].p), + Position.create(line, parsed[line][0].p + parsed[line][0].c), + ), + ) + .toLowerCase() == "import" ) { break; } // Get the full text of the selection - const wordrange = findFullRange(line, parsed, token, parsed[line][token].p, parsed[line][token].p + parsed[line][token].c); + const wordrange = findFullRange( + line, + parsed, + token, + parsed[line][token].p, + parsed[line][token].p + parsed[line][token].c, + ); let word = doc.getText(wordrange); if (word.charAt(0) === ".") { // Can't format $SYSTEM.ClassName @@ -416,11 +513,20 @@ async function formatText(uri: DocumentUri, range?: Range): Promise { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } const server: ServerSpec = await getServerSpec(params.textDocument.uri); const settings = await getLanguageServerSettings(params.textDocument.uri); @@ -51,10 +76,12 @@ export async function onHover(params: TextDocumentPositionParams): Promise= symbolstart && params.position.character <= symbolend) { // We found the right symbol in the line - if (( - (parsed[params.position.line][i].l == ld.cls_langindex && parsed[params.position.line][i].s == ld.cls_clsname_attrindex) || - (parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_clsname_attrindex) - ) && doc.getText(Range.create(params.position.line, 0, params.position.line, 6)).toLowerCase() !== "import" + if ( + ((parsed[params.position.line][i].l == ld.cls_langindex && + parsed[params.position.line][i].s == ld.cls_clsname_attrindex) || + (parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_clsname_attrindex)) && + doc.getText(Range.create(params.position.line, 0, params.position.line, 6)).toLowerCase() !== "import" ) { // This is a class name @@ -63,15 +90,18 @@ export async function onHover(params: TextDocumentPositionParams): Promise { if (isSql) return sqlMacroArgs; // Get the rest of the line following the macro - const restofline = doc.getText(Range.create( - params.position.line, macrorange.end.character, - params.position.line, parsed[params.position.line][parsed[params.position.line].length - 1].p + parsed[params.position.line][parsed[params.position.line].length - 1].c - )); + const restofline = doc.getText( + Range.create( + params.position.line, + macrorange.end.character, + params.position.line, + parsed[params.position.line][parsed[params.position.line].length - 1].p + + parsed[params.position.line][parsed[params.position.line].length - 1].c, + ), + ); // If this macro takes arguments, send them in the request body let macroargs = ""; @@ -153,16 +187,14 @@ export async function onHover(params: TextDocumentPositionParams): Promise 3 && parsed[ln][3].s == ld.cos_delim_attrindex && + parsed[ln].length > 3 && + parsed[ln][3].s == ld.cos_delim_attrindex && doc.getText(Range.create(ln, parsed[ln][3].p, ln, parsed[ln][3].p + parsed[ln][3].c)) == "(" ) { // Capture the formal spec for (let tkn = 3; tkn < parsed[ln].length; tkn++) { - formalspec += doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)); + formalspec += doc.getText( + Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c), + ); if (parsed[ln][tkn].s == ld.cos_delim_attrindex && formalspec.endsWith(")")) { definitionendtkn = tkn; break; } } } - if (definitionendtkn == (parsed[ln].length - 1)) { + if (definitionendtkn == parsed[ln].length - 1) { // This is an empty macro definition break; } @@ -208,33 +245,70 @@ export async function onHover(params: TextDocumentPositionParams): Promise 0) { + if ( + Array.isArray(exprespdata?.data?.result?.content?.expansion) && + exprespdata.data.result.content.expansion.length > 0 + ) { // We got data back const exptext = exprespdata.data.result.content.expansion.join(" \n"); if (exptext.slice(0, 5) === "ERROR") { @@ -326,25 +400,33 @@ export async function onHover(params: TextDocumentPositionParams): Promise 0) { + const defrespdata = await makeRESTRequest( + "POST", + 2, + "/action/getmacrodefinition", + server, + defquerydata, + ); + if ( + Array.isArray(defrespdata?.data?.result?.content?.definition) && + defrespdata.data.result.content.definition.length > 0 + ) { // The macro definition was found return { contents: macroDefToDoc(defrespdata.data.result.content.definition), - range: macrorange + range: macrorange, }; } - } - else { + } else { // The expansion was generated successfully return { contents: { kind: MarkupKind.Markdown, - value: `\`\`\`\n${exprespdata.data.result.content.expansion.join("\n")}\n\`\`\`` + value: `\`\`\`\n${exprespdata.data.result.content.expansion.join("\n")}\n\`\`\``, }, - range: macrorange + range: macrorange, }; } } @@ -359,20 +441,26 @@ export async function onHover(params: TextDocumentPositionParams): Promise 0) { + if ( + Array.isArray(respdata?.data?.result?.content?.definition) && + respdata.data.result.content.definition.length > 0 + ) { // The macro definition was found return { contents: macroDefToDoc(respdata.data.result.content.definition), - range: macrorange + range: macrorange, }; } } } - } - else if (parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_sysf_attrindex && settings.hover.system) { + } else if ( + parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_sysf_attrindex && + settings.hover.system + ) { // This is a system function const sysfrange = Range.create(params.position.line, symbolstart, params.position.line, symbolend); const sysftext = doc.getText(sysfrange).toUpperCase(); @@ -381,16 +469,27 @@ export async function onHover(params: TextDocumentPositionParams): Promise= 0; j--) { - if (parsed[params.position.line][j].l == ld.cos_langindex && parsed[params.position.line][j].s == ld.cos_ssysv_attrindex) { - const firsthalf = doc.getText(Range.create( - params.position.line, parsed[params.position.line][j].p, - params.position.line, parsed[params.position.line][j].p + parsed[params.position.line][j].c - )); + if ( + parsed[params.position.line][j].l == ld.cos_langindex && + parsed[params.position.line][j].s == ld.cos_ssysv_attrindex + ) { + const firsthalf = doc.getText( + Range.create( + params.position.line, + parsed[params.position.line][j].p, + params.position.line, + parsed[params.position.line][j].p + parsed[params.position.line][j].c, + ), + ); if (firsthalf === "^$") { firsthalfstart = parsed[params.position.line][j].p; } @@ -448,14 +562,17 @@ export async function onHover(params: TextDocumentPositionParams): Promise el.label === commandtext || el.alias.includes(commandtext)); } if (commanddoc !== undefined) { @@ -491,16 +610,15 @@ export async function onHover(params: TextDocumentPositionParams): Promise**${member}**`; - const nextchar = doc.getText(Range.create(params.position.line, memberrange.end.character, params.position.line, memberrange.end.character + 1)); - if (member == "%New" && respdata.data.result.content.length == 2 && respdata.data.result.content[1].Origin != "%Library.RegisteredObject") { + const nextchar = doc.getText( + Range.create( + params.position.line, + memberrange.end.character, + params.position.line, + memberrange.end.character + 1, + ), + ); + if ( + member == "%New" && + respdata.data.result.content.length == 2 && + respdata.data.result.content[1].Origin != "%Library.RegisteredObject" + ) { // %OnNew has been overridden for this class header += beautifyFormalSpec(respdata.data.result.content[1].FormalSpec, true); header += ` As **${membercontext.baseclass}**`; return { contents: { kind: MarkupKind.Markdown, - value: markupValue(header, documaticHtmlToMarkdown(respdata.data.result.content[ - respdata.data.result.content[1].Description.trim().length ? 1 : 0 - ].Description)) + value: markupValue( + header, + documaticHtmlToMarkdown( + respdata.data.result.content[respdata.data.result.content[1].Description.trim().length ? 1 : 0] + .Description, + ), + ), }, - range: memberrange + range: memberrange, }; - } - else if (respdata.data.result.content[0].Stub !== "") { + } else if (respdata.data.result.content[0].Stub !== "") { // This is a method generated by member inheritance, so we need to get its metadata from the proper subtable const stubarr = respdata.data.result.content[0].Stub.split("."); let stubquery = ""; if (stubarr[2] === "i") { // This is a method generated from an index - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledIndexMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledIndexMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] === "q") { // This is a method generated from a query - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] === "a") { // This is a method generated from a property - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] === "n") { // This is a method generated from a constraint - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubquery !== "") { const stubrespdata = await makeRESTRequest("POST", 1, "/action/query", server, { query: stubquery, - parameters: [stubarr[1], membercontext.baseclass, stubarr[0]] + parameters: [stubarr[1], membercontext.baseclass, stubarr[0]], }); if (Array.isArray(stubrespdata?.data?.result?.content) && stubrespdata.data.result.content.length > 0) { // We got data back @@ -642,14 +786,16 @@ export async function onHover(params: TextDocumentPositionParams): Promise= 0; j--) { - if (parsed[params.position.line][j].l == ld.cls_langindex && parsed[params.position.line][j].s == ld.cls_delim_attrindex) { + if ( + parsed[params.position.line][j].l == ld.cls_langindex && + parsed[params.position.line][j].s == ld.cls_delim_attrindex + ) { // This is a UDL delimiter const delim = doc.getText( Range.create( - params.position.line, parsed[params.position.line][j].p, - params.position.line, parsed[params.position.line][j].p + 1 - ) + params.position.line, + parsed[params.position.line][j].p, + params.position.line, + parsed[params.position.line][j].p + 1, + ), ); if (delim === "[") { foundopenbracket = true; @@ -699,21 +851,29 @@ export async function onHover(params: TextDocumentPositionParams): Promise= 0; k--) { if (parsed[k].length === 0) { continue; } if (parsed[k][0].l == ld.cls_langindex && parsed[k][0].s == ld.cls_keyword_attrindex) { - firstkey = doc.getText(Range.create( - k, parsed[k][0].p, - k, parsed[k][0].p + parsed[k][0].c - )).toLowerCase(); + firstkey = doc + .getText(Range.create(k, parsed[k][0].p, k, parsed[k][0].p + parsed[k][0].c)) + .toLowerCase(); break; } } @@ -723,44 +883,34 @@ export async function onHover(params: TextDocumentPositionParams): PromiseclassKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "constraint") { + } else if (firstkey === "constraint") { // This is a constraint keyword keydoc = constraintKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "foreignkey") { + } else if (firstkey === "foreignkey") { // This is a ForeignKey keyword keydoc = foreignkeyKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "index") { + } else if (firstkey === "index") { // This is a index keyword keydoc = indexKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "method" || firstkey === "classmethod" || firstkey === "clientmethod") { + } else if (firstkey === "method" || firstkey === "classmethod" || firstkey === "clientmethod") { // This is a method keyword keydoc = methodKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "parameter") { + } else if (firstkey === "parameter") { // This is a parameter keyword keydoc = parameterKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "projection") { + } else if (firstkey === "projection") { // This is a projection keyword keydoc = projectionKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "property" || firstkey === "relationship") { + } else if (firstkey === "property" || firstkey === "relationship") { // This is a property keyword keydoc = propertyKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "query") { + } else if (firstkey === "query") { // This is a query keyword keydoc = queryKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "trigger") { + } else if (firstkey === "trigger") { // This is a trigger keyword keydoc = triggerKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); - } - else if (firstkey === "xdata") { + } else if (firstkey === "xdata") { // This is an XData keyword keydoc = xdataKeywords.find((keydoc) => keydoc.name.toLowerCase() === thiskeytext); } @@ -772,30 +922,44 @@ export async function onHover(params: TextDocumentPositionParams): Promise typedoc.name === tokentext); @@ -803,14 +967,16 @@ export async function onHover(params: TextDocumentPositionParams): Promise 0) { @@ -879,23 +1051,28 @@ export async function onHover(params: TextDocumentPositionParams): Promise 0) { // We got data back let header = `(**${normalizedname}**) **${procname}**`; - const nextchar = doc.getText(Range.create(params.position.line, idenrange.end.character, params.position.line, idenrange.end.character + 1)); + const nextchar = doc.getText( + Range.create( + params.position.line, + idenrange.end.character, + params.position.line, + idenrange.end.character + 1, + ), + ); if (nextchar === "(") { header = header + beautifyFormalSpec(respdata.data.result.content[0].FormalSpec, true); if (respdata.data.result.content[0].ReturnType !== "") { @@ -944,31 +1130,36 @@ export async function onHover(params: TextDocumentPositionParams): Promise 0) { + if (iden.split(".").length - 1 > 0) { // We won't resolve properties that don't contain the table name const tblname = iden.slice(0, iden.lastIndexOf(".")); const propname = iden.slice(iden.lastIndexOf(".") + 1); if (tblname.lastIndexOf("_") > tblname.lastIndexOf(".")) { // This table is projected from a multi-dimensional property, so we can't provide any info - } - else { + } else { // Normalize the class name if there are imports - const normalizedname = await normalizeClassname(doc, parsed, tblname.replace(/_/g, "."), server, params.position.line); + const normalizedname = await normalizeClassname( + doc, + parsed, + tblname.replace(/_/g, "."), + server, + params.position.line, + ); if (normalizedname !== "") { // Query the server to get the description of this property const data: QueryData = { - query: "SELECT Description, CASE WHEN Collection IS NOT NULL THEN Collection||' Of '||Type ELSE Type END AS DisplayType FROM %Dictionary.CompiledProperty WHERE Parent = ? AND name = ?", - parameters: [normalizedname, propname] + query: + "SELECT Description, CASE WHEN Collection IS NOT NULL THEN Collection||' Of '||Type ELSE Type END AS DisplayType FROM %Dictionary.CompiledProperty WHERE Parent = ? AND name = ?", + parameters: [normalizedname, propname], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, data); if (Array.isArray(respdata?.data?.result?.content) && respdata.data.result.content.length > 0) { @@ -980,17 +1171,20 @@ export async function onHover(params: TextDocumentPositionParams): Promise el.label.toLowerCase().replace(/\s+/g, '') === pp.toLowerCase()); + const ppobj = preprocessorDirectives.find( + (el) => el.label.toLowerCase().replace(/\s+/g, "") === pp.toLowerCase(), + ); if (ppobj !== undefined) { return { contents: { kind: MarkupKind.Markdown, value: markupValue( ppobj.documentation, - `[Online documentation](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_macros_${ppobj.link})` - ) + `[Online documentation](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_macros_${ppobj.link})`, + ), }, - range: pprange + range: pprange, }; } - } - else if (parsed[params.position.line][i].l == ld.cls_langindex && parsed[params.position.line][i].s == ld.cls_cparam_attrindex) { + } else if ( + parsed[params.position.line][i].l == ld.cls_langindex && + parsed[params.position.line][i].s == ld.cls_cparam_attrindex + ) { // This is a class name parameter // Verify that is is a parameter for a class name and not a method argument @@ -1024,14 +1222,14 @@ export async function onHover(params: TextDocumentPositionParams): Promise e.name === param); + const coreParam = corePropertyParams.find((e) => e.name === param); if (coreParam !== undefined) { return { contents: { kind: MarkupKind.PlainText, - value: coreParam.desc + value: coreParam.desc, }, - range: paramrange + range: paramrange, }; } @@ -1039,30 +1237,31 @@ export async function onHover(params: TextDocumentPositionParams): Promise 0) { // We got data back - const header = `(**${normalizedcls}**) **${param}**${respdata.data.result.content[0].Type != "" ? ` As **${respdata.data.result.content[0].Type}**` : "" - }`; + const header = `(**${normalizedcls}**) **${param}**${ + respdata.data.result.content[0].Type != "" ? ` As **${respdata.data.result.content[0].Type}**` : "" + }`; return { contents: { kind: MarkupKind.Markdown, - value: markupValue(header, documaticHtmlToMarkdown(respdata.data.result.content[0].Description)) + value: markupValue(header, documaticHtmlToMarkdown(respdata.data.result.content[0].Description)), }, - range: paramrange + range: paramrange, }; } } } - } - else if ( - parsed[params.position.line][i].l == ld.cls_langindex && ( - parsed[params.position.line][i].s == ld.cls_xmlelemname_attrindex || + } else if ( + parsed[params.position.line][i].l == ld.cls_langindex && + (parsed[params.position.line][i].s == ld.cls_xmlelemname_attrindex || parsed[params.position.line][i].s == ld.cls_xmlattrname_attrindex) ) { // This is a Storage XML element or attribute name @@ -1085,38 +1284,40 @@ export async function onHover(params: TextDocumentPositionParams): Promise**${method}**${beautifyFormalSpec(respdata.data.result.content[0].FormalSpec, true)}`; @@ -1190,9 +1391,9 @@ export async function onHover(params: TextDocumentPositionParams): Promise { const doc = documents.get(params.uri); - if (doc === undefined) { return []; } + if (doc === undefined) { + return []; + } if (doc.languageId !== "objectscript-class") { // Can't override class members if the document isn't a class return []; } const parsed = await getParsedDocument(params.uri); - if (parsed === undefined) { return []; } + if (parsed === undefined) { + return []; + } const server: ServerSpec = await getServerSpec(params.uri); const result: QuickPickItem[] = []; @@ -223,7 +257,8 @@ export async function listOverridableMembers(params: ListOverridableMembersParam for (let ln = 0; ln < parsed.length; ln++) { if (!parsed[ln]?.length) continue; if ( - parsed[ln][0].l == ld.cls_langindex && parsed[ln][0].s == ld.cls_keyword_attrindex && + parsed[ln][0].l == ld.cls_langindex && + parsed[ln][0].s == ld.cls_keyword_attrindex && doc.getText(Range.create(ln, parsed[ln][0].p, ln, parsed[ln][0].p + parsed[ln][0].c)).toLowerCase() == "class" ) { thisclass = doc.getText(findFullRange(ln, parsed, 1, parsed[ln][1].p, parsed[ln][1].p + parsed[ln][1].c)); @@ -234,12 +269,12 @@ export async function listOverridableMembers(params: ListOverridableMembersParam if (thisclass !== "") { // We found the name of this class - const showInternalStr = await showInternalForServer(server) ? "" : " AND Internal = 0"; + const showInternalStr = (await showInternalForServer(server)) ? "" : " AND Internal = 0"; // Build the list of QuickPickItems if (params.memberType === "Method") { const querydata: QueryData = { query: `SELECT Name, Origin, ClassMethod, ReturnType FROM %Dictionary.CompiledMethod WHERE Parent = ? AND Stub IS NULL AND Origin != ? AND Final = 0 AND NotInheritable = 0${showInternalStr}`, - parameters: [thisclass, thisclass] + parameters: [thisclass, thisclass], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -248,23 +283,21 @@ export async function listOverridableMembers(params: ListOverridableMembersParam result.push({ label: memobj.Name, description: memobj.ReturnType, - detail: "ClassMethod, Origin class: " + memobj.Origin + detail: "ClassMethod, Origin class: " + memobj.Origin, }); - } - else { + } else { result.push({ label: memobj.Name, description: memobj.ReturnType, - detail: "Method, Origin class: " + memobj.Origin + detail: "Method, Origin class: " + memobj.Origin, }); } } } - } - else if (params.memberType === "Parameter") { + } else if (params.memberType === "Parameter") { const querydata: QueryData = { query: `SELECT Name, Origin, Type FROM %Dictionary.CompiledParameter WHERE Parent = ? AND Origin != ? AND Final = 0${showInternalStr}`, - parameters: [thisclass, thisclass] + parameters: [thisclass, thisclass], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -272,15 +305,14 @@ export async function listOverridableMembers(params: ListOverridableMembersParam result.push({ label: memobj.Name, description: memobj.Type, - detail: "Origin class: " + memobj.Origin + detail: "Origin class: " + memobj.Origin, }); } } - } - else if (params.memberType === "Projection") { + } else if (params.memberType === "Projection") { const querydata: QueryData = { query: `SELECT Name, Origin, Type FROM %Dictionary.CompiledProjection WHERE Parent = ? AND Origin != ?${showInternalStr}`, - parameters: [thisclass, thisclass] + parameters: [thisclass, thisclass], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -288,15 +320,14 @@ export async function listOverridableMembers(params: ListOverridableMembersParam result.push({ label: memobj.Name, description: memobj.Type, - detail: "Origin class: " + memobj.Origin + detail: "Origin class: " + memobj.Origin, }); } } - } - else if (params.memberType === "Property") { + } else if (params.memberType === "Property") { const querydata: QueryData = { query: `SELECT Name, Origin, Type FROM %Dictionary.CompiledProperty WHERE Parent = ? AND Origin != ? AND Final = 0${showInternalStr}`, - parameters: [thisclass, thisclass] + parameters: [thisclass, thisclass], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -304,15 +335,14 @@ export async function listOverridableMembers(params: ListOverridableMembersParam result.push({ label: memobj.Name, description: memobj.Type, - detail: "Origin class: " + memobj.Origin + detail: "Origin class: " + memobj.Origin, }); } } - } - else if (params.memberType === "Query") { + } else if (params.memberType === "Query") { const querydata: QueryData = { query: `SELECT Name, Origin, Type FROM %Dictionary.CompiledQuery WHERE Parent = ? AND Origin != ? AND Final = 0${showInternalStr}`, - parameters: [thisclass, thisclass] + parameters: [thisclass, thisclass], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -320,15 +350,14 @@ export async function listOverridableMembers(params: ListOverridableMembersParam result.push({ label: memobj.Name, description: memobj.Type, - detail: "Origin class: " + memobj.Origin + detail: "Origin class: " + memobj.Origin, }); } } - } - else if (params.memberType === "Trigger") { + } else if (params.memberType === "Trigger") { const querydata: QueryData = { query: `SELECT Name, Origin, Event FROM %Dictionary.CompiledTrigger WHERE Parent = ? AND Origin != ? AND Final = 0${showInternalStr}`, - parameters: [thisclass, thisclass] + parameters: [thisclass, thisclass], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -336,15 +365,14 @@ export async function listOverridableMembers(params: ListOverridableMembersParam result.push({ label: memobj.Name, description: memobj.Event, - detail: "Origin class: " + memobj.Origin + detail: "Origin class: " + memobj.Origin, }); } } - } - else if (params.memberType === "XData") { + } else if (params.memberType === "XData") { const querydata: QueryData = { query: `SELECT Name, Origin, MimeType FROM %Dictionary.CompiledXData WHERE Parent = ? AND Origin != ?${showInternalStr}`, - parameters: [thisclass, thisclass] + parameters: [thisclass, thisclass], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { @@ -352,7 +380,7 @@ export async function listOverridableMembers(params: ListOverridableMembersParam result.push({ label: memobj.Name, description: memobj.MimeType, - detail: "Origin class: " + memobj.Origin + detail: "Origin class: " + memobj.Origin, }); } } @@ -367,13 +395,17 @@ export async function listOverridableMembers(params: ListOverridableMembersParam */ export async function addOverridableMembers(params: AddOverridableMembersParams): Promise { const doc = documents.get(params.uri); - if (doc === undefined) { return {}; } + if (doc === undefined) { + return {}; + } if (doc.languageId !== "objectscript-class") { // Can't override class members if the document isn't a class return {}; } const parsed = await getParsedDocument(params.uri); - if (parsed === undefined) { return {}; } + if (parsed === undefined) { + return {}; + } const server: ServerSpec = await getServerSpec(params.uri); // Insert the new members at the cursor position (offset by one line) @@ -381,7 +413,7 @@ export async function addOverridableMembers(params: AddOverridableMembersParams) insertpos.line++; const change: TextEdit = { range: Range.create(insertpos, insertpos), - newText: "" + newText: "", }; // Loop through the QuickPickItem array and map all origin classes to the members @@ -395,23 +427,23 @@ export async function addOverridableMembers(params: AddOverridableMembersParams) membersarr.push(quoteUDLIdentifier(member.label, 1)); membersPerOrigin.set(origin, membersarr); } - } - else { + } else { // Add this origin class to the map with this member in the members array membersPerOrigin.set(origin, [quoteUDLIdentifier(member.label, 1)]); } } const memberKeywords = - params.memberType == "Method" ? "Method|ClassMethod|ClientMethod" : - params.memberType == "Property" ? "Property|Relationship" : - params.memberType; + params.memberType == "Method" + ? "Method|ClassMethod|ClientMethod" + : params.memberType == "Property" + ? "Property|Relationship" + : params.memberType; // Get the text of all origin classes that we need const respdata = await makeRESTRequest("POST", 1, "/docs", server, [...membersPerOrigin.keys()]); if (respdata !== undefined && respdata.data.result.content.length > 0) { for (const cls of respdata.data.result.content) { - // For each member in this class, add it to the 'newText' string const members = membersPerOrigin.get(cls.name); if (members !== undefined) { @@ -424,8 +456,7 @@ export async function addOverridableMembers(params: AddOverridableMembersParams) const firstword = cls.content[ln].split(" ", 1)[0].toLowerCase(); if (cls.content[ln].slice(0, 3) === "///") { desclinect++; - } - else if (regex.test(cls.content[ln])) { + } else if (regex.test(cls.content[ln])) { // This is the right member // Add the description lines to the 'newtText' string if there are any @@ -444,15 +475,16 @@ export async function addOverridableMembers(params: AddOverridableMembersParams) change.newText = change.newText + line + "\n"; if ( - (firstword.indexOf("property") !== -1) || (firstword.indexOf("relationship") !== -1) || - (firstword.indexOf("parameter") !== -1) || (firstword.indexOf("projection") !== -1) + firstword.indexOf("property") !== -1 || + firstword.indexOf("relationship") !== -1 || + firstword.indexOf("parameter") !== -1 || + firstword.indexOf("projection") !== -1 ) { // Look for the end of the member if (cls.content[mln].trim().slice(-1) === ";") { break; } - } - else { + } else { // Look for the start of the member's implementation if (cls.content[mln].trim() === "{") { // Add a blank line and closing curly brace @@ -466,8 +498,7 @@ export async function addOverridableMembers(params: AddOverridableMembersParams) // Add a trailing newline change.newText = change.newText + "\n"; - } - else { + } else { desclinect = 0; } } @@ -478,8 +509,8 @@ export async function addOverridableMembers(params: AddOverridableMembersParams) return { changes: { - [params.uri]: [change] - } + [params.uri]: [change], + }, }; } @@ -488,32 +519,45 @@ export async function addOverridableMembers(params: AddOverridableMembersParams) */ export async function validateOverrideCursor(params: ValidateOverrideCursorParams): Promise { const doc = documents.get(params.uri); - if (doc === undefined) { return false; } + if (doc === undefined) { + return false; + } if (doc.languageId !== "objectscript-class") { // Can't override class members if the document isn't a class return false; } const parsed = await getParsedDocument(params.uri); - if (parsed === undefined) { return false; } + if (parsed === undefined) { + return false; + } // Check that the first non-empty line above the cursor ends with a UDL token let abovevalid = false; for (let ln = params.line - 1; ln >= 0; ln--) { if (parsed[ln].length > 0) { if (parsed[ln][parsed[ln].length - 1].l === ld.cls_langindex) { - if (parsed[ln].length === 1 && doc.getText(Range.create( - Position.create(ln, parsed[ln][0].p), - Position.create(ln, parsed[ln][0].p + parsed[ln][0].c))) === "{" + if ( + parsed[ln].length === 1 && + doc.getText( + Range.create(Position.create(ln, parsed[ln][0].p), Position.create(ln, parsed[ln][0].p + parsed[ln][0].c)), + ) === "{" ) { // This line only contains a UDL open curly brace, so check that the preceding line is the class definition - if (parsed[ln - 1][0].l === ld.cls_langindex && parsed[ln - 1][0].s === ld.cls_keyword_attrindex && doc.getText(Range.create( - Position.create(ln - 1, parsed[ln - 1][0].p), - Position.create(ln - 1, parsed[ln - 1][0].p + parsed[ln - 1][0].c))).toLowerCase() === "class" + if ( + parsed[ln - 1][0].l === ld.cls_langindex && + parsed[ln - 1][0].s === ld.cls_keyword_attrindex && + doc + .getText( + Range.create( + Position.create(ln - 1, parsed[ln - 1][0].p), + Position.create(ln - 1, parsed[ln - 1][0].p + parsed[ln - 1][0].c), + ), + ) + .toLowerCase() === "class" ) { abovevalid = true; } - } - else { + } else { abovevalid = true; } } @@ -534,7 +578,7 @@ export async function validateOverrideCursor(params: ValidateOverrideCursorParam } } - return (abovevalid && belowvalid); + return abovevalid && belowvalid; } /** @@ -546,7 +590,7 @@ export function listParameterTypes(): QuickPickItem[] { for (let i = 0; i < parameterTypes.length; i++) { result.push({ label: parameterTypes[i].name, - description: parameterTypes[i].documentation + description: parameterTypes[i].documentation, }); } return result; @@ -562,14 +606,15 @@ export async function listImportPackages(params: ListImportPackagesParams): Prom // Fetch the list of import packages const querydata: QueryData = { - query: "SELECT $PIECE(Name,'.',1,$LENGTH(Name,'.')-2) AS Package FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?) WHERE $PIECE(Name,'.',$LENGTH(Name,'.')-1) = ?", - parameters: ["*.cls", 1, 1, 1, 1, 0, 0, classname] + query: + "SELECT $PIECE(Name,'.',1,$LENGTH(Name,'.')-2) AS Package FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?) WHERE $PIECE(Name,'.',$LENGTH(Name,'.')-1) = ?", + parameters: ["*.cls", 1, 1, 1, 1, 0, 0, classname], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { for (const packobj of respdata.data.result.content) { result.push({ - label: packobj.Package + label: packobj.Package, }); } } @@ -581,9 +626,13 @@ export async function listImportPackages(params: ListImportPackagesParams): Prom */ export async function addImportPackage(params: AddImportPackageParams): Promise { const doc = documents.get(params.uri); - if (doc === undefined) { return {}; } + if (doc === undefined) { + return {}; + } const parsed = await getParsedDocument(params.uri); - if (parsed === undefined) { return {}; } + if (parsed === undefined) { + return {}; + } // Compute the TextEdits const edits: TextEdit[] = []; @@ -592,36 +641,42 @@ export async function addImportPackage(params: AddImportPackageParams): Promise< continue; } if (parsed[ln][0].l === ld.cls_langindex && parsed[ln][0].s === ld.cls_keyword_attrindex) { - const keyword: string = doc.getText(Range.create( - Position.create(ln, parsed[ln][0].p), - Position.create(ln, parsed[ln][0].p + parsed[ln][0].c) - )).toLowerCase(); + const keyword: string = doc + .getText( + Range.create(Position.create(ln, parsed[ln][0].p), Position.create(ln, parsed[ln][0].p + parsed[ln][0].c)), + ) + .toLowerCase(); if (keyword === "import") { if ( parsed[ln][1].l === ld.cls_langindex && parsed[ln][1].s === ld.cls_delim_attrindex && - doc.getText(Range.create(Position.create(ln, parsed[ln][1].p), Position.create(ln, parsed[ln][1].p + parsed[ln][1].c))) === "(" + doc.getText( + Range.create(Position.create(ln, parsed[ln][1].p), Position.create(ln, parsed[ln][1].p + parsed[ln][1].c)), + ) === "(" ) { // There are several imported packages already const lastparentkn = parsed[ln][parsed[ln].length - 1]; edits.push({ range: Range.create(Position.create(ln, lastparentkn.p), Position.create(ln, lastparentkn.p)), - newText: ", " + params.packagename + newText: ", " + params.packagename, }); } else { - // There is only one imported package + // There is only one imported package const startcurrentpackagetkn = parsed[ln][1]; const endcurrentpackagetkn = parsed[ln][parsed[ln].length - 1]; edits.push({ - range: Range.create(Position.create(ln, startcurrentpackagetkn.p), Position.create(ln, startcurrentpackagetkn.p)), - newText: "(" + range: Range.create( + Position.create(ln, startcurrentpackagetkn.p), + Position.create(ln, startcurrentpackagetkn.p), + ), + newText: "(", }); edits.push({ range: Range.create( Position.create(ln, endcurrentpackagetkn.p + endcurrentpackagetkn.c), - Position.create(ln, endcurrentpackagetkn.p + endcurrentpackagetkn.c) + Position.create(ln, endcurrentpackagetkn.p + endcurrentpackagetkn.c), ), - newText: ", " + params.packagename + ")" + newText: ", " + params.packagename + ")", }); } break; @@ -629,7 +684,7 @@ export async function addImportPackage(params: AddImportPackageParams): Promise< // There is no "Import" keyword edits.push({ range: Range.create(Position.create(0, 0), Position.create(0, 0)), - newText: "Import " + params.packagename + "\n\n" + newText: "Import " + params.packagename + "\n\n", }); break; } @@ -638,8 +693,8 @@ export async function addImportPackage(params: AddImportPackageParams): Promise< return { changes: { - [params.uri]: edits - } + [params.uri]: edits, + }, }; } @@ -648,20 +703,24 @@ export async function addImportPackage(params: AddImportPackageParams): Promise< */ export async function addMethod(params: AddMethodParams): Promise { const doc = documents.get(params.uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.uri); - if (parsed === undefined) { return null; } - const lnstart = params.lnstart; // First non-empty line of the selection - const lnend = params.lnend; // Last non-empty line of the selection + if (parsed === undefined) { + return null; + } + const lnstart = params.lnstart; // First non-empty line of the selection + const lnend = params.lnend; // Last non-empty line of the selection // Compute the TextEdits const edits: TextEdit[] = []; - // Adapt to VSCode Workspace settings + // Adapt to VSCode Workspace settings const vscodesettings = await connection.workspace.getConfiguration([ { scopeUri: params.uri, section: "editor.tabSize" }, { scopeUri: params.uri, section: "editor.insertSpaces" }, - { scopeUri: params.uri, section: "objectscript.multilineMethodArgs" } + { scopeUri: params.uri, section: "objectscript.multilineMethodArgs" }, ]); const tabSize = vscodesettings[0]; const insertSpaces = vscodesettings[1]; @@ -680,7 +739,8 @@ export async function addMethod(params: AddMethodParams): Promise 0; ln--) { - if (parsed[ln].length === 0) { // Empty line + if (parsed[ln].length === 0) { + // Empty line insertpos = Position.create(ln, 0); break; } else if (parsed[ln][0].l === ld.cls_langindex && parsed[ln][0].s === ld.cls_desc_attrindex) { @@ -698,12 +758,13 @@ export async function addMethod(params: AddMethodParams): Promise add keyword "ByRef" to the signature and "." in argument (Ignore ByVal) - const keywordtext: string = doc.getText(Range.create( - Position.create(previoustknln, parsed[previoustknln][previoustkn].p), - Position.create(previoustknln, parsed[previoustknln][previoustkn].p + parsed[previoustknln][previoustkn].c) - )).toLowerCase(); + const keywordtext: string = doc + .getText( + Range.create( + Position.create(previoustknln, parsed[previoustknln][previoustkn].p), + Position.create( + previoustknln, + parsed[previoustknln][previoustkn].p + parsed[previoustknln][previoustkn].c, + ), + ), + ) + .toLowerCase(); if (keywordtext === "output" || keywordtext === "byref") { donorarg[1] = true; } } } else if (donorarg[0] !== "" && parsed[ln][tkn].l === ld.cls_langindex) { // This is the text after the cls parameter - const tkntext: string = doc.getText(Range.create( - Position.create(ln, parsed[ln][tkn].p), - Position.create(ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) + const tkntext: string = doc.getText( + Range.create( + Position.create(ln, parsed[ln][tkn].p), + Position.create(ln, parsed[ln][tkn].p + parsed[ln][tkn].c), + ), ); if (tkntext.charAt(0) === "." || tkntext === ")" || countparen > 1) { // This is a class type or parameter text (in parenthesis) - no space @@ -804,32 +889,42 @@ export async function addMethod(params: AddMethodParams): Promise 0 && parsed[ln][tkn - 1].s === ld.cos_oper_attrindex && - doc.getText(Range.create( - Position.create(ln, parsed[ln][tkn - 1].p), - Position.create(ln, parsed[ln][tkn - 1].p + parsed[ln][tkn - 1].c) - )) === "." + doc.getText( + Range.create( + Position.create(ln, parsed[ln][tkn - 1].p), + Position.create(ln, parsed[ln][tkn - 1].p + parsed[ln][tkn - 1].c), + ), + ) === "." ) { // The declared variable is ByRef or Output of a method if (!declaredbyrefvar.includes(thisvar)) { @@ -939,14 +1043,18 @@ export async function addMethod(params: AddMethodParams): Promise 1 && - parsed[ln][0].l === ld.cos_langindex && parsed[ln][0].s === ld.cos_ppc_attrindex && - parsed[ln][1].l === ld.cos_langindex && parsed[ln][1].s === ld.cos_ppc_attrindex + parsed[ln][0].l === ld.cos_langindex && + parsed[ln][0].s === ld.cos_ppc_attrindex && + parsed[ln][1].l === ld.cos_langindex && + parsed[ln][1].s === ld.cos_ppc_attrindex ) { // This is 2 preprocessor command - const thisdim: string = doc.getText(Range.create( - Position.create(ln, parsed[ln][0].p), - Position.create(ln, parsed[ln][1].p + parsed[ln][1].c) - )); + const thisdim: string = doc.getText( + Range.create( + Position.create(ln, parsed[ln][0].p), + Position.create(ln, parsed[ln][1].p + parsed[ln][1].c), + ), + ); if (thisdim.toLowerCase() === "#dim") { // The first call of the variable is a #Dim -> skip skip = true; @@ -976,25 +1084,29 @@ export async function addMethod(params: AddMethodParams): Promise 0 && parsed[ln][tkn - 1].s === ld.cos_oper_attrindex && - doc.getText(Range.create( - Position.create(ln, parsed[ln][tkn - 1].p), - Position.create(ln, parsed[ln][tkn - 1].p + parsed[ln][tkn - 1].c) - )) === "." + doc.getText( + Range.create( + Position.create(ln, parsed[ln][tkn - 1].p), + Position.create(ln, parsed[ln][tkn - 1].p + parsed[ln][tkn - 1].c), + ), + ) === "." ) { // The undeclared variable is ByRef or Output of a method if (!undeclaredbyrefvar.includes(thisvar)) { @@ -1029,10 +1147,14 @@ export async function addMethod(params: AddMethodParams): Promise 1 && - parsed[ln][0].l === ld.cos_langindex && parsed[ln][0].s === ld.cos_ppc_attrindex && - parsed[ln][1].l === ld.cos_langindex && parsed[ln][1].s === ld.cos_ppc_attrindex + parsed[ln][0].l === ld.cos_langindex && + parsed[ln][0].s === ld.cos_ppc_attrindex && + parsed[ln][1].l === ld.cos_langindex && + parsed[ln][1].s === ld.cos_ppc_attrindex ) { // This is 2 preprocessor command - const thisvar: string = doc.getText(Range.create( - Position.create(ln, parsed[ln][0].p), - Position.create(ln, parsed[ln][1].p + parsed[ln][1].c) - )); + const thisvar: string = doc.getText( + Range.create(Position.create(ln, parsed[ln][0].p), Position.create(ln, parsed[ln][1].p + parsed[ln][1].c)), + ); if (thisvar.toLowerCase() === "#dim") { dimlocation.push(ln); } @@ -1085,19 +1208,33 @@ export async function addMethod(params: AddMethodParams): Promise 0 && donorargs.length > 0) { for (let arg = 0; arg < donorargs.length; arg++) { if (parametervar.includes(donorargs[arg][0])) { - [signature, methodarguments] = prepareExtractMethodSignature(donorargs[arg][0], donorargs[arg][1], donorargs[arg][2], signature, methodarguments, comma); + [signature, methodarguments] = prepareExtractMethodSignature( + donorargs[arg][0], + donorargs[arg][1], + donorargs[arg][2], + signature, + methodarguments, + comma, + ); countarg++; } } } - // Update "undeclaredvar" array: delete the variables that are ByRef/Output of a method - undeclaredvar = undeclaredvar.filter(undeclared => !undeclaredbyrefvar.includes(undeclared)); + // Update "undeclaredvar" array: delete the variables that are ByRef/Output of a method + undeclaredvar = undeclaredvar.filter((undeclared) => !undeclaredbyrefvar.includes(undeclared)); // Add the undeclared variable ByRef to the signature if (undeclaredbyrefvar.length > 0) { for (let ivar = 0; ivar < undeclaredbyrefvar.length; ivar++) { - [signature, methodarguments] = prepareExtractMethodSignature(undeclaredbyrefvar[ivar], true, "", signature, methodarguments, comma); + [signature, methodarguments] = prepareExtractMethodSignature( + undeclaredbyrefvar[ivar], + true, + "", + signature, + methodarguments, + comma, + ); countarg++; } } @@ -1105,10 +1242,10 @@ export async function addMethod(params: AddMethodParams): Promise 0 && initializedundeclaredvar.length > 0) { // Update "undeclaredvar" array: delete the variables that already have been initialized by the For loop of the selection - undeclaredvar = undeclaredvar.filter(undeclared => !initializedundeclaredvar.includes(undeclared)); + undeclaredvar = undeclaredvar.filter((undeclared) => !initializedundeclaredvar.includes(undeclared)); } // Check if the undeclared variable has been set in the selection block - const foundsetundeclaredvar: string[] = []; // List of undeclared variables that have been Set in the selection block + const foundsetundeclaredvar: string[] = []; // List of undeclared variables that have been Set in the selection block // (before the undeclared variable) if (undeclaredvar.length > 0 && setlocation.length > 0) { for (let ivar = 0; ivar < undeclaredvar.length; ivar++) { @@ -1116,11 +1253,17 @@ export async function addMethod(params: AddMethodParams): Promise !foundsetundeclaredvar.includes(undeclared)); + undeclaredvar = undeclaredvar.filter((undeclared) => !foundsetundeclaredvar.includes(undeclared)); } // Add the undeclared variable (not Set in the selection) to the signature if (undeclaredvar.length > 0) { for (let ivar = 0; ivar < undeclaredvar.length; ivar++) { - [signature, methodarguments] = prepareExtractMethodSignature(undeclaredvar[ivar], false, "", signature, methodarguments, comma); + [signature, methodarguments] = prepareExtractMethodSignature( + undeclaredvar[ivar], + false, + "", + signature, + methodarguments, + comma, + ); countarg++; } } @@ -1144,15 +1294,15 @@ export async function addMethod(params: AddMethodParams): Promise 0 && initializeddeclaredvar.length > 0) { // Update "declaredvar" array: delete the variables that already have been initialized by the For loop of the selection - declaredvar = declaredvar.filter(declared => !initializeddeclaredvar.includes(declared)); + declaredvar = declaredvar.filter((declared) => !initializeddeclaredvar.includes(declared)); } // Check if the declared variable has been set by #Dim default value in in the selection block if (declaredvar.length > 0 && setdim.length > 0) { - // Update "declaredvar" array: delete the variables that already have been Set as a default value in the #Dim of the selection - declaredvar = declaredvar.filter(declared => !setdim.includes(declared)); + // Update "declaredvar" array: delete the variables that already have been Set as a default value in the #Dim of the selection + declaredvar = declaredvar.filter((declared) => !setdim.includes(declared)); } // Check if the declared variable has been set by Set in in the selection block - const foundsetdeclaredvar: string[] = []; // List of declared variables that have been Set in the selection block + const foundsetdeclaredvar: string[] = []; // List of declared variables that have been Set in the selection block // (before the declared variable) if (declaredvar.length > 0 && setlocation.length > 0) { for (let ivar = 0; ivar < declaredvar.length; ivar++) { @@ -1160,11 +1310,17 @@ export async function addMethod(params: AddMethodParams): Promise !foundsetdeclaredvar.includes(declared)); + declaredvar = declaredvar.filter((declared) => !foundsetdeclaredvar.includes(declared)); } // Check if the public variable or the local declared variable (dimvar) is declared (dimlocation) in the selection block @@ -1194,7 +1350,14 @@ export async function addMethod(params: AddMethodParams): Promise !founddimvar.includes(dim)); + dimvar = dimvar.filter((dim) => !founddimvar.includes(dim)); } - // Scan for #Dim above selection block + // Scan for #Dim above selection block for (let ln = lnstart - 1; ln > params.lnmethod; ln--) { - if (parsed[ln].length === 0) {// Empty line + if (parsed[ln].length === 0) { + // Empty line continue; } if (dimvar.length > 0) { const todel: string[] = []; // List of variables that have been declared at line ln if ( parsed[ln].length > 1 && - parsed[ln][0].l === ld.cos_langindex && parsed[ln][0].s === ld.cos_ppc_attrindex && - parsed[ln][1].l === ld.cos_langindex && parsed[ln][1].s === ld.cos_ppc_attrindex + parsed[ln][0].l === ld.cos_langindex && + parsed[ln][0].s === ld.cos_ppc_attrindex && + parsed[ln][1].l === ld.cos_langindex && + parsed[ln][1].s === ld.cos_ppc_attrindex ) { // This is 2 preprocessor command - const thisvar: string = doc.getText(Range.create( - Position.create(ln, parsed[ln][0].p), - Position.create(ln, parsed[ln][1].p + parsed[ln][1].c) - )); - if (thisvar.toLowerCase() === "#dim") { // This is a #Dim declaration + const thisvar: string = doc.getText( + Range.create(Position.create(ln, parsed[ln][0].p), Position.create(ln, parsed[ln][1].p + parsed[ln][1].c)), + ); + if (thisvar.toLowerCase() === "#dim") { + // This is a #Dim declaration let dimaddtext: string = ""; let dimtype: string = ""; // Check whether the variables have been declared by this #Dim for (let idimvar = 0; idimvar < dimvar.length; idimvar++) { const dimresult = parseDimLine(doc, parsed, ln, dimvar[idimvar]); - if (dimresult.founddim) { // The variable has been declared by a #Dim. + if (dimresult.founddim) { + // The variable has been declared by a #Dim. dimtype = dimresult.class; todel.push(dimvar[idimvar]); if (declaredvar.includes(dimvar[idimvar]) || declaredbyrefvar.includes(dimvar[idimvar])) { @@ -1243,10 +1410,17 @@ export async function addMethod(params: AddMethodParams): Promise !todel.includes(dim)); + dimvar = dimvar.filter((dim) => !todel.includes(dim)); } else { // All the #Dim have been found break; @@ -1274,9 +1448,8 @@ export async function addMethod(params: AddMethodParams): Promise= 4 && countarg > 1) { signature = "\n" + tab + signature; } - } else { - // The method is a not procedure block + // The method is a not procedure block if (procedurekeyword !== "") { methodkeywords = "[ " + procedurekeyword + " ]"; } @@ -1294,9 +1467,19 @@ export async function addMethod(params: AddMethodParams): Promise= 0; dimln--) { edits.push({ range: Range.create(insertpos, insertpos), - newText: tab + dimadd[dimln] + "\n" + newText: tab + dimadd[dimln] + "\n", }); } edits.push({ range: Range.create(insertpos, insertpos), - newText: "\n" + newText: "\n", }); } let foundfirstindent: boolean = false; let firstwhitespace: string = ""; - for (let ln = lnstart; ln <= lnend; ln++) { // Add the selection block in the method + for (let ln = lnstart; ln <= lnend; ln++) { + // Add the selection block in the method if (parsed[ln].length === 0) { edits.push({ range: Range.create(insertpos, insertpos), - newText: "\n" + newText: "\n", }); } else { - const whitespace = doc.getText(Range.create( - Position.create(ln, 0), - Position.create(ln, parsed[ln][0].p) - )).replace(/\t/g, " ".repeat(tabSize)); + const whitespace = doc + .getText(Range.create(Position.create(ln, 0), Position.create(ln, parsed[ln][0].p))) + .replace(/\t/g, " ".repeat(tabSize)); if (!foundfirstindent) { if (!(parsed[ln][0].l === ld.cos_langindex && parsed[ln][0].s === ld.cos_label_attrindex)) { // This is the first non-label line, record the indent @@ -1348,17 +1531,23 @@ export async function addMethod(params: AddMethodParams): Promise delete the entire line dimtext = ""; @@ -1373,10 +1562,14 @@ export async function addMethod(params: AddMethodParams): Promise { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } const settings = await getLanguageServerSettings(doc.uri); const result: CodeAction[] = []; if (!Array.isArray(params.context.only) || params.context.only.includes(CodeActionKind.Refactor)) { result.push({ - title: 'Wrap in Try/Catch', - kind: CodeActionKind.Refactor - }) + title: "Wrap in Try/Catch", + kind: CodeActionKind.Refactor, + }); result.push({ - title: 'Extract to method', + title: "Extract to method", kind: CodeActionKind.Refactor, - }) + }); if (doc.languageId === "objectscript-macros") { // Can't wrap macro definitions in try/catch, so return disabled CodeAction result[0].disabled = { - reason: "Can't wrap macro definitions in a Try/Catch block" + reason: "Can't wrap macro definitions in a Try/Catch block", }; result[1].disabled = { - reason: "Can't extract macro definitions into a new method" + reason: "Can't extract macro definitions into a new method", }; return result; } @@ -1460,18 +1665,20 @@ export async function onCodeAction(params: CodeActionParams): Promise= 0; ln--) { - if (parsed[ln].length === 0) { // Empty line + if (parsed[ln].length === 0) { + // Empty line continue; } if (parsed[ln][0].l === ld.cls_langindex && parsed[ln][0].s === ld.cls_keyword_attrindex) { - const keyword = doc.getText(Range.create( - Position.create(ln, parsed[ln][0].p), - Position.create(ln, parsed[ln][0].p + parsed[ln][0].c) - )).toLowerCase(); + const keyword = doc + .getText( + Range.create( + Position.create(ln, parsed[ln][0].p), + Position.create(ln, parsed[ln][0].p + parsed[ln][0].c), + ), + ) + .toLowerCase(); if ( - keyword === "classmethod" || keyword === "method" || keyword === "query" || - keyword === "trigger" || keyword === "clientmethod" + keyword === "classmethod" || + keyword === "method" || + keyword === "query" || + keyword === "trigger" || + keyword === "clientmethod" ) { if (keyword === "method") { newmethodtype = "Method"; @@ -1584,14 +1805,22 @@ export async function onCodeAction(params: CodeActionParams): Promise { - // Compute the TextEdits const edits: TextEdit[] = []; - if (codeAction.title === 'Wrap in Try/Catch') { + if (codeAction.title === "Wrap in Try/Catch") { const data: [string, number, number] = <[string, number, number]>codeAction.data; const doc = documents.get(data[0]); - if (doc === undefined) { return codeAction; } + if (doc === undefined) { + return codeAction; + } const parsed = await getParsedDocument(data[0]); - if (parsed === undefined) { return codeAction; } + if (parsed === undefined) { + return codeAction; + } - const lnstart = data[1]; // First non-empty line of the selection - const lnend = data[2]; // Last non-empty line of the selection + const lnstart = data[1]; // First non-empty line of the selection + const lnend = data[2]; // Last non-empty line of the selection // Adapt to VSCode Workspace settings (tabsize/insertspaces) const vscodesettings = await connection.workspace.getConfiguration([ { scopeUri: data[0], section: "editor.tabSize" }, - { scopeUri: data[0], section: "editor.insertSpaces" } + { scopeUri: data[0], section: "editor.insertSpaces" }, ]); const tabSize = vscodesettings[0]; const insertSpaces = vscodesettings[1]; @@ -1741,7 +1997,9 @@ export async function onCodeActionResolve(codeAction: CodeAction): PromisecodeAction.data; const parsed = await getParsedDocument(data[0]); - if (parsed === undefined) { return codeAction; } + if (parsed === undefined) { + return codeAction; + } - const ln = data[1].start.line - const range = Range.create(Position.create(ln, parsed[ln][1].p + parsed[ln][1].c), Position.create(ln, parsed[ln][3].p + parsed[ln][3].c)); + const ln = data[1].start.line; + const range = Range.create( + Position.create(ln, parsed[ln][1].p + parsed[ln][1].c), + Position.create(ln, parsed[ln][3].p + parsed[ln][3].c), + ); - edits.push({ // Remove "As InvalidType" + edits.push({ + // Remove "As InvalidType" range: range, - newText: "" + newText: "", }); codeAction.edit = { changes: { - [data[0]]: edits - } + [data[0]]: edits, + }, }; } return codeAction; diff --git a/server/src/providers/rename.ts b/server/src/providers/rename.ts index 6033b1d..941c938 100644 --- a/server/src/providers/rename.ts +++ b/server/src/providers/rename.ts @@ -1,13 +1,17 @@ -import { Position, RenameParams, TextDocumentPositionParams, TextEdit, Range } from 'vscode-languageserver/node'; -import { documents } from '../utils/variables'; -import * as ld from '../utils/languageDefinitions'; -import { getParsedDocument } from '../utils/functions'; +import { Position, RenameParams, TextDocumentPositionParams, TextEdit, Range } from "vscode-languageserver/node"; +import { documents } from "../utils/variables"; +import * as ld from "../utils/languageDefinitions"; +import { getParsedDocument } from "../utils/functions"; export async function onPrepareRename(params: TextDocumentPositionParams) { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) {return null;} + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) {return null;} + if (parsed === undefined) { + return null; + } if (doc.languageId === "objectscript-class") { let result: Range | null = null; @@ -15,19 +19,23 @@ export async function onPrepareRename(params: TextDocumentPositionParams) { let symbollang: number = -1; for (let i = 0; i < parsed[params.position.line].length; i++) { const symbolstart: number = parsed[params.position.line][i].p; - const symbolend: number = parsed[params.position.line][i].p + parsed[params.position.line][i].c; + const symbolend: number = parsed[params.position.line][i].p + parsed[params.position.line][i].c; if (params.position.character >= symbolstart && params.position.character <= symbolend) { // We found the right symbol in the line if ( (parsed[params.position.line][i].l == ld.cos_langindex && - (parsed[params.position.line][i].s == ld.cos_localdec_attrindex || - parsed[params.position.line][i].s == ld.cos_param_attrindex || - parsed[params.position.line][i].s == ld.cos_otw_attrindex || - parsed[params.position.line][i].s == ld.cos_localundec_attrindex)) || - (parsed[params.position.line][i].l == ld.cls_langindex && parsed[params.position.line][i].s == ld.cls_param_attrindex) + (parsed[params.position.line][i].s == ld.cos_localdec_attrindex || + parsed[params.position.line][i].s == ld.cos_param_attrindex || + parsed[params.position.line][i].s == ld.cos_otw_attrindex || + parsed[params.position.line][i].s == ld.cos_localundec_attrindex)) || + (parsed[params.position.line][i].l == ld.cls_langindex && + parsed[params.position.line][i].s == ld.cls_param_attrindex) ) { // Only save the symbol range if it's potentially renameable - result = Range.create(Position.create(params.position.line,symbolstart),Position.create(params.position.line,symbolend)); + result = Range.create( + Position.create(params.position.line, symbolstart), + Position.create(params.position.line, symbolend), + ); symbollang = parsed[params.position.line][i].l; } break; @@ -36,53 +44,86 @@ export async function onPrepareRename(params: TextDocumentPositionParams) { if (symbollang === ld.cls_langindex) { if ( - parsed[params.position.line][0].l === ld.cls_langindex && parsed[params.position.line][0].s === ld.cls_keyword_attrindex && - doc.getText(Range.create( - Position.create(params.position.line,parsed[params.position.line][0].p), - Position.create(params.position.line,parsed[params.position.line][0].p+parsed[params.position.line][0].c) - )).toLowerCase().indexOf("method") === -1 + parsed[params.position.line][0].l === ld.cls_langindex && + parsed[params.position.line][0].s === ld.cls_keyword_attrindex && + doc + .getText( + Range.create( + Position.create(params.position.line, parsed[params.position.line][0].p), + Position.create( + params.position.line, + parsed[params.position.line][0].p + parsed[params.position.line][0].c, + ), + ), + ) + .toLowerCase() + .indexOf("method") === -1 ) { // This UDL parameter isn't part of a method definition so we can't rename it result = null; - } - else { + } else { // Check if this method is ProcedureBlock let methodprocedureblock: boolean | undefined = undefined; if ( - parsed[params.position.line][0].l === ld.cls_langindex && parsed[params.position.line][0].s === ld.cls_keyword_attrindex && - doc.getText(Range.create( - Position.create(params.position.line,parsed[params.position.line][0].p), - Position.create(params.position.line,parsed[params.position.line][0].p+parsed[params.position.line][0].c) - )).toLowerCase().indexOf("method") !== -1 + parsed[params.position.line][0].l === ld.cls_langindex && + parsed[params.position.line][0].s === ld.cls_keyword_attrindex && + doc + .getText( + Range.create( + Position.create(params.position.line, parsed[params.position.line][0].p), + Position.create( + params.position.line, + parsed[params.position.line][0].p + parsed[params.position.line][0].c, + ), + ), + ) + .toLowerCase() + .indexOf("method") !== -1 ) { // This is a single-line method definition so look for the ProcedureBlock keyword on this line for (let l = 1; l < parsed[params.position.line].length; l++) { - if (parsed[params.position.line][l].l == ld.cls_langindex && parsed[params.position.line][l].s == ld.cls_keyword_attrindex) { - const kw = doc.getText(Range.create( - Position.create(params.position.line,parsed[params.position.line][l].p), - Position.create(params.position.line,parsed[params.position.line][l].p+parsed[params.position.line][l].c) - )); + if ( + parsed[params.position.line][l].l == ld.cls_langindex && + parsed[params.position.line][l].s == ld.cls_keyword_attrindex + ) { + const kw = doc.getText( + Range.create( + Position.create(params.position.line, parsed[params.position.line][l].p), + Position.create( + params.position.line, + parsed[params.position.line][l].p + parsed[params.position.line][l].c, + ), + ), + ); if (kw.toLowerCase() === "procedureblock") { // The ProcedureBlock keyword is set if ( - doc.getText(Range.create( - Position.create(params.position.line,parsed[params.position.line][l+1].p), - Position.create(params.position.line,parsed[params.position.line][l+1].p+parsed[params.position.line][l+1].c) - )) === "=" + doc.getText( + Range.create( + Position.create(params.position.line, parsed[params.position.line][l + 1].p), + Position.create( + params.position.line, + parsed[params.position.line][l + 1].p + parsed[params.position.line][l + 1].c, + ), + ), + ) === "=" ) { // The ProcedureBlock keyword has a value - const kwval = doc.getText(Range.create( - Position.create(params.position.line,parsed[params.position.line][l+2].p), - Position.create(params.position.line,parsed[params.position.line][l+2].p+parsed[params.position.line][l+2].c) - )); + const kwval = doc.getText( + Range.create( + Position.create(params.position.line, parsed[params.position.line][l + 2].p), + Position.create( + params.position.line, + parsed[params.position.line][l + 2].p + parsed[params.position.line][l + 2].c, + ), + ), + ); if (kwval === "0") { methodprocedureblock = false; - } - else { + } else { methodprocedureblock = true; } - } - else { + } else { // The ProcedureBlock keyword doesn't have a value methodprocedureblock = true; } @@ -90,42 +131,54 @@ export async function onPrepareRename(params: TextDocumentPositionParams) { } } } - } - else { + } else { // This is a multi-line method definition - for (let mline = params.position.line+1; mline < parsed.length; mline++) { + for (let mline = params.position.line + 1; mline < parsed.length; mline++) { if ( - parsed[mline][parsed[mline].length-1].l == ld.cls_langindex && parsed[mline][parsed[mline].length-1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - Position.create(mline,parsed[mline][parsed[mline].length-1].p), - Position.create(mline,parsed[mline][parsed[mline].length-1].p+parsed[mline][parsed[mline].length-1].c) - )) !== "," + parsed[mline][parsed[mline].length - 1].l == ld.cls_langindex && + parsed[mline][parsed[mline].length - 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + Position.create(mline, parsed[mline][parsed[mline].length - 1].p), + Position.create( + mline, + parsed[mline][parsed[mline].length - 1].p + parsed[mline][parsed[mline].length - 1].c, + ), + ), + ) !== "," ) { // We've passed the argument lines so look for the ProcedureBlock keyword on this line for (let l = 1; l < parsed[mline].length; l++) { if (parsed[mline][l].l == ld.cls_langindex && parsed[mline][l].s == ld.cls_keyword_attrindex) { - const kw = doc.getText(Range.create(Position.create(mline,parsed[mline][l].p),Position.create(mline,parsed[mline][l].p+parsed[mline][l].c))); + const kw = doc.getText( + Range.create( + Position.create(mline, parsed[mline][l].p), + Position.create(mline, parsed[mline][l].p + parsed[mline][l].c), + ), + ); if (kw.toLowerCase() === "procedureblock") { // The ProcedureBlock keyword is set if ( - doc.getText(Range.create( - Position.create(mline,parsed[mline][l+1].p), - Position.create(mline,parsed[mline][l+1].p+parsed[mline][l+1].c) - )) === "=" + doc.getText( + Range.create( + Position.create(mline, parsed[mline][l + 1].p), + Position.create(mline, parsed[mline][l + 1].p + parsed[mline][l + 1].c), + ), + ) === "=" ) { // The ProcedureBlock keyword has a value - const kwval = doc.getText(Range.create( - Position.create(mline,parsed[mline][l+2].p), - Position.create(mline,parsed[mline][l+2].p+parsed[mline][l+2].c) - )); + const kwval = doc.getText( + Range.create( + Position.create(mline, parsed[mline][l + 2].p), + Position.create(mline, parsed[mline][l + 2].p + parsed[mline][l + 2].c), + ), + ); if (kwval === "0") { methodprocedureblock = false; - } - else { + } else { methodprocedureblock = true; } - } - else { + } else { // The ProcedureBlock keyword doesn't have a value methodprocedureblock = true; } @@ -146,28 +199,43 @@ export async function onPrepareRename(params: TextDocumentPositionParams) { continue; } if ( - parsed[line][0].l === ld.cls_langindex && parsed[line][0].s === ld.cls_keyword_attrindex && - doc.getText(Range.create( - Position.create(line,parsed[line][0].p), - Position.create(line,parsed[line][0].p+parsed[line][0].c) - )).toLowerCase() === "class" + parsed[line][0].l === ld.cls_langindex && + parsed[line][0].s === ld.cls_keyword_attrindex && + doc + .getText( + Range.create( + Position.create(line, parsed[line][0].p), + Position.create(line, parsed[line][0].p + parsed[line][0].c), + ), + ) + .toLowerCase() === "class" ) { // This line is the class definition for (let ctkn = 2; ctkn < parsed[line].length; ctkn++) { if ( - parsed[line][ctkn].l === ld.cls_langindex && parsed[line][ctkn].s === ld.cls_keyword_attrindex && - doc.getText(Range.create( - Position.create(line,parsed[line][ctkn].p), - Position.create(line,parsed[line][ctkn].p+parsed[line][ctkn].c) - )).toLowerCase() === "procedureblock" + parsed[line][ctkn].l === ld.cls_langindex && + parsed[line][ctkn].s === ld.cls_keyword_attrindex && + doc + .getText( + Range.create( + Position.create(line, parsed[line][ctkn].p), + Position.create(line, parsed[line][ctkn].p + parsed[line][ctkn].c), + ), + ) + .toLowerCase() === "procedureblock" ) { // The ProcedureBlock keyword is set if ( - parsed[line][ctkn-1].l === ld.cls_langindex && parsed[line][ctkn-1].s === ld.cls_keyword_attrindex && - doc.getText(Range.create( - Position.create(line,parsed[line][ctkn-1].p), - Position.create(line,parsed[line][ctkn-1].p+parsed[line][ctkn-1].c) - )).toLowerCase() === "not" + parsed[line][ctkn - 1].l === ld.cls_langindex && + parsed[line][ctkn - 1].s === ld.cls_keyword_attrindex && + doc + .getText( + Range.create( + Position.create(line, parsed[line][ctkn - 1].p), + Position.create(line, parsed[line][ctkn - 1].p + parsed[line][ctkn - 1].c), + ), + ) + .toLowerCase() === "not" ) { // The methods in this class are not ProcedureBlock by default classprocedureblock = false; @@ -181,8 +249,7 @@ export async function onPrepareRename(params: TextDocumentPositionParams) { // We don't allow renaming on methods that aren't ProcedureBlock result = null; } - } - else { + } else { if (!methodprocedureblock) { // We don't allow renaming on methods that aren't ProcedureBlock result = null; @@ -190,10 +257,9 @@ export async function onPrepareRename(params: TextDocumentPositionParams) { } } } - + return result; - } - else { + } else { // We don't support renaming on routines, include files or csp files return null; } @@ -201,9 +267,13 @@ export async function onPrepareRename(params: TextDocumentPositionParams) { export async function onRenameRequest(params: RenameParams) { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) {return null;} + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) {return null;} + if (parsed === undefined) { + return null; + } // Loop through the line that we're on to find the token type and old name let oldname: string = ""; @@ -211,26 +281,31 @@ export async function onRenameRequest(params: RenameParams) { let type: number = -1; for (let i = 0; i < parsed[params.position.line].length; i++) { const symbolstart: number = parsed[params.position.line][i].p; - const symbolend: number = parsed[params.position.line][i].p + parsed[params.position.line][i].c; + const symbolend: number = parsed[params.position.line][i].p + parsed[params.position.line][i].c; if (params.position.character >= symbolstart && params.position.character <= symbolend) { // We found the right symbol in the line if ( (parsed[params.position.line][i].l == ld.cos_langindex && - (parsed[params.position.line][i].s == ld.cos_localdec_attrindex || - parsed[params.position.line][i].s == ld.cos_param_attrindex || - parsed[params.position.line][i].s == ld.cos_otw_attrindex || - parsed[params.position.line][i].s == ld.cos_localundec_attrindex)) || - (parsed[params.position.line][i].l == ld.cls_langindex && parsed[params.position.line][i].s == ld.cls_param_attrindex) + (parsed[params.position.line][i].s == ld.cos_localdec_attrindex || + parsed[params.position.line][i].s == ld.cos_param_attrindex || + parsed[params.position.line][i].s == ld.cos_otw_attrindex || + parsed[params.position.line][i].s == ld.cos_localundec_attrindex)) || + (parsed[params.position.line][i].l == ld.cls_langindex && + parsed[params.position.line][i].s == ld.cls_param_attrindex) ) { // This token is a type that we support renaming on lang = parsed[params.position.line][i].l; if (lang === ld.cls_langindex) { type = ld.cos_param_attrindex; - } - else { + } else { type = parsed[params.position.line][i].s; } - oldname = doc.getText(Range.create(Position.create(params.position.line,symbolstart),Position.create(params.position.line,symbolend))); + oldname = doc.getText( + Range.create( + Position.create(params.position.line, symbolstart), + Position.create(params.position.line, symbolend), + ), + ); } break; } @@ -244,27 +319,29 @@ export async function onRenameRequest(params: RenameParams) { const edits: TextEdit[] = []; if (lang === ld.cos_langindex) { // Loop up in the file and compute edits on every line until you reach the method definition - for (let line1 = params.position.line-1; line1 >= 0; line1--) { + for (let line1 = params.position.line - 1; line1 >= 0; line1--) { if (parsed[line1].length === 0) { continue; } if (parsed[line1][0].l === ld.cls_langindex) { if (type !== ld.cos_param_attrindex) { break; - } - else { + } else { // Loop through the line looking for the parameter definition let foundparamdefn = false; for (let udltkn = 0; udltkn < parsed[line1].length; udltkn++) { if (parsed[line1][udltkn].l == ld.cls_langindex && parsed[line1][udltkn].s == ld.cls_param_attrindex) { // This token is a UDL parameter - const udltknrange = Range.create(Position.create(line1,parsed[line1][udltkn].p),Position.create(line1,parsed[line1][udltkn].p+parsed[line1][udltkn].c)); + const udltknrange = Range.create( + Position.create(line1, parsed[line1][udltkn].p), + Position.create(line1, parsed[line1][udltkn].p + parsed[line1][udltkn].c), + ); const udltkntext = doc.getText(udltknrange); if (udltkntext === oldname) { // This is an instance of the variable that we're renaming edits.push({ range: udltknrange, - newText: params.newName + newText: params.newName, }); foundparamdefn = true; break; @@ -279,13 +356,16 @@ export async function onRenameRequest(params: RenameParams) { for (let tkn1 = 0; tkn1 < parsed[line1].length; tkn1++) { if (parsed[line1][tkn1].l == ld.cos_langindex && parsed[line1][tkn1].s == type) { // This token is the same type as the one we're renaming - const tkn1range = Range.create(Position.create(line1,parsed[line1][tkn1].p),Position.create(line1,parsed[line1][tkn1].p+parsed[line1][tkn1].c)); + const tkn1range = Range.create( + Position.create(line1, parsed[line1][tkn1].p), + Position.create(line1, parsed[line1][tkn1].p + parsed[line1][tkn1].c), + ); const tkn1text = doc.getText(tkn1range); if (tkn1text === oldname) { // This is an instance of the variable that we're renaming edits.push({ range: tkn1range, - newText: params.newName + newText: params.newName, }); } } @@ -303,26 +383,32 @@ export async function onRenameRequest(params: RenameParams) { for (let tkn2 = 0; tkn2 < parsed[line2].length; tkn2++) { if (parsed[line2][tkn2].l == ld.cos_langindex && parsed[line2][tkn2].s == type) { // This token is the same type as the one we're renaming - const tkn2range = Range.create(Position.create(line2,parsed[line2][tkn2].p),Position.create(line2,parsed[line2][tkn2].p+parsed[line2][tkn2].c)); + const tkn2range = Range.create( + Position.create(line2, parsed[line2][tkn2].p), + Position.create(line2, parsed[line2][tkn2].p + parsed[line2][tkn2].c), + ); const tkn2text = doc.getText(tkn2range); if (tkn2text === oldname) { // This is an instance of the variable that we're renaming edits.push({ range: tkn2range, - newText: params.newName + newText: params.newName, }); } } } } - } - else { + } else { // Loop down in the file and compute edits on every line until you reach the end of the method for (let line3 = params.position.line; line3 < parsed.length; line3++) { if (parsed[line3].length === 0) { continue; } - if (line3 !== params.position.line && parsed[line3][0].l === ld.cls_langindex && parsed[line3][0].s === ld.cls_keyword_attrindex) { + if ( + line3 !== params.position.line && + parsed[line3][0].l === ld.cls_langindex && + parsed[line3][0].s === ld.cls_keyword_attrindex + ) { break; } for (let tkn3 = 0; tkn3 < parsed[line3].length; tkn3++) { @@ -331,13 +417,16 @@ export async function onRenameRequest(params: RenameParams) { (parsed[line3][tkn3].l == ld.cls_langindex && parsed[line3][tkn3].s == ld.cls_param_attrindex) ) { // This token is the same type as the one we're renaming - const tkn3range = Range.create(Position.create(line3,parsed[line3][tkn3].p),Position.create(line3,parsed[line3][tkn3].p+parsed[line3][tkn3].c)); + const tkn3range = Range.create( + Position.create(line3, parsed[line3][tkn3].p), + Position.create(line3, parsed[line3][tkn3].p + parsed[line3][tkn3].c), + ); const tkn3text = doc.getText(tkn3range); if (tkn3text === oldname) { // This is an instance of the variable that we're renaming edits.push({ range: tkn3range, - newText: params.newName + newText: params.newName, }); } } @@ -349,11 +438,10 @@ export async function onRenameRequest(params: RenameParams) { if (edits.length > 0) { return { changes: { - [params.textDocument.uri]: edits - } + [params.textDocument.uri]: edits, + }, }; - } - else { + } else { return null; } } diff --git a/server/src/providers/requestForwarding.ts b/server/src/providers/requestForwarding.ts index 7f4318d..a51e65c 100644 --- a/server/src/providers/requestForwarding.ts +++ b/server/src/providers/requestForwarding.ts @@ -1,7 +1,7 @@ -import { DocumentUri, Position, Range, TextDocumentPositionParams } from 'vscode-languageserver/node'; -import { documents } from '../utils/variables'; -import * as ld from '../utils/languageDefinitions'; -import { getParsedDocument } from '../utils/functions'; +import { DocumentUri, Position, Range, TextDocumentPositionParams } from "vscode-languageserver/node"; +import { documents } from "../utils/variables"; +import * as ld from "../utils/languageDefinitions"; +import { getParsedDocument } from "../utils/functions"; interface IsolateEmbeddedLanguageParams { uri: DocumentUri; @@ -23,7 +23,7 @@ export async function languageAtPosition(params: TextDocumentPositionParams): Pr let thistoken: number = -1; for (let i = 0; i < parsed[params.position.line].length; i++) { const symbolstart: number = parsed[params.position.line][i].p; - const symbolend: number = parsed[params.position.line][i].p + parsed[params.position.line][i].c; + const symbolend: number = parsed[params.position.line][i].p + parsed[params.position.line][i].c; thistoken = i; if (params.position.character >= symbolstart && params.position.character <= symbolend) { // We found the right symbol in the line @@ -38,10 +38,14 @@ export async function languageAtPosition(params: TextDocumentPositionParams): Pr */ export async function isolateEmbeddedLanguage(params: IsolateEmbeddedLanguageParams): Promise { const doc = documents.get(params.uri); - if (doc === undefined) {return undefined;} + if (doc === undefined) { + return undefined; + } const parsed = await getParsedDocument(params.uri); - if (parsed === undefined) {return undefined;} - + if (parsed === undefined) { + return undefined; + } + if (params.language == ld.py_langindex) { // Embedded language is python @@ -70,32 +74,33 @@ export async function isolateEmbeddedLanguage(params: IsolateEmbeddedLanguagePar parsed[line][0].p == 0 ) { // Keep track of the member that we are in - lastMemberKeyword = doc.getText(Range.create(line,parsed[line][0].p,line,parsed[line][0].p+parsed[line][0].c)); - lastMemberName = doc.getText(Range.create(line,parsed[line][1].p,line,parsed[line][1].p+parsed[line][1].c)); + lastMemberKeyword = doc.getText( + Range.create(line, parsed[line][0].p, line, parsed[line][0].p + parsed[line][0].c), + ); + lastMemberName = doc.getText( + Range.create(line, parsed[line][1].p, line, parsed[line][1].p + parsed[line][1].c), + ); lastMemberKeywordLine = line; } for (let tkn = 0; tkn < parsed[line].length; tkn++) { if ( // This token is not the python - parsed[line][tkn].l != params.language || ( - // We are not in the containing member or the %import XData - lastMemberKeywordLine != positionMemberKeywordLine && - !(lastMemberKeyword.toLowerCase() == "xdata" && - lastMemberName.toLowerCase() == "%import") - ) + parsed[line][tkn].l != params.language || + // We are not in the containing member or the %import XData + (lastMemberKeywordLine != positionMemberKeywordLine && + !(lastMemberKeyword.toLowerCase() == "xdata" && lastMemberName.toLowerCase() == "%import")) ) { // Replace all text for this token with whitespace - newText[line] = - newText[line].slice(0,parsed[line][tkn].p) + + newText[line] = + newText[line].slice(0, parsed[line][tkn].p) + " ".repeat(parsed[line][tkn].c) + - newText[line].slice(parsed[line][tkn].p+parsed[line][tkn].c); + newText[line].slice(parsed[line][tkn].p + parsed[line][tkn].c); } } } return newText.join("\n"); - } - else if (params.language == ld.html_langindex) { + } else if (params.language == ld.html_langindex) { // Embedded language is HTML // Find the offset of the token at params.position @@ -103,7 +108,7 @@ export async function isolateEmbeddedLanguage(params: IsolateEmbeddedLanguagePar let thistoken: number = 0; for (let i = 0; i < parsed[params.position.line].length; i++) { const symbolstart: number = parsed[params.position.line][i].p; - const symbolend: number = parsed[params.position.line][i].p + parsed[params.position.line][i].c; + const symbolend: number = parsed[params.position.line][i].p + parsed[params.position.line][i].c; thistoken = i; if (params.position.character >= symbolstart && params.position.character <= symbolend) { // We found the right symbol in the line @@ -140,12 +145,12 @@ export async function isolateEmbeddedLanguage(params: IsolateEmbeddedLanguagePar ignoreHTML = true; } } - if ((parsed[line][tkn].l != params.language) || ignoreHTML) { + if (parsed[line][tkn].l != params.language || ignoreHTML) { // Replace all text for this token with whitespace - newText[line] = - newText[line].slice(0,parsed[line][tkn].p) + + newText[line] = + newText[line].slice(0, parsed[line][tkn].p) + " ".repeat(parsed[line][tkn].c) + - newText[line].slice(parsed[line][tkn].p+parsed[line][tkn].c); + newText[line].slice(parsed[line][tkn].p + parsed[line][tkn].c); } } } @@ -173,20 +178,19 @@ export async function isolateEmbeddedLanguage(params: IsolateEmbeddedLanguagePar ignoreHTML = true; } } - if ((parsed[line][tkn].l != params.language) || ignoreHTML) { + if (parsed[line][tkn].l != params.language || ignoreHTML) { // Replace all text for this token with whitespace - newText[line] = - newText[line].slice(0,parsed[line][tkn].p) + + newText[line] = + newText[line].slice(0, parsed[line][tkn].p) + " ".repeat(parsed[line][tkn].c) + - newText[line].slice(parsed[line][tkn].p+parsed[line][tkn].c); + newText[line].slice(parsed[line][tkn].p + parsed[line][tkn].c); } } } return newText.join("\n"); - } - else if (params.language == ld.css_langindex) { - // Embedded language is CSS + } else if (params.language == ld.css_langindex) { + // Embedded language is CSS // Only keep CSS in this code block (excluding embeddings) const newText: string[] = doc.getText().split("\n"); @@ -228,24 +232,22 @@ export async function isolateEmbeddedLanguage(params: IsolateEmbeddedLanguagePar if ( parsed[line][tkn].l != params.language || line < startLine || - line > endLine || ( - // Also replace CSP extension tokens - parsed[line][tkn].l == params.language && parsed[line][tkn].s == ld.css_cspext_attrindex - ) + line > endLine || + // Also replace CSP extension tokens + (parsed[line][tkn].l == params.language && parsed[line][tkn].s == ld.css_cspext_attrindex) ) { // Replace all text for this token with whitespace - newText[line] = - newText[line].slice(0,parsed[line][tkn].p) + + newText[line] = + newText[line].slice(0, parsed[line][tkn].p) + " ".repeat(parsed[line][tkn].c) + - newText[line].slice(parsed[line][tkn].p+parsed[line][tkn].c); + newText[line].slice(parsed[line][tkn].p + parsed[line][tkn].c); } } } return newText.join("\n"); - } - else if (params.language == ld.javascript_langindex) { - // Embedded language is JavaScript + } else if (params.language == ld.javascript_langindex) { + // Embedded language is JavaScript // Only keep JavaScript in this code block (excluding embeddings) const newText: string[] = doc.getText().split("\n"); @@ -257,10 +259,8 @@ export async function isolateEmbeddedLanguage(params: IsolateEmbeddedLanguagePar for (let tkn = 0; tkn < parsed[line].length; tkn++) { if ( parsed[line][tkn].l == ld.html_langindex || - parsed[line][tkn].l == ld.cls_langindex || ( - parsed[line][tkn].l == ld.cos_langindex && - parsed[line][tkn].s == ld.cos_js_attrindex - ) + parsed[line][tkn].l == ld.cls_langindex || + (parsed[line][tkn].l == ld.cos_langindex && parsed[line][tkn].s == ld.cos_js_attrindex) ) { startLine = line; found = true; @@ -278,10 +278,8 @@ export async function isolateEmbeddedLanguage(params: IsolateEmbeddedLanguagePar for (let tkn = 0; tkn < parsed[line].length; tkn++) { if ( parsed[line][tkn].l == ld.html_langindex || - parsed[line][tkn].l == ld.cls_langindex || ( - parsed[line][tkn].l == ld.cos_langindex && - parsed[line][tkn].s == ld.cos_embc_attrindex - ) + parsed[line][tkn].l == ld.cls_langindex || + (parsed[line][tkn].l == ld.cos_langindex && parsed[line][tkn].s == ld.cos_embc_attrindex) ) { endLine = line; found = true; @@ -299,16 +297,15 @@ export async function isolateEmbeddedLanguage(params: IsolateEmbeddedLanguagePar if ( parsed[line][tkn].l != params.language || line < startLine || - line > endLine || ( - // Also replace CSP extension tokens - parsed[line][tkn].l == params.language && parsed[line][tkn].s == ld.javascript_cspext_attrindex - ) + line > endLine || + // Also replace CSP extension tokens + (parsed[line][tkn].l == params.language && parsed[line][tkn].s == ld.javascript_cspext_attrindex) ) { // Replace all text for this token with whitespace - newText[line] = - newText[line].slice(0,parsed[line][tkn].p) + + newText[line] = + newText[line].slice(0, parsed[line][tkn].p) + " ".repeat(parsed[line][tkn].c) + - newText[line].slice(parsed[line][tkn].p+parsed[line][tkn].c); + newText[line].slice(parsed[line][tkn].p + parsed[line][tkn].c); } } } diff --git a/server/src/providers/semanticTokens.ts b/server/src/providers/semanticTokens.ts index 3f768c5..761bc4b 100644 --- a/server/src/providers/semanticTokens.ts +++ b/server/src/providers/semanticTokens.ts @@ -1,12 +1,12 @@ -import { SemanticTokensBuilder, SemanticTokensDeltaParams, SemanticTokensParams } from 'vscode-languageserver/node'; -import { lookupattr } from '../parse/parse'; -import { getParsedDocument } from '../utils/functions'; -import { compressedline } from '../utils/types'; -import { tokenBuilders } from '../utils/variables'; +import { SemanticTokensBuilder, SemanticTokensDeltaParams, SemanticTokensParams } from "vscode-languageserver/node"; +import { lookupattr } from "../parse/parse"; +import { getParsedDocument } from "../utils/functions"; +import { compressedline } from "../utils/types"; +import { tokenBuilders } from "../utils/variables"; /** * Get the semantic tokens builder for this document, or create one if it doesn't exist. - * + * * @param document The TextDocument */ function getTokenBuilder(document: string): SemanticTokensBuilder { @@ -24,28 +24,32 @@ function insertTokensIntoBuilder(tokens: compressedline[], builder: SemanticToke const line = tokens[lineno]; for (let itemno = 0; itemno < line.length; itemno++) { const item = line[itemno]; - builder.push(lineno, item.p, item.c, lookupattr(item.l,item.s), 0); + builder.push(lineno, item.p, item.c, lookupattr(item.l, item.s), 0); } } } export async function onSemanticTokens(params: SemanticTokensParams) { const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) {return { data: [] };} - + if (parsed === undefined) { + return { data: [] }; + } + // Get the token builder for this document const builder = getTokenBuilder(params.textDocument.uri); // Push the tokens into the builder insertTokensIntoBuilder(parsed, builder); - + return builder.build(); } export async function onSemanticTokensDelta(params: SemanticTokensDeltaParams) { const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) {return { edits: [] };} - + if (parsed === undefined) { + return { edits: [] }; + } + // Get the token builder for this document const builder = getTokenBuilder(params.textDocument.uri); @@ -54,6 +58,6 @@ export async function onSemanticTokensDelta(params: SemanticTokensDeltaParams) { // Push the tokens into the builder insertTokensIntoBuilder(parsed, builder); - + return builder.buildEdits(); } diff --git a/server/src/providers/signatureHelp.ts b/server/src/providers/signatureHelp.ts index 038f871..6670418 100644 --- a/server/src/providers/signatureHelp.ts +++ b/server/src/providers/signatureHelp.ts @@ -1,8 +1,30 @@ -import { Position, SignatureHelp, SignatureHelpParams, SignatureHelpTriggerKind, SignatureInformation, Range, MarkupKind, ParameterInformation } from 'vscode-languageserver/node'; -import { getServerSpec, getLanguageServerSettings, makeRESTRequest, getMacroContext, findFullRange, getClassMemberContext, beautifyFormalSpec, documaticHtmlToMarkdown, findOpenParen, getParsedDocument, quoteUDLIdentifier, determineActiveParam } from '../utils/functions'; -import { ServerSpec, SignatureHelpDocCache, SignatureHelpMacroContext } from '../utils/types'; -import { documents } from '../utils/variables'; -import * as ld from '../utils/languageDefinitions'; +import { + Position, + SignatureHelp, + SignatureHelpParams, + SignatureHelpTriggerKind, + SignatureInformation, + Range, + MarkupKind, + ParameterInformation, +} from "vscode-languageserver/node"; +import { + getServerSpec, + getLanguageServerSettings, + makeRESTRequest, + getMacroContext, + findFullRange, + getClassMemberContext, + beautifyFormalSpec, + documaticHtmlToMarkdown, + findOpenParen, + getParsedDocument, + quoteUDLIdentifier, + determineActiveParam, +} from "../utils/functions"; +import { ServerSpec, SignatureHelpDocCache, SignatureHelpMacroContext } from "../utils/types"; +import { documents } from "../utils/variables"; +import * as ld from "../utils/languageDefinitions"; /** * Cache of the macro context info required to do a macro expansion when the selected parameter changes. @@ -27,7 +49,7 @@ const emphasizeSuffix: string = "@@@@@"; /** * Edit the macro argument list to markdown-emphasize a given argument in the list. - * + * * @param arglist The list of arguments. * @param arg The one-indexed number of the argument to emphasize. */ @@ -57,9 +79,14 @@ function emphasizeArgument(arglist: string, arg: number): string { } if (start !== -1 && end !== -1) { // Do the replacement - return (arglist.slice(0, start) + emphasizePrefix + arglist.slice(start, end) + emphasizeSuffix + arglist.slice(end)).replace(/\s+/g, ""); - } - else { + return ( + arglist.slice(0, start) + + emphasizePrefix + + arglist.slice(start, end) + + emphasizeSuffix + + arglist.slice(end) + ).replace(/\s+/g, ""); + } else { // Find the unknown positions let result = arglist; while (arglist.indexOf(" ", lastspace + 1) !== -1) { @@ -72,27 +99,37 @@ function emphasizeArgument(arglist: string, arg: number): string { // Look for the next space end = arglist.indexOf(" ", start) - 1; } - result = arglist.slice(0, start) + emphasizePrefix + arglist.slice(start, end) + emphasizeSuffix + arglist.slice(end); + result = + arglist.slice(0, start) + emphasizePrefix + arglist.slice(start, end) + emphasizeSuffix + arglist.slice(end); break; } lastspace = thisspace; } return result.replace(/\s+/g, ""); } -}; +} /** Use HTML to display `exp` as a code block with the empasized argument rendered bold, italic and underlined. */ function markdownifyExpansion(exp: string[]): string { - return "
\n" + exp.map(e => e.trimEnd()).join("\n")
-		.replace(new RegExp(emphasizePrefix, "g"), "")
-		.replace(new RegExp(emphasizeSuffix, "g"), "") + "\n
"; + return ( + "
\n" +
+		exp
+			.map((e) => e.trimEnd())
+			.join("\n")
+			.replace(new RegExp(emphasizePrefix, "g"), "")
+			.replace(new RegExp(emphasizeSuffix, "g"), "") +
+		"\n
" + ); } /** Returns the [start,end] tuples for all parameters in `formalSpec` */ function formalSpecToParamsArr(formalSpec: string): ParameterInformation[] { const result: ParameterInformation[] = []; if (formalSpec.replace(/\s+/g, "") == "()") return result; // No parameters - let currentParamStart = 1, openParenCount = 0, openBraceCount = 0, inQuote = false; + let currentParamStart = 1, + openParenCount = 0, + openBraceCount = 0, + inQuote = false; Array.from(formalSpec).forEach((char: string, idx: number) => { switch (char) { case "{": @@ -107,7 +144,7 @@ function formalSpecToParamsArr(formalSpec: string): ParameterInformation[] { case ")": if (!inQuote) openParenCount--; break; - case "\"": + case '"': inQuote = !inQuote; break; case ",": @@ -122,17 +159,25 @@ function formalSpecToParamsArr(formalSpec: string): ParameterInformation[] { } export async function onSignatureHelp(params: SignatureHelpParams): Promise { - if (params.context === undefined) { return null; } + if (params.context === undefined) { + return null; + } const doc = documents.get(params.textDocument.uri); - if (doc === undefined) { return null; } + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) { return null; } + if (parsed === undefined) { + return null; + } const server: ServerSpec = await getServerSpec(params.textDocument.uri); const settings = await getLanguageServerSettings(params.textDocument.uri); if (params.context.triggerKind == SignatureHelpTriggerKind.Invoked) { // We always base our return value on the triggerCharacter - params.context.triggerCharacter = doc.getText(Range.create(Position.create(params.position.line, params.position.character - 1), params.position)); + params.context.triggerCharacter = doc.getText( + Range.create(Position.create(params.position.line, params.position.character - 1), params.position), + ); } let thistoken: number = -1; @@ -150,9 +195,10 @@ export async function onSignatureHelp(params: SignatureHelpParams): Promise 0) { signatureHelpDocumentationCache.doc = { kind: MarkupKind.Markdown, - value: markdownifyExpansion(exprespdata.data.result.content.expansion) + value: markdownifyExpansion(exprespdata.data.result.content.expansion), }; params.context.activeSignatureHelp.signatures[0].documentation = signatureHelpDocumentationCache.doc; } - } - else { + } else { // This is a method or a macro without an active parameter params.context.activeSignatureHelp.signatures[0].documentation = signatureHelpDocumentationCache.doc; } } return params.context.activeSignatureHelp; - } - else { + } else { // Can't do anything with a retrigger that lacks an active signature return null; } } if ( - params.context.triggerCharacter == "(" && triggerlang === ld.cos_langindex && + params.context.triggerCharacter == "(" && + triggerlang === ld.cos_langindex && ![ld.cos_comment_attrindex, ld.cos_dcom_attrindex, ld.cos_str_attrindex].includes(triggerattr) && thistoken > 0 ) { // This is potentially the start of a signature let newsignature: SignatureHelp | null = null; - if (parsed[params.position.line][thistoken - 1].l == ld.cos_langindex && parsed[params.position.line][thistoken - 1].s == ld.cos_macro_attrindex) { + if ( + parsed[params.position.line][thistoken - 1].l == ld.cos_langindex && + parsed[params.position.line][thistoken - 1].s == ld.cos_macro_attrindex + ) { // This is a macro // Get the details of this class const maccon = getMacroContext(doc, parsed, params.position.line); // Get the full range of the macro - const macrorange = findFullRange(params.position.line, parsed, thistoken - 1, parsed[params.position.line][thistoken - 1].p, parsed[params.position.line][thistoken - 1].p + parsed[params.position.line][thistoken - 1].c); + const macrorange = findFullRange( + params.position.line, + parsed, + thistoken - 1, + parsed[params.position.line][thistoken - 1].p, + parsed[params.position.line][thistoken - 1].p + parsed[params.position.line][thistoken - 1].c, + ); const macroname = doc.getText(macrorange).slice(3); // Get the macro signature from the server @@ -230,7 +294,7 @@ export async function onSignatureHelp(params: SignatureHelpParams): Promise 0) { signatureHelpDocumentationCache = { type: "macro", doc: { kind: MarkupKind.Markdown, - value: markdownifyExpansion(exprespdata.data.result.content.expansion) - } + value: markdownifyExpansion(exprespdata.data.result.content.expansion), + }, }; sig.documentation = signatureHelpDocumentationCache.doc; } @@ -269,21 +333,25 @@ export async function onSignatureHelp(params: SignatureHelpParams): Promise 0) { // We got data back if (member == "%New") { - if (respdata.data.result.content.length == 2 && respdata.data.result.content[1].Origin != "%Library.RegisteredObject") { + if ( + respdata.data.result.content.length == 2 && + respdata.data.result.content[1].Origin != "%Library.RegisteredObject" + ) { // %OnNew has been overridden for this class const sig: SignatureInformation = { label: beautifyFormalSpec(respdata.data.result.content[1].FormalSpec), - parameters: [] + parameters: [], }; if (settings.signaturehelp.documentation) { signatureHelpDocumentationCache = { type: "method", doc: { kind: MarkupKind.Markdown, - value: documaticHtmlToMarkdown(respdata.data.result.content[ - respdata.data.result.content[1].Description.trim().length ? 1 : 0 - ].Description) - } + value: documaticHtmlToMarkdown( + respdata.data.result.content[respdata.data.result.content[1].Description.trim().length ? 1 : 0] + .Description, + ), + }, }; sig.documentation = signatureHelpDocumentationCache.doc; } @@ -332,7 +409,7 @@ export async function onSignatureHelp(params: SignatureHelpParams): PromiseParent = ? AND parent->Name = ?"; } if (stubarr[2] === "q") { // This is a method generated from a query - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] === "a") { // This is a method generated from a property - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] === "n") { // This is a method generated from a constraint - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubquery !== "") { const stubrespdata = await makeRESTRequest("POST", 1, "/action/query", server, { query: stubquery, - parameters: [stubarr[1], membercontext.baseclass, stubarr[0]] + parameters: [stubarr[1], membercontext.baseclass, stubarr[0]], }); if (Array.isArray(stubrespdata?.data?.result?.content) && stubrespdata.data.result.content.length > 0) { // We got data back @@ -375,15 +456,15 @@ export async function onSignatureHelp(params: SignatureHelpParams): Promise 0) { signatureHelpDocumentationCache = { type: "macro", doc: { kind: MarkupKind.Markdown, - value: markdownifyExpansion(exprespdata.data.result.content.expansion) - } + value: markdownifyExpansion(exprespdata.data.result.content.expansion), + }, }; sig.documentation = signatureHelpDocumentationCache.doc; } @@ -488,21 +581,25 @@ export async function onSignatureHelp(params: SignatureHelpParams): Promise 0) { // We got data back if (member == "%New") { - if (respdata.data.result.content.length == 2 && respdata.data.result.content[1].Origin != "%Library.RegisteredObject") { + if ( + respdata.data.result.content.length == 2 && + respdata.data.result.content[1].Origin != "%Library.RegisteredObject" + ) { // %OnNew has been overridden for this class const sig: SignatureInformation = { label: beautifyFormalSpec(respdata.data.result.content[1].FormalSpec), - parameters: [] + parameters: [], }; if (settings.signaturehelp.documentation) { signatureHelpDocumentationCache = { type: "method", doc: { kind: MarkupKind.Markdown, - value: documaticHtmlToMarkdown(respdata.data.result.content[ - respdata.data.result.content[1].Description.trim().length ? 1 : 0 - ].Description) - } + value: documaticHtmlToMarkdown( + respdata.data.result.content[respdata.data.result.content[1].Description.trim().length ? 1 : 0] + .Description, + ), + }, }; sig.documentation = signatureHelpDocumentationCache.doc; } @@ -560,24 +666,28 @@ export async function onSignatureHelp(params: SignatureHelpParams): PromiseParent = ? AND parent->Name = ?"; } if (stubarr[2] === "q") { // This is a method generated from a query - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] === "a") { // This is a method generated from a property - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] === "n") { // This is a method generated from a constraint - stubquery = "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT Description, FormalSpec, ReturnType FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubquery !== "") { const stubrespdata = await makeRESTRequest("POST", 1, "/action/query", server, { query: stubquery, - parameters: [stubarr[1], membercontext.baseclass, stubarr[0]] + parameters: [stubarr[1], membercontext.baseclass, stubarr[0]], }); if (Array.isArray(stubrespdata?.data?.result?.content) && stubrespdata.data.result.content.length > 0) { // We got data back @@ -589,15 +699,15 @@ export async function onSignatureHelp(params: SignatureHelpParams): Promise { const doc = documents.get(params.textDocument.uri); - if (doc === undefined) {return null;} + if (doc === undefined) { + return null; + } const parsed = await getParsedDocument(params.textDocument.uri); - if (parsed === undefined) {return null;} + if (parsed === undefined) { + return null; + } const server: ServerSpec = await getServerSpec(params.textDocument.uri); let cls: string | null = null; @@ -34,41 +41,43 @@ export async function onPrepare(params: TypeHierarchyPrepareParams): Promise= symbolstart && params.position.character <= symbolend) { // We found the right symbol in the line if ( - ((parsed[params.position.line][i].l == ld.cls_langindex && parsed[params.position.line][i].s == ld.cls_clsname_attrindex) || - (parsed[params.position.line][i].l == ld.cos_langindex && parsed[params.position.line][i].s == ld.cos_clsname_attrindex)) && - doc.getText(Range.create( - Position.create(params.position.line,0), - Position.create(params.position.line,6) - )).toLowerCase() !== "import" + ((parsed[params.position.line][i].l == ld.cls_langindex && + parsed[params.position.line][i].s == ld.cls_clsname_attrindex) || + (parsed[params.position.line][i].l == ld.cos_langindex && + parsed[params.position.line][i].s == ld.cos_clsname_attrindex)) && + doc + .getText(Range.create(Position.create(params.position.line, 0), Position.create(params.position.line, 6))) + .toLowerCase() !== "import" ) { // This is a class name - + // Get the full text of the selection - const wordrange = findFullRange(params.position.line,parsed,i,symbolstart,symbolend); + const wordrange = findFullRange(params.position.line, parsed, i, symbolstart, symbolend); let word = doc.getText(wordrange); if (word.charAt(0) === ".") { // This might be $SYSTEM.ClassName - const prevseven = doc.getText(Range.create( - Position.create(params.position.line,wordrange.start.character-7), - Position.create(params.position.line,wordrange.start.character) - )); + const prevseven = doc.getText( + Range.create( + Position.create(params.position.line, wordrange.start.character - 7), + Position.create(params.position.line, wordrange.start.character), + ), + ); if (prevseven.toUpperCase() === "$SYSTEM") { // This is $SYSTEM.ClassName word = "%SYSTEM" + word; - } - else { + } else { // This classname is invalid break; } } if (word.charAt(0) === '"') { // This classname is delimited with ", so strip them - word = word.slice(1,-1); + word = word.slice(1, -1); } cls = word; } @@ -82,19 +91,17 @@ export async function onPrepare(params: TypeHierarchyPrepareParams): Promiseitem.data,querydata); + const respdata = await makeRESTRequest("POST", 1, "/action/query", item.data, querydata); if (respdata !== undefined && respdata.data.result.content !== undefined && respdata.data.result.content.length > 0) { const classes: string[] = respdata.data.result.content.map((clsobj) => clsobj.Name); - const uris: string[] = await connection.sendRequest("intersystems/uri/forTypeHierarchyClasses",classes); + const uris: string[] = await connection.sendRequest("intersystems/uri/forTypeHierarchyClasses", classes); for (let i = 0; i < classes.length; i++) { result.push({ name: classes[i], kind: SymbolKind.Class, - range: Range.create(Position.create(0,0),Position.create(0,0)), - selectionRange: Range.create(Position.create(0,0),Position.create(0,0)), + range: Range.create(Position.create(0, 0), Position.create(0, 0)), + selectionRange: Range.create(Position.create(0, 0), Position.create(0, 0)), uri: uris[i], - data: item.data + data: item.data, }); } } @@ -170,7 +177,7 @@ async function classesToTHItems(item: TypeHierarchyItem, query: string): Promise export async function onSubtypes(params: TypeHierarchySubtypesParams): Promise { return classesToTHItems( params.item, - "SELECT Name FROM %Dictionary.CompiledClass WHERE ? %INLIST $LISTFROMSTRING(Super)" + "SELECT Name FROM %Dictionary.CompiledClass WHERE ? %INLIST $LISTFROMSTRING(Super)", ); } @@ -180,6 +187,6 @@ export async function onSubtypes(params: TypeHierarchySubtypesParams): Promise { return classesToTHItems( params.item, - "SELECT Name FROM %Dictionary.CompiledClass WHERE Name %INLIST (SELECT $LISTFROMSTRING(Super) FROM %Dictionary.CompiledClass WHERE Name = ?)" + "SELECT Name FROM %Dictionary.CompiledClass WHERE Name %INLIST (SELECT $LISTFROMSTRING(Super) FROM %Dictionary.CompiledClass WHERE Name = ?)", ); } diff --git a/server/src/server.ts b/server/src/server.ts index f659c0f..2e11a0e 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,8 +1,8 @@ -import { DidChangeConfigurationNotification, TextDocumentSyncKind, CodeActionKind } from 'vscode-languageserver/node'; -import { URI } from 'vscode-uri'; +import { DidChangeConfigurationNotification, TextDocumentSyncKind, CodeActionKind } from "vscode-languageserver/node"; +import { URI } from "vscode-uri"; -import { onPrepare, onSubtypes, onSupertypes } from './providers/typeHierarchy'; -import { evaluatableExpression } from './providers/evaluatableExpression'; +import { onPrepare, onSubtypes, onSupertypes } from "./providers/typeHierarchy"; +import { evaluatableExpression } from "./providers/evaluatableExpression"; import { addImportPackage, addMethod, @@ -12,26 +12,33 @@ import { listParameterTypes, onCodeAction, onCodeActionResolve, - validateOverrideCursor -} from './providers/refactoring'; -import { onDocumentLinkResolve, onDocumentLinks } from './providers/documentLink'; -import { onDeclaration } from './providers/declaration'; -import { onTypeDefinition } from './providers/typeDefinition'; -import { onPrepareRename, onRenameRequest } from './providers/rename'; -import { onFoldingRanges } from './providers/foldingRange'; -import { onDocumentSymbol } from './providers/documentSymbol'; -import { onDefinition } from './providers/definition'; -import { onHover } from './providers/hover'; -import { onCompletion, onCompletionResolve, schemaCaches } from './providers/completion'; -import { onSignatureHelp } from './providers/signatureHelp'; -import { onDocumentFormatting, onDocumentRangeFormatting } from './providers/formatting'; -import { onDiagnostics } from './providers/diagnostic'; -import { onSemanticTokens, onSemanticTokensDelta } from './providers/semanticTokens'; - -import { LanguageServerConfiguration, ServerSpec } from './utils/types'; -import { connection, documents, languageServerSettings, parsedDocuments, serverSpecs, tokenBuilders } from './utils/variables'; -import { parseDocument, getLegend } from './parse/parse'; -import { isolateEmbeddedLanguage, languageAtPosition } from './providers/requestForwarding'; + validateOverrideCursor, +} from "./providers/refactoring"; +import { onDocumentLinkResolve, onDocumentLinks } from "./providers/documentLink"; +import { onDeclaration } from "./providers/declaration"; +import { onTypeDefinition } from "./providers/typeDefinition"; +import { onPrepareRename, onRenameRequest } from "./providers/rename"; +import { onFoldingRanges } from "./providers/foldingRange"; +import { onDocumentSymbol } from "./providers/documentSymbol"; +import { onDefinition } from "./providers/definition"; +import { onHover } from "./providers/hover"; +import { onCompletion, onCompletionResolve, schemaCaches } from "./providers/completion"; +import { onSignatureHelp } from "./providers/signatureHelp"; +import { onDocumentFormatting, onDocumentRangeFormatting } from "./providers/formatting"; +import { onDiagnostics } from "./providers/diagnostic"; +import { onSemanticTokens, onSemanticTokensDelta } from "./providers/semanticTokens"; + +import { LanguageServerConfiguration, ServerSpec } from "./utils/types"; +import { + connection, + documents, + languageServerSettings, + parsedDocuments, + serverSpecs, + tokenBuilders, +} from "./utils/variables"; +import { parseDocument, getLegend } from "./parse/parse"; +import { isolateEmbeddedLanguage, languageAtPosition } from "./providers/requestForwarding"; connection.onInitialize(() => { return { @@ -39,51 +46,50 @@ connection.onInitialize(() => { textDocumentSync: TextDocumentSyncKind.Full, completionProvider: { resolveProvider: true, - triggerCharacters: [".","$","("," ","<",'"',"#","^"] + triggerCharacters: [".", "$", "(", " ", "<", '"', "#", "^"], }, hoverProvider: true, definitionProvider: true, signatureHelpProvider: { - triggerCharacters: ["(",","], - retriggerCharacters: [","] + triggerCharacters: ["(", ","], + retriggerCharacters: [","], }, documentFormattingProvider: true, documentRangeFormattingProvider: true, semanticTokensProvider: { legend: getLegend(), full: { - delta: true - } + delta: true, + }, }, documentSymbolProvider: true, foldingRangeProvider: true, renameProvider: { - prepareProvider: true + prepareProvider: true, }, typeDefinitionProvider: true, declarationProvider: true, codeActionProvider: { - codeActionKinds: [ - CodeActionKind.Refactor, - CodeActionKind.QuickFix - ], - resolveProvider: true + codeActionKinds: [CodeActionKind.Refactor, CodeActionKind.QuickFix], + resolveProvider: true, }, documentLinkProvider: { - resolveProvider: true + resolveProvider: true, }, typeHierarchyProvider: true, diagnosticProvider: { interFileDependencies: false, - workspaceDiagnostics: false - } - } + workspaceDiagnostics: false, + }, + }, }; }); connection.onInitialized(() => { // Register for relevant configuration changes. - connection.client.register(DidChangeConfigurationNotification.type, {section: ["intersystems.language-server","intersystems.servers","objectscript.conn"]}); + connection.client.register(DidChangeConfigurationNotification.type, { + section: ["intersystems.language-server", "intersystems.servers", "objectscript.conn"], + }); }); connection.onExit(() => { @@ -100,7 +106,9 @@ connection.onDidChangeConfiguration(async () => { // This is done here because it's more efficient to pack everything into one request to the client const uris: string[] = documents.keys(); const configs: LanguageServerConfiguration[] = await connection.workspace.getConfiguration( - uris.map((uri) => { return { scopeUri: uri, section: "intersystems.language-server" }; }) + uris.map((uri) => { + return { scopeUri: uri, section: "intersystems.language-server" }; + }), ); configs.forEach((config, index) => languageServerSettings.set(uris[index], config)); @@ -108,27 +116,27 @@ connection.onDidChangeConfiguration(async () => { connection.languages.diagnostics.refresh(); }); -documents.onDidClose(e => { +documents.onDidClose((e) => { parsedDocuments.delete(e.document.uri); tokenBuilders.delete(e.document.uri); serverSpecs.delete(e.document.uri); languageServerSettings.delete(e.document.uri); - connection.sendDiagnostics({uri: e.document.uri, diagnostics: []}); + connection.sendDiagnostics({ uri: e.document.uri, diagnostics: [] }); }); // The content of a text document has changed. This event is emitted // when the text document first opened or when its content has changed. -documents.onDidChangeContent(async change => { +documents.onDidChangeContent(async (change) => { // Clear the parsedDocuments value so we know to wait for an update elsewhere - parsedDocuments.set(change.document.uri,undefined); + parsedDocuments.set(change.document.uri, undefined); const path = URI.parse(change.document.uri).path; parsedDocuments.set( change.document.uri, parseDocument( change.document.languageId, - path.slice(path.lastIndexOf(".")+1).toLowerCase(), - change.document.getText() - ).compressedlinearray + path.slice(path.lastIndexOf(".") + 1).toLowerCase(), + change.document.getText(), + ).compressedlinearray, ); }); @@ -150,31 +158,29 @@ connection.languages.semanticTokens.on(onSemanticTokens); connection.languages.semanticTokens.onDelta(onSemanticTokensDelta); -connection.onNotification("intersystems/server/passwordChange", - (serverName: string) => { - const invalid: string[] = []; - for (const [uri, server] of serverSpecs.entries()) { - if (server.serverName == serverName) { - invalid.push(uri); - } - } - for (const uri of invalid) { - serverSpecs.delete(uri); +connection.onNotification("intersystems/server/passwordChange", (serverName: string) => { + const invalid: string[] = []; + for (const [uri, server] of serverSpecs.entries()) { + if (server.serverName == serverName) { + invalid.push(uri); } - let toRemove: ServerSpec | undefined = undefined; - for (const server of schemaCaches.keys()) { - if (server.serverName == serverName) { - toRemove = server; - break; - } - } - if (toRemove !== undefined) { - schemaCaches.delete(toRemove); + } + for (const uri of invalid) { + serverSpecs.delete(uri); + } + let toRemove: ServerSpec | undefined = undefined; + for (const server of schemaCaches.keys()) { + if (server.serverName == serverName) { + toRemove = server; + break; } } -); + if (toRemove !== undefined) { + schemaCaches.delete(toRemove); + } +}); -connection.onNotification("intersystems/server/connectionChange",() => { +connection.onNotification("intersystems/server/connectionChange", () => { // Clear all cached server connection info serverSpecs.clear(); schemaCaches.clear(); @@ -192,21 +198,21 @@ connection.onTypeDefinition(onTypeDefinition); connection.onDeclaration(onDeclaration); -connection.onRequest("intersystems/debugger/evaluatableExpression",evaluatableExpression); +connection.onRequest("intersystems/debugger/evaluatableExpression", evaluatableExpression); -connection.onRequest("intersystems/refactor/listOverridableMembers",listOverridableMembers); +connection.onRequest("intersystems/refactor/listOverridableMembers", listOverridableMembers); -connection.onRequest("intersystems/refactor/addOverridableMembers",addOverridableMembers); +connection.onRequest("intersystems/refactor/addOverridableMembers", addOverridableMembers); -connection.onRequest("intersystems/refactor/validateOverrideCursor",validateOverrideCursor); +connection.onRequest("intersystems/refactor/validateOverrideCursor", validateOverrideCursor); -connection.onRequest("intersystems/refactor/listParameterTypes",listParameterTypes); +connection.onRequest("intersystems/refactor/listParameterTypes", listParameterTypes); -connection.onRequest("intersystems/refactor/listImportPackages",listImportPackages); +connection.onRequest("intersystems/refactor/listImportPackages", listImportPackages); -connection.onRequest("intersystems/refactor/addImportPackage",addImportPackage); +connection.onRequest("intersystems/refactor/addImportPackage", addImportPackage); -connection.onRequest("intersystems/refactor/addMethod",addMethod); +connection.onRequest("intersystems/refactor/addMethod", addMethod); connection.onCodeAction(onCodeAction); @@ -222,9 +228,9 @@ connection.languages.typeHierarchy.onSubtypes(onSubtypes); connection.languages.typeHierarchy.onSupertypes(onSupertypes); -connection.onRequest("intersystems/embedded/languageAtPosition",languageAtPosition); +connection.onRequest("intersystems/embedded/languageAtPosition", languageAtPosition); -connection.onRequest("intersystems/embedded/isolateEmbeddedLanguage",isolateEmbeddedLanguage); +connection.onRequest("intersystems/embedded/isolateEmbeddedLanguage", isolateEmbeddedLanguage); connection.languages.diagnostics.on(onDiagnostics); diff --git a/server/src/utils/functions.ts b/server/src/utils/functions.ts index b57f3b6..a1f7e8c 100644 --- a/server/src/utils/functions.ts +++ b/server/src/utils/functions.ts @@ -1,11 +1,29 @@ -import { MarkupContent, MarkupKind, Position, Range } from 'vscode-languageserver'; -import { TextDocument } from 'vscode-languageserver-textdocument'; -import { URI } from 'vscode-uri'; -import { parse } from 'node-html-parser'; - -import { ServerSpec, StudioOpenDialogFile, QueryData, compressedline, CommandDoc, LanguageServerConfiguration, MacroContext, DimResult, PossibleClasses, ClassMemberContext } from './types'; -import { parsedDocuments, connection, serverSpecs, languageServerSettings, documents, classMemberTypes } from './variables'; -import * as ld from './languageDefinitions'; +import { MarkupContent, MarkupKind, Position, Range } from "vscode-languageserver"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import { URI } from "vscode-uri"; +import { parse } from "node-html-parser"; + +import { + ServerSpec, + StudioOpenDialogFile, + QueryData, + compressedline, + CommandDoc, + LanguageServerConfiguration, + MacroContext, + DimResult, + PossibleClasses, + ClassMemberContext, +} from "./types"; +import { + parsedDocuments, + connection, + serverSpecs, + languageServerSettings, + documents, + classMemberTypes, +} from "./variables"; +import * as ld from "./languageDefinitions"; import commands from "../documentation/commands.json"; import structuredSystemVariables from "../documentation/structuredSystemVariables.json"; @@ -16,7 +34,7 @@ import systemVariables from "../documentation/systemVariables.json"; import { default as TurndownService } from "turndown"; const turndown = new TurndownService({ codeBlockStyle: "fenced", - blankReplacement: (content, node: HTMLElement) => node.nodeName == 'SPAN' ? node.outerHTML : '' + blankReplacement: (content, node: HTMLElement) => (node.nodeName == "SPAN" ? node.outerHTML : ""), }); turndown.remove("style"); turndown.keep(["span", "table", "tr", "td", "u"]); @@ -24,9 +42,14 @@ turndown.addRule("pre", { filter: "pre", replacement: function (content: string, node: HTMLElement) { let lang = ""; - content = content.replace(/\\\\/g, "\\").replace(/\\\[/g, "[").replace(/\\\]/g, "]") - .replace(/&/g, "&").replace(/&/g, "&") - .replace(/</g, "<").replace(/>/g, ">"); + content = content + .replace(/\\\\/g, "\\") + .replace(/\\\[/g, "[") + .replace(/\\\]/g, "]") + .replace(/&/g, "&") + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">"); const attrVal = node.getAttribute("LANGUAGE"); if (attrVal == null) { try { @@ -34,9 +57,10 @@ turndown.addRule("pre", { if (typeof obj == "object") { lang = "json"; } - } catch { /* empty */ } - } - else { + } catch { + /* empty */ + } + } else { switch (attrVal.split("!").shift().toUpperCase()) { case "OBJECTSCRIPT": case "COS": @@ -69,7 +93,7 @@ turndown.addRule("pre", { } return "\n```" + lang + "\n" + content + "\n```\n"; - } + }, }); turndown.addRule("documaticLinks", { filter: ["class", "method", "property", "query", "parameter"] as any, @@ -77,7 +101,7 @@ turndown.addRule("documaticLinks", { const methodOrQuery = ["METHOD", "QUERY"].includes(node.nodeName); const wrapper = node.nodeName == "CLASS" ? "***" : "**"; return `${wrapper}${methodOrQuery ? content.replace(/\(\)/g, "") : content}${methodOrQuery ? "()" : ""}${wrapper}`; - } + }, }); turndown.addRule("documaticArgs", { filter: "args" as any, @@ -85,7 +109,7 @@ turndown.addRule("documaticArgs", { if (node.children.length > 0) { return `\n#### Arguments:\n${content}\n`; } - } + }, }); turndown.addRule("documaticArg", { filter: "arg" as any, @@ -94,34 +118,55 @@ turndown.addRule("documaticArg", { if (attrVal !== null) { return `\n- \`${attrVal}\` - ${content}`; } - } + }, }); turndown.addRule("documaticReturn", { filter: "return" as any, replacement: function (content: string) { return `\n#### Return Value:\n${content}\n`; - } + }, }); /** * Determine if the command at position (line,token) in doc is a "HALT" or "HANG". - * + * * @param doc The TextDocument that the command is in. * @param parsed The tokenized representation of doc. * @param line The line that the command is in. * @param token The offset of the command in the line. */ -export function haltOrHang(doc: TextDocument, parsed: compressedline[], line: number, token: number): CommandDoc | undefined { +export function haltOrHang( + doc: TextDocument, + parsed: compressedline[], + line: number, + token: number, +): CommandDoc | undefined { if (parsed[line][token + 1] === undefined) { // This is a "halt" return commands.find((el) => el.label === "HALT"); - } - else { - let nexttokentext = doc.getText(Range.create(Position.create(line, parsed[line][token + 1].p), Position.create(line, parsed[line][token + 1].p + parsed[line][token + 1].c))); + } else { + let nexttokentext = doc.getText( + Range.create( + Position.create(line, parsed[line][token + 1].p), + Position.create(line, parsed[line][token + 1].p + parsed[line][token + 1].c), + ), + ); if (nexttokentext === ":") { // There's a postconditional - nexttokentext = doc.getText(Range.create(Position.create(line, parsed[line][token + 2].p), Position.create(line, parsed[line][token + 2].p + parsed[line][token + 2].c))); - const restofline = doc.getText(Range.create(Position.create(line, parsed[line][token + 2].p + parsed[line][token + 2].c), Position.create(line + 1, 0))).trim(); + nexttokentext = doc.getText( + Range.create( + Position.create(line, parsed[line][token + 2].p), + Position.create(line, parsed[line][token + 2].p + parsed[line][token + 2].c), + ), + ); + const restofline = doc + .getText( + Range.create( + Position.create(line, parsed[line][token + 2].p + parsed[line][token + 2].c), + Position.create(line + 1, 0), + ), + ) + .trim(); if (nexttokentext === "(") { let opencount = 1; let closecount = 0; @@ -129,8 +174,7 @@ export function haltOrHang(doc: TextDocument, parsed: compressedline[], line: nu for (let i = 0; i < restofline.length; i++) { if (restofline.charAt(i) === "(") { opencount++; - } - else if (restofline.charAt(i) === ")") { + } else if (restofline.charAt(i) === ")") { closecount++; } if (opencount === closecount) { @@ -141,30 +185,26 @@ export function haltOrHang(doc: TextDocument, parsed: compressedline[], line: nu if (lastclose === restofline.length - 1) { // This is a "halt" return commands.find((el) => el.label === "HALT"); - } - else { + } else { // This is a "hang" return commands.find((el) => el.label === "HANG"); } - } - else { + } else { const restoflinearr = restofline.split(" "); if (restoflinearr.length === 1) { // This is a "halt" return commands.find((el) => el.label === "HALT"); - } - else { + } else { // This is a "hang" return commands.find((el) => el.label === "HANG"); } } - } - else { + } else { // This is a "hang" return commands.find((el) => el.label === "HANG"); } } -}; +} /** * Get the configuration parameters from the cache or the client if the cache is empty. @@ -172,35 +212,46 @@ export function haltOrHang(doc: TextDocument, parsed: compressedline[], line: nu export async function getLanguageServerSettings(uri: string): Promise { const settings = languageServerSettings.get(uri); if (settings == undefined) { - const newsettings: LanguageServerConfiguration = await connection.workspace.getConfiguration({ scopeUri: uri, section: "intersystems.language-server" }); + const newsettings: LanguageServerConfiguration = await connection.workspace.getConfiguration({ + scopeUri: uri, + section: "intersystems.language-server", + }); languageServerSettings.set(uri, newsettings); return newsettings; - } - else { + } else { return settings; } -}; +} /** * Find the full range of this word. - * + * * @param line The line that the word is in. * @param parsed The tokenized representation of the document. * @param lineidx The position of the token in the line. * @param symbolstart The start of the selected token. * @param symbolend The end of the selected token. */ -export function findFullRange(line: number, parsed: compressedline[], lineidx: number, symbolstart: number, symbolend: number): Range { +export function findFullRange( + line: number, + parsed: compressedline[], + lineidx: number, + symbolstart: number, + symbolend: number, +): Range { let rangestart: number = symbolstart; let rangeend: number = symbolend; // Scan backwards on the line to see where the selection starts let newidx = lineidx; while (true) { newidx--; - if ((newidx == -1) || (parsed[line][newidx].l != parsed[line][lineidx].l) || (parsed[line][newidx].s != parsed[line][lineidx].s)) { + if ( + newidx == -1 || + parsed[line][newidx].l != parsed[line][lineidx].l || + parsed[line][newidx].s != parsed[line][lineidx].s + ) { break; - } - else if (parsed[line][newidx].p + parsed[line][newidx].c !== parsed[line][newidx + 1].p) { + } else if (parsed[line][newidx].p + parsed[line][newidx].c !== parsed[line][newidx + 1].p) { // There's whitespace in between the next token and this one break; } @@ -210,21 +261,24 @@ export function findFullRange(line: number, parsed: compressedline[], lineidx: n newidx = lineidx; while (true) { newidx++; - if ((parsed[line][newidx] === undefined) || (parsed[line][newidx].l != parsed[line][lineidx].l) || (parsed[line][newidx].s != parsed[line][lineidx].s)) { + if ( + parsed[line][newidx] === undefined || + parsed[line][newidx].l != parsed[line][lineidx].l || + parsed[line][newidx].s != parsed[line][lineidx].s + ) { break; - } - else if (parsed[line][newidx].p !== parsed[line][newidx - 1].p + parsed[line][newidx - 1].c) { + } else if (parsed[line][newidx].p !== parsed[line][newidx - 1].p + parsed[line][newidx - 1].c) { // There's whitespace in between the previous token and this one break; } rangeend = parsed[line][newidx].p + parsed[line][newidx].c; } return Range.create(Position.create(line, rangestart), Position.create(line, rangeend)); -}; +} /** * Get the context of the method/routine that a macro is in. - * + * * @param doc The TextDocument that the macro is in. * @param parsed The tokenized representation of doc. * @param line The line that the macro is in. @@ -237,7 +291,7 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin includes: [], includegenerators: [], imports: [], - mode: "" + mode: "", }; let sqlIsClassQuery = false; if (doc.languageId == "objectscript-class") { @@ -245,8 +299,7 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin for (let i = 0; i < parsed.length; i++) { if (parsed[i].length === 0) { continue; - } - else if (parsed[i][0].l == ld.cls_langindex && parsed[i][0].s == ld.cls_keyword_attrindex) { + } else if (parsed[i][0].l == ld.cls_langindex && parsed[i][0].s == ld.cls_keyword_attrindex) { // This line starts with a UDL keyword const keyword = doc.getText(Range.create(i, parsed[i][0].p, i, parsed[i][0].p + parsed[i][0].c)); @@ -259,31 +312,29 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin if (result.superclasses.length == 0) { result.superclasses.push(""); } - result.superclasses[result.superclasses.length - 1] += doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)); - } - else { + result.superclasses[result.superclasses.length - 1] += doc.getText( + Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c), + ); + } else { result.docname += doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)); } - } - else if ( + } else if ( parsed[i][j].l == ld.cls_langindex && parsed[i][j].s == ld.cls_keyword_attrindex && - doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)).toLowerCase() == "extends" + doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)).toLowerCase() == + "extends" ) { seenextends = true; - } - else { + } else { // This is a delimiter if (j == parsed[i].length - 1) { // This is the trailing ")" break; - } - else { + } else { if (parsed[i][j + 1].l == ld.cls_langindex && parsed[i][j + 1].s == ld.cls_clsname_attrindex) { // This is a "," or the opening "(" result.superclasses.push(""); - } - else { + } else { // This is the trailing ")" break; } @@ -291,18 +342,33 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin } } break; - } - else if (keyword.toLowerCase() === "include" && parsed[i].length > 1) { - result.includes = doc.getText(Range.create(i, parsed[i][1].p, i, parsed[i][parsed[i].length - 1].p + parsed[i][parsed[i].length - 1].c)) - .replace("(", "").replace(")", "").replace(/\s+/g, "").split(","); - } - else if (keyword.toLowerCase() === "includegenerator" && parsed[i].length > 1) { - result.includegenerators = doc.getText(Range.create(i, parsed[i][1].p, i, parsed[i][parsed[i].length - 1].p + parsed[i][parsed[i].length - 1].c)) - .replace("(", "").replace(")", "").replace(/\s+/g, "").split(","); - } - else if (keyword.toLowerCase() === "import" && parsed[i].length > 1) { - result.imports = doc.getText(Range.create(i, parsed[i][1].p, i, parsed[i][parsed[i].length - 1].p + parsed[i][parsed[i].length - 1].c)) - .replace("(", "").replace(")", "").replace(/\s+/g, "").split(","); + } else if (keyword.toLowerCase() === "include" && parsed[i].length > 1) { + result.includes = doc + .getText( + Range.create(i, parsed[i][1].p, i, parsed[i][parsed[i].length - 1].p + parsed[i][parsed[i].length - 1].c), + ) + .replace("(", "") + .replace(")", "") + .replace(/\s+/g, "") + .split(","); + } else if (keyword.toLowerCase() === "includegenerator" && parsed[i].length > 1) { + result.includegenerators = doc + .getText( + Range.create(i, parsed[i][1].p, i, parsed[i][parsed[i].length - 1].p + parsed[i][parsed[i].length - 1].c), + ) + .replace("(", "") + .replace(")", "") + .replace(/\s+/g, "") + .split(","); + } else if (keyword.toLowerCase() === "import" && parsed[i].length > 1) { + result.imports = doc + .getText( + Range.create(i, parsed[i][1].p, i, parsed[i][parsed[i].length - 1].p + parsed[i][parsed[i].length - 1].c), + ) + .replace("(", "") + .replace(")", "") + .replace(/\s+/g, "") + .split(","); } } } @@ -312,33 +378,53 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin } if (parsed[k][0].l == ld.cls_langindex && parsed[k][0].s == ld.cls_keyword_attrindex) { // This is the definition for the method that the macro is in - if (sql && doc.getText(Range.create( - k, parsed[k][0].p, - k, parsed[k][0].p + parsed[k][0].c - )).toLowerCase() == "Query") sqlIsClassQuery = true; if ( - parsed[k][parsed[k].length - 1].l == ld.cls_langindex && parsed[k][parsed[k].length - 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - k, parsed[k][parsed[k].length - 1].p, - k, parsed[k][parsed[k].length - 1].p + parsed[k][parsed[k].length - 1].c - )) === "(" + sql && + doc.getText(Range.create(k, parsed[k][0].p, k, parsed[k][0].p + parsed[k][0].c)).toLowerCase() == "Query" + ) + sqlIsClassQuery = true; + if ( + parsed[k][parsed[k].length - 1].l == ld.cls_langindex && + parsed[k][parsed[k].length - 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + k, + parsed[k][parsed[k].length - 1].p, + k, + parsed[k][parsed[k].length - 1].p + parsed[k][parsed[k].length - 1].c, + ), + ) === "(" ) { // This is a multi-line method definition for (let mline = k + 1; mline < parsed.length; mline++) { if ( - parsed[mline][parsed[mline].length - 1].l == ld.cls_langindex && parsed[mline][parsed[mline].length - 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - mline, parsed[mline][parsed[mline].length - 1].p, - mline, parsed[mline][parsed[mline].length - 1].p + parsed[mline][parsed[mline].length - 1].c - )) !== "," + parsed[mline][parsed[mline].length - 1].l == ld.cls_langindex && + parsed[mline][parsed[mline].length - 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + mline, + parsed[mline][parsed[mline].length - 1].p, + mline, + parsed[mline][parsed[mline].length - 1].p + parsed[mline][parsed[mline].length - 1].c, + ), + ) !== "," ) { // We've passed the argument lines so look for the CodeMode keyword on this line for (let l = 1; l < parsed[mline].length; l++) { if (parsed[mline][l].l == ld.cls_langindex && parsed[mline][l].s == ld.cls_keyword_attrindex) { - const kw = doc.getText(Range.create(mline, parsed[mline][l].p, mline, parsed[mline][l].p + parsed[mline][l].c)); + const kw = doc.getText( + Range.create(mline, parsed[mline][l].p, mline, parsed[mline][l].p + parsed[mline][l].c), + ); if (kw.toLowerCase() == "codemode") { // The CodeMode keyword is set - const kwval = doc.getText(Range.create(mline, parsed[mline][l + 2].p, mline, parsed[mline][l + 2].p + parsed[mline][l + 2].c)); + const kwval = doc.getText( + Range.create( + mline, + parsed[mline][l + 2].p, + mline, + parsed[mline][l + 2].p + parsed[mline][l + 2].c, + ), + ); if (kwval.toLowerCase() == "generator" || kwval.toLowerCase() == "objectgenerator") { result.mode = "generator"; } @@ -349,15 +435,16 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin break; } } - } - else { + } else { // This is a single-line method definition so look for the CodeMode keyword on this line for (let l = 1; l < parsed[k].length; l++) { if (parsed[k][l].l == ld.cls_langindex && parsed[k][l].s == ld.cls_keyword_attrindex) { const kw = doc.getText(Range.create(k, parsed[k][l].p, k, parsed[k][l].p + parsed[k][l].c)); if (kw.toLowerCase() == "codemode") { // The CodeMode keyword is set - const kwval = doc.getText(Range.create(k, parsed[k][l + 2].p, k, parsed[k][l + 2].p + parsed[k][l + 2].c)); + const kwval = doc.getText( + Range.create(k, parsed[k][l + 2].p, k, parsed[k][l + 2].p + parsed[k][l + 2].c), + ); if (kwval.toLowerCase() == "generator" || kwval.toLowerCase() == "objectgenerator") { result.mode = "generator"; } @@ -370,15 +457,14 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin } } result.docname += ".cls"; - } - else if (doc.languageId == "objectscript-csp") { + } else if (doc.languageId == "objectscript-csp") { // This is a CSP file // The docname doesn't matter as long as it's valid, // so use the URI path for convenience result.docname = URI.parse(doc.uri).path; - // Loop through the file until we hit 'line', + // Loop through the file until we hit 'line', // looking for CSP:CLASS HTML tags let inclasstag: boolean = false; let searchname: string = ""; @@ -387,50 +473,42 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin if ( parsed[i][j].l == ld.html_langindex && parsed[i][j].s == ld.html_tag_attrindex && - doc.getText(Range.create( - i, parsed[i][j].p, - i, parsed[i][j].p + parsed[i][j].c - )).toLowerCase() == "csp:class" + doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)).toLowerCase() == "csp:class" ) { // This is the start of a CSP:CLASS HTML element inclasstag = true; - } - else if ( + } else if ( inclasstag && parsed[i][j].l == ld.html_langindex && parsed[i][j].s == ld.html_delim_attrindex && - doc.getText(Range.create( - i, parsed[i][j].p, - i, parsed[i][j].p + parsed[i][j].c - )) == ">" + doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)) == ">" ) { // This is a tag close delimiter inclasstag = false; searchname = ""; - } - else if (inclasstag && parsed[i][j].l == ld.html_langindex && parsed[i][j].s == ld.html_name_attrindex) { + } else if (inclasstag && parsed[i][j].l == ld.html_langindex && parsed[i][j].s == ld.html_name_attrindex) { // This is an attribute of a CSP:CLASS HTML element - const nametext: string = doc.getText(Range.create( - i, parsed[i][j].p, - i, parsed[i][j].p + parsed[i][j].c - )).toLowerCase(); + const nametext: string = doc + .getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)) + .toLowerCase(); if (nametext == "super" || nametext == "import" || nametext == "includes") { searchname = nametext; } - } - else if (searchname !== "" && parsed[i][j].l == ld.html_langindex && parsed[i][j].s == ld.html_str_attrindex) { + } else if ( + searchname !== "" && + parsed[i][j].l == ld.html_langindex && + parsed[i][j].s == ld.html_str_attrindex + ) { // This is the value of the last attribute that we saw - const valuearr: string[] = doc.getText(Range.create( - i, parsed[i][j].p, - i, parsed[i][j].p + parsed[i][j].c - )).slice(1, -1).split(","); + const valuearr: string[] = doc + .getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)) + .slice(1, -1) + .split(","); if (searchname == "super") { result.superclasses = valuearr; - } - else if (searchname == "import") { + } else if (searchname == "import") { result.imports = valuearr; - } - else { + } else { result.includes = valuearr; } searchname = ""; @@ -444,13 +522,13 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin for (let i = 0; i < line; i++) { if (i === 0 && doc.languageId != "objectscript-class") { // Get the routine name from the ROUTINE header line - const fullline = doc.getText(Range.create(0, 0, 0, parsed[0][parsed[0].length - 1].p + parsed[0][parsed[0].length - 1].c)); + const fullline = doc.getText( + Range.create(0, 0, 0, parsed[0][parsed[0].length - 1].p + parsed[0][parsed[0].length - 1].c), + ); result.docname = fullline.split(" ")[1] + ".mac"; - } - else if (parsed[i].length === 0) { + } else if (parsed[i].length === 0) { continue; - } - else if (parsed[i][0].l == ld.cos_langindex && parsed[i][0].s == ld.cos_ppc_attrindex) { + } else if (parsed[i][0].l == ld.cos_langindex && parsed[i][0].s == ld.cos_ppc_attrindex) { // This is a preprocessor command const command = doc.getText(Range.create(i, parsed[i][0].p, i, parsed[i][1].p + parsed[i][1].c)); if (command.toLowerCase() == "#include") { @@ -461,11 +539,11 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin } return result; -}; +} /** * Parse a line of ObjectScript code that starts with #Dim and look to see if it contains `selector`. - * + * * @param doc The TextDocument that the line is in. * @param parsed The tokenized representation of `doc`. * @param line The line to parse. @@ -474,53 +552,67 @@ export function getMacroContext(doc: TextDocument, parsed: compressedline[], lin export function parseDimLine(doc: TextDocument, parsed: compressedline[], line: number, selector: string): DimResult { const result: DimResult = { founddim: false, - class: "" + class: "", }; for (let k = 2; k < parsed[line].length; k++) { if (parsed[line][k].s === ld.cos_localdec_attrindex || parsed[line][k].s === ld.cos_localvar_attrindex) { // This is a declared local variable or a public variable - const localvar = doc.getText(Range.create(Position.create(line, parsed[line][k].p), Position.create(line, parsed[line][k].p + parsed[line][k].c))); + const localvar = doc.getText( + Range.create( + Position.create(line, parsed[line][k].p), + Position.create(line, parsed[line][k].p + parsed[line][k].c), + ), + ); if (localvar === selector) { // This is the #Dim for the selector result.founddim = true; } - } - else if (parsed[line][k].s === ld.cos_command_attrindex) { + } else if (parsed[line][k].s === ld.cos_command_attrindex) { // This is the "As" keyword if (result.founddim) { - const nextword = doc.getText(Range.create(Position.create(line, parsed[line][k + 1].p), Position.create(line, parsed[line][k + 1].p + parsed[line][k + 1].c))); + const nextword = doc.getText( + Range.create( + Position.create(line, parsed[line][k + 1].p), + Position.create(line, parsed[line][k + 1].p + parsed[line][k + 1].c), + ), + ); if (parsed[line][k + 1].s === ld.cos_clsname_attrindex) { - result.class = doc.getText(findFullRange(line, parsed, k + 1, parsed[line][k + 1].p, parsed[line][k + 1].p + parsed[line][k + 1].c)); - } - else if (nextword.toLowerCase() === "list") { + result.class = doc.getText( + findFullRange(line, parsed, k + 1, parsed[line][k + 1].p, parsed[line][k + 1].p + parsed[line][k + 1].c), + ); + } else if (nextword.toLowerCase() === "list") { result.class = "%Collection.ListOfObj"; - } - else if (nextword.toLowerCase() === "array") { + } else if (nextword.toLowerCase() === "array") { result.class = "%Collection.ArrayOfObj"; } } break; - } - else if (parsed[line][k].s === ld.cos_oper_attrindex) { + } else if (parsed[line][k].s === ld.cos_oper_attrindex) { // This is the "=" operator break; } } return result; -}; +} /** * Get the list of all imported packages at this line of a document. - * + * * @param doc The TextDocument of the class to examine. * @param parsed The tokenized representation of doc. * @param line The line in the document that we need to resolve imports at. * @param server The server that this document is associated with. - * + * * The following optional parameter is only provided when called via `onDiagnostics()`: * @param inheritedpackages An array containing packages imported by superclasses of this class. */ -export async function getImports(doc: TextDocument, parsed: compressedline[], line: number, server: ServerSpec, inheritedpackages?: string[]): Promise { +export async function getImports( + doc: TextDocument, + parsed: compressedline[], + line: number, + server: ServerSpec, + inheritedpackages?: string[], +): Promise { let result: string[] = []; if (doc.languageId === "objectscript-class") { // Look for the "Import" keyword @@ -529,16 +621,23 @@ export async function getImports(doc: TextDocument, parsed: compressedline[], li for (let i = 0; i < parsed.length; i++) { if (parsed[i].length === 0) { continue; - } - else if (parsed[i][0].l == ld.cls_langindex && parsed[i][0].s == ld.cls_keyword_attrindex) { + } else if (parsed[i][0].l == ld.cls_langindex && parsed[i][0].s == ld.cls_keyword_attrindex) { // This line starts with a UDL keyword - const keyword = doc.getText(Range.create(Position.create(i, parsed[i][0].p), Position.create(i, parsed[i][0].p + parsed[i][0].c))).toLowerCase(); + const keyword = doc + .getText( + Range.create(Position.create(i, parsed[i][0].p), Position.create(i, parsed[i][0].p + parsed[i][0].c)), + ) + .toLowerCase(); if (keyword === "import" && parsed[i].length > 1) { - const codes = doc.getText(Range.create(Position.create(i, parsed[i][1].p), Position.create(i, parsed[i][parsed[i].length - 1].p + parsed[i][parsed[i].length - 1].c))); + const codes = doc.getText( + Range.create( + Position.create(i, parsed[i][1].p), + Position.create(i, parsed[i][parsed[i].length - 1].p + parsed[i][parsed[i].length - 1].c), + ), + ); result = codes.replace("(", "").replace(")", "").replace(/\s+/g, "").split(","); - } - else if (keyword === "class") { + } else if (keyword === "class") { // Add the current package if it's not explicitly imported clsname = doc.getText(findFullRange(i, parsed, 1, parsed[i][1].p, parsed[i][1].p + parsed[i][1].c)); if (!result.includes(clsname.slice(0, clsname.lastIndexOf(".")))) { @@ -546,11 +645,13 @@ export async function getImports(doc: TextDocument, parsed: compressedline[], li } for (let j = 1; j < parsed[i].length; j++) { if ( - parsed[i][j].l == ld.cls_langindex && parsed[i][j].s == ld.cls_keyword_attrindex && - doc.getText(Range.create( - Position.create(i, parsed[i][j].p), - Position.create(i, parsed[i][j].p + parsed[i][j].c) - )).toLowerCase() === "extends" + parsed[i][j].l == ld.cls_langindex && + parsed[i][j].s == ld.cls_keyword_attrindex && + doc + .getText( + Range.create(Position.create(i, parsed[i][j].p), Position.create(i, parsed[i][j].p + parsed[i][j].c)), + ) + .toLowerCase() === "extends" ) { // The 'Extends' keyword is present hassupers = true; @@ -572,9 +673,15 @@ export async function getImports(doc: TextDocument, parsed: compressedline[], li } if ( parsed[i].length > 2 && - (parsed[i][0].l == ld.cos_langindex && parsed[i][0].s == ld.cos_ppc_attrindex) && - (parsed[i][1].l == ld.cos_langindex && parsed[i][1].s == ld.cos_ppc_attrindex) && - (doc.getText(Range.create(Position.create(i, parsed[i][0].p), Position.create(i, parsed[i][1].p + parsed[i][1].c))).toLowerCase() === "#import") + parsed[i][0].l == ld.cos_langindex && + parsed[i][0].s == ld.cos_ppc_attrindex && + parsed[i][1].l == ld.cos_langindex && + parsed[i][1].s == ld.cos_ppc_attrindex && + doc + .getText( + Range.create(Position.create(i, parsed[i][0].p), Position.create(i, parsed[i][1].p + parsed[i][1].c)), + ) + .toLowerCase() === "#import" ) { // This is a #import const restofline = doc.getText(Range.create(Position.create(i, parsed[i][2].p), Position.create(i + 1, 0))); @@ -597,17 +704,16 @@ export async function getImports(doc: TextDocument, parsed: compressedline[], li result.push(pkg); } } - } - else { + } else { const querydata = { query: "SELECT $LISTTOSTRING(Importall) AS Importall FROM %Dictionary.CompiledClass WHERE Name = ?", - parameters: [clsname] + parameters: [clsname], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata?.data?.result?.content?.length == 1) { // We got data back if (respdata.data.result.content[0].Importall != "") { - const pkgs = respdata.data.result.content[0].Importall.replace(/[^\x20-\x7E]/g, '').split(','); + const pkgs = respdata.data.result.content[0].Importall.replace(/[^\x20-\x7E]/g, "").split(","); for (const pkg of pkgs) { if (!result.includes(pkg)) { result.push(pkg); @@ -617,15 +723,20 @@ export async function getImports(doc: TextDocument, parsed: compressedline[], li } } } - } - else if (doc.languageId === "objectscript" || doc.languageId === "objectscript-csp") { + } else if (doc.languageId === "objectscript" || doc.languageId === "objectscript-csp") { // Look for #import's above this line for (let i = line; i >= 0; i--) { if ( parsed[i].length > 2 && - (parsed[i][0].l == ld.cos_langindex && parsed[i][0].s == ld.cos_ppc_attrindex) && - (parsed[i][1].l == ld.cos_langindex && parsed[i][1].s == ld.cos_ppc_attrindex) && - (doc.getText(Range.create(Position.create(i, parsed[i][0].p), Position.create(i, parsed[i][1].p + parsed[i][1].c))).toLowerCase() === "#import") + parsed[i][0].l == ld.cos_langindex && + parsed[i][0].s == ld.cos_ppc_attrindex && + parsed[i][1].l == ld.cos_langindex && + parsed[i][1].s == ld.cos_ppc_attrindex && + doc + .getText( + Range.create(Position.create(i, parsed[i][0].p), Position.create(i, parsed[i][1].p + parsed[i][1].c)), + ) + .toLowerCase() === "#import" ) { // This is a #import const restofline = doc.getText(Range.create(Position.create(i, parsed[i][2].p), Position.create(i + 1, 0))); @@ -645,27 +756,33 @@ export async function getImports(doc: TextDocument, parsed: compressedline[], li } } return result; -}; +} /** * Normalize a class name using the import statements at the top of the class, if applicable. * Optionally pass in an array of all the files in that database to avoid making an extra REST request and * an object to output the number full class names that this short class name may resolve to. - * + * * @param doc The TextDocument that the class name is in. * @param parsed The tokenized representation of doc. * @param clsname The class name to normalize. * @param server The server that doc is associated with. * @param line The line of doc that we're in. - * + * * The following optional parameters are only provided when called via `onDiagnostics()`: * @param allfiles An array of all files in a database. * @param possiblecls The number of possible classes that this short class name could map to. * @param inheritedpackages An array containing packages imported by superclasses of this class. */ export async function normalizeClassname( - doc: TextDocument, parsed: compressedline[], clsname: string, server: ServerSpec, line: number, - allfiles?: StudioOpenDialogFile[], possiblecls?: PossibleClasses, inheritedpackages?: string[] + doc: TextDocument, + parsed: compressedline[], + clsname: string, + server: ServerSpec, + line: number, + allfiles?: StudioOpenDialogFile[], + possiblecls?: PossibleClasses, + inheritedpackages?: string[], ): Promise { let result = ""; @@ -678,8 +795,7 @@ export async function normalizeClassname( if (clsname.indexOf(".") === -1) { // This is the special case where "%Library" is shortened to "%" return "%Library.".concat(clsname.slice(1)); - } - else { + } else { return clsname; } } @@ -693,7 +809,7 @@ export async function normalizeClassname( // Get all potential fully qualified classnames const querydata = { query: "SELECT Name FROM %Dictionary.ClassDefinition WHERE $PIECE(Name,'.',$LENGTH(Name,'.')) = ?", - parameters: [clsname] + parameters: [clsname], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (Array.isArray(respdata?.data?.result?.content) && respdata.data.result.content.length > 0) { @@ -704,8 +820,7 @@ export async function normalizeClassname( if (clsobj.Name === clsname) { // The one class we got back is exactly the one we were looking for result = clsname; - } - else { + } else { // The class isn't an exact match. Check if any of the imports appear. let foundimport = false; for (let j = 0; j < imports.length; j++) { @@ -737,17 +852,15 @@ export async function normalizeClassname( } } } - } - else { + } else { // This was called from computeDiagnosics(), which already has an array of all the classes in the database - const filtered = allfiles.filter(file => file.Name.indexOf("." + clsname + ".cls") !== -1); + const filtered = allfiles.filter((file) => file.Name.indexOf("." + clsname + ".cls") !== -1); if (filtered.length === 1) { const clsobj = filtered[0]; if (clsobj.Name.slice(0, -4) === clsname) { // The one class we got back is exactly the one we were looking for result = clsname; - } - else { + } else { // The class isn't an exact match. Check if any of the imports appear. let foundimport = false; for (let j = 0; j < imports.length; j++) { @@ -761,8 +874,7 @@ export async function normalizeClassname( result = clsobj.Name.slice(0, -4); } } - } - else if (filtered.length > 1) { + } else if (filtered.length > 1) { const potential = filtered.filter((clsobj) => { for (let j = 0; j < imports.length; j++) { const numdots = imports[j].replace(/[^.]/g, "").length; @@ -774,47 +886,45 @@ export async function normalizeClassname( }); if (potential.length === 1) { result = potential[0].Name.slice(0, -4); - } - else if (potential.length > 1 && possiblecls !== undefined) { + } else if (potential.length > 1 && possiblecls !== undefined) { possiblecls.num = potential.length; } } } - } - else { + } else { result = clsname; } return result; -}; +} /** * Determine the normalized name of the class that a member is in and how that class was determined. - * + * * @param doc The TextDocument that the class member is in. * @param parsed The tokenized representation of doc. * @param dot The token number of the ".". * @param line The line that the class member is in. * @param server The server that doc is associated with. - * + * * The following optional parameters are only provided when called via `onDiagnostics()`: * @param allfiles An array of all files in a database. * @param inheritedpackages An array containing packages imported by superclasses of this class. */ export async function getClassMemberContext( - doc: TextDocument, parsed: compressedline[], dot: number, line: number, - server: ServerSpec, allfiles?: StudioOpenDialogFile[], inheritedpackages?: string[] + doc: TextDocument, + parsed: compressedline[], + dot: number, + line: number, + server: ServerSpec, + allfiles?: StudioOpenDialogFile[], + inheritedpackages?: string[], ): Promise { let result: ClassMemberContext = { baseclass: "", - context: "" + context: "", }; - if ( - doc.getText(Range.create( - line, parsed[line][dot].p, - line, parsed[line][dot].p + parsed[line][dot].c - )) === ".." - ) { + if (doc.getText(Range.create(line, parsed[line][dot].p, line, parsed[line][dot].p + parsed[line][dot].c)) === "..") { // This is relative dot syntax // Find the class name @@ -829,46 +939,68 @@ export async function getClassMemberContext( const keytext = doc.getText(Range.create(k, parsed[k][0].p, k, parsed[k][0].p + parsed[k][0].c)); if (keytext.toLowerCase() === "method") { result.context = "instance"; - } - else { + } else { result.context = "class"; } break; } } - } - else if ( - dot > 0 && parsed[line][dot - 1].l == ld.cos_langindex && parsed[line][dot - 1].s == ld.cos_delim_attrindex && - doc.getText(Range.create( - line, parsed[line][dot - 1].p, - line, parsed[line][dot - 1].p + parsed[line][dot - 1].c - )) === ")" + } else if ( + dot > 0 && + parsed[line][dot - 1].l == ld.cos_langindex && + parsed[line][dot - 1].s == ld.cos_delim_attrindex && + doc.getText( + Range.create(line, parsed[line][dot - 1].p, line, parsed[line][dot - 1].p + parsed[line][dot - 1].c), + ) === ")" ) { // The token before the dot is a close parenthesis - if (dot - 1 > 0 && parsed[line][dot - 2].l == ld.cos_langindex && parsed[line][dot - 2].s == ld.cos_clsname_attrindex) { + if ( + dot - 1 > 0 && + parsed[line][dot - 2].l == ld.cos_langindex && + parsed[line][dot - 2].s == ld.cos_clsname_attrindex + ) { // This is the end of a ##class - const clstext = doc.getText(findFullRange(line, parsed, dot - 2, parsed[line][dot - 2].p, parsed[line][dot - 2].p + parsed[line][dot - 2].c)); + const clstext = doc.getText( + findFullRange( + line, + parsed, + dot - 2, + parsed[line][dot - 2].p, + parsed[line][dot - 2].p + parsed[line][dot - 2].c, + ), + ); if (clstext.charAt(0) === '"') { // This class name is delimited with double quotes and is fully qualified result = { baseclass: clstext.slice(1, -1), - context: "class" + context: "class", }; - } - else { + } else { result = { baseclass: await normalizeClassname( - doc, parsed, - doc.getText(findFullRange(line, parsed, dot - 2, parsed[line][dot - 2].p, parsed[line][dot - 2].p + parsed[line][dot - 2].c)), - server, line, allfiles, undefined, inheritedpackages + doc, + parsed, + doc.getText( + findFullRange( + line, + parsed, + dot - 2, + parsed[line][dot - 2].p, + parsed[line][dot - 2].p + parsed[line][dot - 2].c, + ), + ), + server, + line, + allfiles, + undefined, + inheritedpackages, ), - context: "class" + context: "class", }; } - } - else { + } else { // This is potentially a chained method call // Loop backwards in the file and look for the first open parenthesis that isn't closed @@ -880,15 +1012,23 @@ export async function getClassMemberContext( // Check the language and attribute of the token before the "(" if ( parsed[openln][opentkn - 1].l == ld.cos_langindex && - (parsed[openln][opentkn - 1].s == ld.cos_method_attrindex || parsed[openln][opentkn - 1].s == ld.cos_mem_attrindex) + (parsed[openln][opentkn - 1].s == ld.cos_method_attrindex || + parsed[openln][opentkn - 1].s == ld.cos_mem_attrindex) ) { // This is a method or multidimensional property // Get the full text of the member - const member = quoteUDLIdentifier(doc.getText(Range.create( - openln, parsed[openln][opentkn - 1].p, - openln, parsed[openln][opentkn - 1].p + parsed[openln][opentkn - 1].c - )), 0); + const member = quoteUDLIdentifier( + doc.getText( + Range.create( + openln, + parsed[openln][opentkn - 1].p, + openln, + parsed[openln][opentkn - 1].p + parsed[openln][opentkn - 1].c, + ), + ), + 0, + ); // Get the base class that this member is in const membercontext = await getClassMemberContext(doc, parsed, opentkn - 2, openln, server); @@ -897,68 +1037,95 @@ export async function getClassMemberContext( if (cls) { result = { baseclass: cls, - context: "instance" + context: "instance", }; } } } } } - } - else if (dot > 0 && parsed[line][dot - 1].l == ld.cos_langindex && parsed[line][dot - 1].s == ld.cos_clsname_attrindex) { + } else if ( + dot > 0 && + parsed[line][dot - 1].l == ld.cos_langindex && + parsed[line][dot - 1].s == ld.cos_clsname_attrindex + ) { // The token before the dot is part of a class name result = { - baseclass: "%SYSTEM".concat(doc.getText(findFullRange(line, parsed, dot - 1, parsed[line][dot - 1].p, parsed[line][dot - 1].p + parsed[line][dot - 1].c))), - context: "system" + baseclass: "%SYSTEM".concat( + doc.getText( + findFullRange( + line, + parsed, + dot - 1, + parsed[line][dot - 1].p, + parsed[line][dot - 1].p + parsed[line][dot - 1].c, + ), + ), + ), + context: "system", }; - } - else if (dot > 0 && parsed[line][dot - 1].l == ld.cos_langindex && ( - parsed[line][dot - 1].s == ld.cos_param_attrindex || - parsed[line][dot - 1].s == ld.cos_localdec_attrindex || - parsed[line][dot - 1].s == ld.cos_localvar_attrindex || - parsed[line][dot - 1].s == ld.cos_otw_attrindex || - parsed[line][dot - 1].s == ld.cos_localundec_attrindex || ( + } else if ( + dot > 0 && + parsed[line][dot - 1].l == ld.cos_langindex && + (parsed[line][dot - 1].s == ld.cos_param_attrindex || + parsed[line][dot - 1].s == ld.cos_localdec_attrindex || + parsed[line][dot - 1].s == ld.cos_localvar_attrindex || + parsed[line][dot - 1].s == ld.cos_otw_attrindex || + parsed[line][dot - 1].s == ld.cos_localundec_attrindex || // This macro token looks like a variable reference, so attempt to compute intellisense for it. // For example, $$$TRACE(var.|) or $$$TRACE(a,var.|) but not $$$TRACE(var.a.|) - doc.languageId != "objectscript-macros" && - parsed[line][dot - 1].s == ld.cos_macro_attrindex && - /^[%\p{L}][\p{L}\d]{0,30}$/u.test(doc.getText(Range.create( - line, parsed[line][dot - 1].p, - line, parsed[line][dot - 1].p + parsed[line][dot - 1].c - ))) && ( - dot - 1 == 0 || parsed[line][dot - 2].s != ld.cos_macro_attrindex || doc.getText(Range.create( - line, parsed[line][dot - 2].p, - line, parsed[line][dot - 2].p + parsed[line][dot - 2].c - )) == "," - ) - ) - )) { + (doc.languageId != "objectscript-macros" && + parsed[line][dot - 1].s == ld.cos_macro_attrindex && + /^[%\p{L}][\p{L}\d]{0,30}$/u.test( + doc.getText( + Range.create(line, parsed[line][dot - 1].p, line, parsed[line][dot - 1].p + parsed[line][dot - 1].c), + ), + ) && + (dot - 1 == 0 || + parsed[line][dot - 2].s != ld.cos_macro_attrindex || + doc.getText( + Range.create(line, parsed[line][dot - 2].p, line, parsed[line][dot - 2].p + parsed[line][dot - 2].c), + ) == ","))) + ) { // The token before the dot is a parameter, local variable, public variable or warning variable const varClass = await determineVariableClass(doc, parsed, line, dot - 1, server, allfiles, inheritedpackages); if (varClass) result = { baseclass: varClass, context: "instance" }; - } - else if (dot > 0 && parsed[line][dot - 1].l == ld.cos_langindex && parsed[line][dot - 1].s == ld.cos_sysv_attrindex) { + } else if ( + dot > 0 && + parsed[line][dot - 1].l == ld.cos_langindex && + parsed[line][dot - 1].s == ld.cos_sysv_attrindex + ) { // The token before the dot is a system variable - const thisvar = doc.getText(findFullRange(line, parsed, dot - 1, parsed[line][dot - 1].p, parsed[line][dot - 1].p + parsed[line][dot - 1].c)).toLowerCase(); + const thisvar = doc + .getText( + findFullRange( + line, + parsed, + dot - 1, + parsed[line][dot - 1].p, + parsed[line][dot - 1].p + parsed[line][dot - 1].c, + ), + ) + .toLowerCase(); if (thisvar === "$this") { // Find the class name for (let i = 0; i < parsed.length; i++) { if (parsed[i].length === 0) { continue; - } - else if (parsed[i][0].l == ld.cls_langindex && parsed[i][0].s == ld.cls_keyword_attrindex) { + } else if (parsed[i][0].l == ld.cls_langindex && parsed[i][0].s == ld.cls_keyword_attrindex) { // This line starts with a UDL keyword const keyword = doc.getText(Range.create(i, parsed[i][0].p, i, parsed[i][0].p + parsed[i][0].c)); if (keyword.toLowerCase() === "class") { for (let j = 1; j < parsed[i].length; j++) { if (parsed[i][j].l == ld.cls_langindex && parsed[i][j].s == ld.cls_clsname_attrindex) { - result.baseclass = result.baseclass.concat(doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c))); + result.baseclass = result.baseclass.concat( + doc.getText(Range.create(i, parsed[i][j].p, i, parsed[i][j].p + parsed[i][j].c)), + ); result.context = "instance"; - } - else if (parsed[i][j].l == ld.cls_langindex && parsed[i][j].s == ld.cls_keyword_attrindex) { + } else if (parsed[i][j].l == ld.cls_langindex && parsed[i][j].s == ld.cls_keyword_attrindex) { // We hit the 'Extends' keyword break; } @@ -968,46 +1135,53 @@ export async function getClassMemberContext( } } } - } - else if ( - dot > 0 && parsed[line][dot - 1].l == ld.cos_langindex && ( - (parsed[line][dot - 1].s == ld.cos_attr_attrindex && dot >= 2) || - parsed[line][dot - 1].s == ld.cos_instvar_attrindex - ) + } else if ( + dot > 0 && + parsed[line][dot - 1].l == ld.cos_langindex && + ((parsed[line][dot - 1].s == ld.cos_attr_attrindex && dot >= 2) || + parsed[line][dot - 1].s == ld.cos_instvar_attrindex) ) { // The token before the dot is an object attribute // This is a chained reference, so get the base class of the previous token - const cls = parsed[line][dot - 1].s == ld.cos_instvar_attrindex - ? currentClass(doc, parsed) : - (await getClassMemberContext(doc, parsed, dot - 2, line, server, allfiles, inheritedpackages)).baseclass; + const cls = + parsed[line][dot - 1].s == ld.cos_instvar_attrindex + ? currentClass(doc, parsed) + : (await getClassMemberContext(doc, parsed, dot - 2, line, server, allfiles, inheritedpackages)).baseclass; if (!["", "%Library.DynamicArray", "%Library.DynamicObject"].includes(cls)) { // We got a base class for the previous token // Skip JSON base classes because they don't have any UDL Properties - const attrtxt = quoteUDLIdentifier(doc.getText(Range.create( - line, parsed[line][dot - 1].p, - line, parsed[line][dot - 1].p + parsed[line][dot - 1].c - )).slice(parsed[line][dot - 1].s == ld.cos_instvar_attrindex ? 2 : 0), 0); + const attrtxt = quoteUDLIdentifier( + doc + .getText(Range.create(line, parsed[line][dot - 1].p, line, parsed[line][dot - 1].p + parsed[line][dot - 1].c)) + .slice(parsed[line][dot - 1].s == ld.cos_instvar_attrindex ? 2 : 0), + 0, + ); // Query the database to find the type of this attribute, if it has one const querydata: QueryData = { query: "SELECT RuntimeType FROM %Dictionary.CompiledProperty WHERE Parent = ? AND Name = ?", - parameters: [cls, attrtxt] + parameters: [cls, attrtxt], }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (respdata !== undefined && respdata.data.result.content.length > 0) { result = { baseclass: respdata.data.result.content[0].RuntimeType, - context: "instance" + context: "instance", }; } } - } - else if (dot > 0 && parsed[line][dot - 1].l == ld.cos_langindex && parsed[line][dot - 1].s == ld.cos_jsonb_attrindex) { + } else if ( + dot > 0 && + parsed[line][dot - 1].l == ld.cos_langindex && + parsed[line][dot - 1].s == ld.cos_jsonb_attrindex + ) { // The token before the dot is a JSON bracket result.context = "instance"; - switch (doc.getText(Range.create(line, parsed[line][dot - 1].p, line, parsed[line][dot - 1].p + parsed[line][dot - 1].c))) { + switch ( + doc.getText(Range.create(line, parsed[line][dot - 1].p, line, parsed[line][dot - 1].p + parsed[line][dot - 1].c)) + ) { case "}": result.baseclass = "%Library.DynamicObject"; break; @@ -1017,11 +1191,11 @@ export async function getClassMemberContext( } return result; -}; +} /** * Send a REST request to an InterSystems server. - * + * * @param method The REST method. * @param api The version of the Atelier API required for this request. * @param path The path portion of the URL. @@ -1030,22 +1204,32 @@ export async function getClassMemberContext( * @param checksum Optional checksum. Only passed for SASchema requests. * @param params Optional URL parameters. Only passed for GET /doc/ requests. */ -export async function makeRESTRequest(method: "GET" | "POST", api: number, path: string, server: ServerSpec, data?: any, checksum?: string, params?: any): Promise { +export async function makeRESTRequest( + method: "GET" | "POST", + api: number, + path: string, + server: ServerSpec, + data?: any, + checksum?: string, + params?: any, +): Promise { // As of version 2.0.0, REST requests are made on the client side - return connection.sendRequest("intersystems/server/makeRESTRequest", { - method, - api, - path, - server, - data, - checksum, - params - }).then((respdata) => respdata ?? undefined); + return connection + .sendRequest("intersystems/server/makeRESTRequest", { + method, + api, + path, + server, + data, + checksum, + params, + }) + .then((respdata) => respdata ?? undefined); } /** * Get the ServerSpec for this document, or ask the client if it's not in the cache. - * + * * @param uri The TextDocument URI */ export async function getServerSpec(uri: string): Promise { @@ -1056,11 +1240,11 @@ export async function getServerSpec(uri: string): Promise { const newspec: ServerSpec = await connection.sendRequest("intersystems/server/resolveFromUri", uri); serverSpecs.set(uri, newspec); return newspec; -}; +} /** * Create the URI for the result of a 'textDocument/definition' request. - * + * * @param paramsUri The URI of the document that the definition request was made on. * @param filename The name of the file that contains the definition. * @param ext The extension of the file that contains the definition, including the leading dot. @@ -1090,22 +1274,20 @@ export async function createDefinitionUri(paramsUri: string, filename: string, e } newuri = URI.from(urijson).toString(); - } - else if (newuri == null) { + } else if (newuri == null) { // The main extension failed to create the URI newuri = ""; } return newuri; - } - catch { + } catch { return ""; } -}; +} /** * Determine if the selected macro is defined in the current file. * Returns the line number of the macro definition if it was found or -1 if it wasn't. - * + * * @param doc The TextDocument that the macro is in. * @param parsed The tokenized representation of doc. * @param line The line that the macro is in. @@ -1119,15 +1301,13 @@ export function isMacroDefinedAbove(doc: TextDocument, parsed: compressedline[], if (!parsed[ln]?.length) continue; if (parsed[ln].length > 1 && parsed[ln][0].l == ld.cos_langindex && parsed[ln][0].s == ld.cos_ppc_attrindex) { // This line begins with a preprocessor command - const ppctext = doc.getText(Range.create( - ln, parsed[ln][1].p, - ln, parsed[ln][1].p + parsed[ln][1].c - )).toLowerCase(); + const ppctext = doc + .getText(Range.create(ln, parsed[ln][1].p, ln, parsed[ln][1].p + parsed[ln][1].c)) + .toLowerCase(); if ( - parsed[ln].length > 2 && ["define", "def1arg", "undef"].includes(ppctext) && doc.getText(Range.create( - ln, parsed[ln][2].p, - ln, parsed[ln][2].p + parsed[ln][2].c - )) == macro + parsed[ln].length > 2 && + ["define", "def1arg", "undef"].includes(ppctext) && + doc.getText(Range.create(ln, parsed[ln][2].p, ln, parsed[ln][2].p + parsed[ln][2].c)) == macro ) { // We found the (un-)definition for the selected macro if (ppctext != "undef") result = ln; @@ -1143,53 +1323,96 @@ export function isMacroDefinedAbove(doc: TextDocument, parsed: compressedline[], /** * Look through this line of a method definition for parameter "thisparam". * If it's found, return its class. Helper method for getClassMemberContext(). - * + * * @param doc The TextDocument that the method definition is in. * @param parsed The tokenized representation of doc. * @param line The line that the method definition is in. * @param server The server that doc is associated with. * @param thisparam The parameter that we're looking for. - * + * * The following optional parameters are only provided when called via `onDiagnostics()`: * @param allfiles An array of all files in a database. * @param inheritedpackages An array containing packages imported by superclasses of this class. */ export async function findMethodParameterClass( - doc: TextDocument, parsed: compressedline[], line: number, server: ServerSpec, - thisparam: string, allfiles?: StudioOpenDialogFile[], inheritedpackages?: string[] + doc: TextDocument, + parsed: compressedline[], + line: number, + server: ServerSpec, + thisparam: string, + allfiles?: StudioOpenDialogFile[], + inheritedpackages?: string[], ): Promise { let result: ClassMemberContext | undefined = undefined; for (let tkn = 0; tkn < parsed[line].length; tkn++) { if (parsed[line][tkn].l == ld.cls_langindex && parsed[line][tkn].s == ld.cls_param_attrindex) { // This is a parameter - const paramtext = doc.getText(Range.create( - Position.create(line, parsed[line][tkn].p), - Position.create(line, parsed[line][tkn].p + parsed[line][tkn].c) - )); + const paramtext = doc.getText( + Range.create( + Position.create(line, parsed[line][tkn].p), + Position.create(line, parsed[line][tkn].p + parsed[line][tkn].c), + ), + ); if (thisparam === paramtext) { // This is the correct parameter if (parsed[line][tkn + 1].l == ld.cls_langindex && parsed[line][tkn + 1].s == ld.cls_keyword_attrindex) { // The token following the parameter name is "as", so this parameter has a type - const clsname = doc.getText(findFullRange(line, parsed, tkn + 2, parsed[line][tkn + 2].p, parsed[line][tkn + 2].p + parsed[line][tkn + 2].c)); + const clsname = doc.getText( + findFullRange( + line, + parsed, + tkn + 2, + parsed[line][tkn + 2].p, + parsed[line][tkn + 2].p + parsed[line][tkn + 2].c, + ), + ); result = { - baseclass: await normalizeClassname(doc, parsed, clsname, server, line, allfiles, undefined, inheritedpackages), - context: "instance" + baseclass: await normalizeClassname( + doc, + parsed, + clsname, + server, + line, + allfiles, + undefined, + inheritedpackages, + ), + context: "instance", }; - } - else if ( - parsed[line][tkn + 1].l == ld.cls_langindex && parsed[line][tkn + 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - Position.create(line, parsed[line][tkn + 1].p), - Position.create(line, parsed[line][tkn + 1].p + parsed[line][tkn + 1].c) - )) === "..." + } else if ( + parsed[line][tkn + 1].l == ld.cls_langindex && + parsed[line][tkn + 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + Position.create(line, parsed[line][tkn + 1].p), + Position.create(line, parsed[line][tkn + 1].p + parsed[line][tkn + 1].c), + ), + ) === "..." ) { // The token following the parameter name is "...", so this is a variable argument parameter if (parsed[line][tkn + 2].l == ld.cls_langindex && parsed[line][tkn + 2].s == ld.cls_keyword_attrindex) { // The token following the "..." is "as", so this parameter has a type - const clsname = doc.getText(findFullRange(line, parsed, tkn + 3, parsed[line][tkn + 3].p, parsed[line][tkn + 3].p + parsed[line][tkn + 3].c)); + const clsname = doc.getText( + findFullRange( + line, + parsed, + tkn + 3, + parsed[line][tkn + 3].p, + parsed[line][tkn + 3].p + parsed[line][tkn + 3].c, + ), + ); result = { - baseclass: await normalizeClassname(doc, parsed, clsname, server, line, allfiles, undefined, inheritedpackages), - context: "instance" + baseclass: await normalizeClassname( + doc, + parsed, + clsname, + server, + line, + allfiles, + undefined, + inheritedpackages, + ), + context: "instance", }; } } @@ -1203,12 +1426,16 @@ export async function findMethodParameterClass( /** * Normalize a system function, variable or structured system variable * name according to the language server configuration settings. - * + * * @param name The name of this system object. Must be in the "default" state, which is long form and all uppercase. * @param type The type of this system object. * @param settings The language server configuration settings. */ -export function normalizeSystemName(name: string, type: "sf" | "sv" | "ssv" | "unkn", settings: LanguageServerConfiguration): string { +export function normalizeSystemName( + name: string, + type: "sf" | "sv" | "ssv" | "unkn", + settings: LanguageServerConfiguration, +): string { let result: string = ""; if (type === "sf") { // This is a system function @@ -1218,57 +1445,85 @@ export function normalizeSystemName(name: string, type: "sf" | "sv" | "ssv" | "u let idealsysftext: string; if (settings.formatting.system.length === "short" && sysfdoc.alias.length === 2) { idealsysftext = sysfdoc.alias[1]; - } - else { + } else { idealsysftext = sysfdoc.label; } if (settings.formatting.system.case === "lower") { idealsysftext = idealsysftext.toLowerCase(); - } - else if (settings.formatting.system.case === "word") { - if (idealsysftext === "$BITCOUNT") { idealsysftext = "$BitCount"; } - else if (idealsysftext === "$BITFIND") { idealsysftext = "$BitFind"; } - else if (idealsysftext === "$BITLOGIC") { idealsysftext = "$BitLogic"; } - else if (idealsysftext === "$CLASSMETHOD") { idealsysftext = "$ClassMethod"; } - else if (idealsysftext === "$CLASSNAME") { idealsysftext = "$ClassName"; } - else if (idealsysftext === "$FNUMBER") { idealsysftext = "$FNumber"; } - else if (idealsysftext === "$INUMBER") { idealsysftext = "$INumber"; } - else if (idealsysftext === "$ISOBJECT") { idealsysftext = "$IsObject"; } - else if (idealsysftext === "$ISVALIDNUM") { idealsysftext = "$IsValidNum"; } - else if (idealsysftext === "$ISVALIDDOUBLE") { idealsysftext = "$IsValidDouble"; } - else if (idealsysftext === "$LISTBUILD") { idealsysftext = "$ListBuild"; } - else if (idealsysftext === "$LISTDATA") { idealsysftext = "$ListData"; } - else if (idealsysftext === "$LISTFIND") { idealsysftext = "$ListFind"; } - else if (idealsysftext === "$LISTFROMSTRING") { idealsysftext = "$ListFromString"; } - else if (idealsysftext === "$LISTGET") { idealsysftext = "$ListGet"; } - else if (idealsysftext === "$LISTLENGTH") { idealsysftext = "$ListLength"; } - else if (idealsysftext === "$LISTNEXT") { idealsysftext = "$ListNext"; } - else if (idealsysftext === "$LISTSAME") { idealsysftext = "$ListSame"; } - else if (idealsysftext === "$LISTTOSTRING") { idealsysftext = "$ListToString"; } - else if (idealsysftext === "$LISTUPDATE") { idealsysftext = "$ListUpdate"; } - else if (idealsysftext === "$LISTVALID") { idealsysftext = "$ListValid"; } - else if (idealsysftext === "$NCONVERT") { idealsysftext = "$NConvert"; } - else if (idealsysftext === "$PREFETCHOFF") { idealsysftext = "$PrefetchOff"; } - else if (idealsysftext === "$PREFETCHON") { idealsysftext = "$PrefetchOn"; } - else if (idealsysftext === "$QLENGTH") { idealsysftext = "$QLength"; } - else if (idealsysftext === "$QSUBSCRIPT") { idealsysftext = "$QSubscript"; } - else if (idealsysftext === "$SCONVERT") { idealsysftext = "$SConvert"; } - else if (idealsysftext === "$SORTBEGIN") { idealsysftext = "$SortBegin"; } - else if (idealsysftext === "$SORTEND") { idealsysftext = "$SortEnd"; } - else if (idealsysftext.charAt(1) === "W") { + } else if (settings.formatting.system.case === "word") { + if (idealsysftext === "$BITCOUNT") { + idealsysftext = "$BitCount"; + } else if (idealsysftext === "$BITFIND") { + idealsysftext = "$BitFind"; + } else if (idealsysftext === "$BITLOGIC") { + idealsysftext = "$BitLogic"; + } else if (idealsysftext === "$CLASSMETHOD") { + idealsysftext = "$ClassMethod"; + } else if (idealsysftext === "$CLASSNAME") { + idealsysftext = "$ClassName"; + } else if (idealsysftext === "$FNUMBER") { + idealsysftext = "$FNumber"; + } else if (idealsysftext === "$INUMBER") { + idealsysftext = "$INumber"; + } else if (idealsysftext === "$ISOBJECT") { + idealsysftext = "$IsObject"; + } else if (idealsysftext === "$ISVALIDNUM") { + idealsysftext = "$IsValidNum"; + } else if (idealsysftext === "$ISVALIDDOUBLE") { + idealsysftext = "$IsValidDouble"; + } else if (idealsysftext === "$LISTBUILD") { + idealsysftext = "$ListBuild"; + } else if (idealsysftext === "$LISTDATA") { + idealsysftext = "$ListData"; + } else if (idealsysftext === "$LISTFIND") { + idealsysftext = "$ListFind"; + } else if (idealsysftext === "$LISTFROMSTRING") { + idealsysftext = "$ListFromString"; + } else if (idealsysftext === "$LISTGET") { + idealsysftext = "$ListGet"; + } else if (idealsysftext === "$LISTLENGTH") { + idealsysftext = "$ListLength"; + } else if (idealsysftext === "$LISTNEXT") { + idealsysftext = "$ListNext"; + } else if (idealsysftext === "$LISTSAME") { + idealsysftext = "$ListSame"; + } else if (idealsysftext === "$LISTTOSTRING") { + idealsysftext = "$ListToString"; + } else if (idealsysftext === "$LISTUPDATE") { + idealsysftext = "$ListUpdate"; + } else if (idealsysftext === "$LISTVALID") { + idealsysftext = "$ListValid"; + } else if (idealsysftext === "$NCONVERT") { + idealsysftext = "$NConvert"; + } else if (idealsysftext === "$PREFETCHOFF") { + idealsysftext = "$PrefetchOff"; + } else if (idealsysftext === "$PREFETCHON") { + idealsysftext = "$PrefetchOn"; + } else if (idealsysftext === "$QLENGTH") { + idealsysftext = "$QLength"; + } else if (idealsysftext === "$QSUBSCRIPT") { + idealsysftext = "$QSubscript"; + } else if (idealsysftext === "$SCONVERT") { + idealsysftext = "$SConvert"; + } else if (idealsysftext === "$SORTBEGIN") { + idealsysftext = "$SortBegin"; + } else if (idealsysftext === "$SORTEND") { + idealsysftext = "$SortEnd"; + } else if (idealsysftext.charAt(1) === "W") { idealsysftext = idealsysftext.slice(0, 3) + idealsysftext.slice(3).toLowerCase(); - } - else if (idealsysftext.charAt(1) === "Z" && idealsysftext.charAt(2) !== "O" && idealsysftext.charAt(2) !== "F") { + } else if ( + idealsysftext.charAt(1) === "Z" && + idealsysftext.charAt(2) !== "O" && + idealsysftext.charAt(2) !== "F" + ) { idealsysftext = idealsysftext.slice(0, 3) + idealsysftext.slice(3).toLowerCase(); - } - else { + } else { idealsysftext = idealsysftext.slice(0, 2) + idealsysftext.slice(2).toLowerCase(); } } result = idealsysftext; } - } - else if (type === "ssv") { + } else if (type === "ssv") { // This is a structured system variable const ssysvdoc = structuredSystemVariables.find((el) => el.label === name.toUpperCase()); @@ -1276,20 +1531,17 @@ export function normalizeSystemName(name: string, type: "sf" | "sv" | "ssv" | "u let idealssysvtext: string; if (settings.formatting.system.length === "short" && ssysvdoc.alias.length === 2) { idealssysvtext = ssysvdoc.alias[1]; - } - else { + } else { idealssysvtext = ssysvdoc.label; } if (settings.formatting.system.case === "lower") { idealssysvtext = idealssysvtext.toLowerCase(); - } - else if (settings.formatting.system.case === "word") { + } else if (settings.formatting.system.case === "word") { idealssysvtext = idealssysvtext.slice(0, 3) + idealssysvtext.slice(3).toLowerCase(); } result = idealssysvtext; } - } - else if (type === "sv") { + } else if (type === "sv") { // This is a system variable const sysvdoc = systemVariables.find((el) => el.label === name.toUpperCase()); @@ -1297,35 +1549,29 @@ export function normalizeSystemName(name: string, type: "sf" | "sv" | "ssv" | "u let idealsysvtext: string; if (settings.formatting.system.length === "short" && sysvdoc.alias.length === 2) { idealsysvtext = sysvdoc.alias[1]; - } - else { + } else { idealsysvtext = sysvdoc.label; } if (settings.formatting.system.case === "lower") { idealsysvtext = idealsysvtext.toLowerCase(); - } - else if (settings.formatting.system.case === "word") { + } else if (settings.formatting.system.case === "word") { if (idealsysvtext.charAt(1) === "Z") { idealsysvtext = idealsysvtext.slice(0, 3) + idealsysvtext.slice(3).toLowerCase(); - } - else { + } else { idealsysvtext = idealsysvtext.slice(0, 2) + idealsysvtext.slice(2).toLowerCase(); } } result = idealsysvtext; } - } - else { + } else { // This is an unknown Z function or variable let idealunknstext = name; if (settings.formatting.system.case === "upper") { idealunknstext = idealunknstext.toUpperCase(); - } - else if (settings.formatting.system.case === "lower") { + } else if (settings.formatting.system.case === "lower") { idealunknstext = idealunknstext.toLowerCase(); - } - else { + } else { idealunknstext = idealunknstext.slice(0, 3).toUpperCase() + idealunknstext.slice(3).toLowerCase(); } result = idealunknstext; @@ -1335,7 +1581,7 @@ export function normalizeSystemName(name: string, type: "sf" | "sv" | "ssv" | "u /** * Escape a UDL identifier using quotes, if necessary. - * + * * @param identifier The identifier to modify. * @param direction Pass 1 to add quotes if necessary, 0 to remove existing quotes. */ @@ -1346,20 +1592,20 @@ export function quoteUDLIdentifier(identifier: string, direction: 0 | 1): string result = result.slice(1, -1); // Turn any "" into " result = result.replace(/""/g, '"'); - } - else if (direction === 1 && identifier.indexOf('"') !== 0) { + } else if (direction === 1 && identifier.indexOf('"') !== 0) { let needsquoting: boolean = false; for (let i = 0; i < result.length; i++) { const char: string = result.charAt(i); const code: number = result.charCodeAt(i); if (i === 0) { - if (!(char === "%" || (char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || code > 0x80)) { + if (!(char === "%" || (char >= "A" && char <= "Z") || (char >= "a" && char <= "z") || code > 0x80)) { needsquoting = true; break; } - } - else { - if (!((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || code > 0x80 || (char >= '0' && char <= '9'))) { + } else { + if ( + !((char >= "A" && char <= "Z") || (char >= "a" && char <= "z") || code > 0x80 || (char >= "0" && char <= "9")) + ) { needsquoting = true; break; } @@ -1378,20 +1624,25 @@ export function quoteUDLIdentifier(identifier: string, direction: 0 | 1): string /** * Determine the normalized name of the class for the parameter at (line,tkn). * If it's found, return its class. Helper method for getClassMemberContext() and onTypeDefinition(). - * + * * @param doc The TextDocument that the parameter is in. * @param parsed The tokenized representation of doc. * @param line The line that the parameter is in. * @param varText The name of the parameter. * @param server The server that doc is associated with. - * + * * The following optional parameters are only provided when called via `onDiagnostics()`: * @param allfiles An array of all files in a database. * @param inheritedpackages An array containing packages imported by superclasses of this class. */ async function determineParameterClass( - doc: TextDocument, parsed: compressedline[], line: number, varText: string, - server: ServerSpec, allfiles?: StudioOpenDialogFile[], inheritedpackages?: string[] + doc: TextDocument, + parsed: compressedline[], + line: number, + varText: string, + server: ServerSpec, + allfiles?: StudioOpenDialogFile[], + inheritedpackages?: string[], ): Promise { let result: ClassMemberContext | undefined = undefined; if (doc.languageId === "objectscript-class") { @@ -1401,39 +1652,54 @@ async function determineParameterClass( for (let j = line; j >= 0; j--) { if (parsed[j].length === 0) { continue; - } - else if (parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { + } else if (parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { // This is the method definition if ( - parsed[j][parsed[j].length - 1].l == ld.cls_langindex && parsed[j][parsed[j].length - 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - j, parsed[j][parsed[j].length - 1].p, - j, parsed[j][parsed[j].length - 1].p + parsed[j][parsed[j].length - 1].c - )) === "(" + parsed[j][parsed[j].length - 1].l == ld.cls_langindex && + parsed[j][parsed[j].length - 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + j, + parsed[j][parsed[j].length - 1].p, + j, + parsed[j][parsed[j].length - 1].p + parsed[j][parsed[j].length - 1].c, + ), + ) === "(" ) { // This is a multi-line method definition for (let mline = j + 1; mline < parsed.length; mline++) { // Loop through the line and look for this parameter - const paramcon = await findMethodParameterClass(doc, parsed, mline, server, varText, allfiles, inheritedpackages); + const paramcon = await findMethodParameterClass( + doc, + parsed, + mline, + server, + varText, + allfiles, + inheritedpackages, + ); if (paramcon !== undefined) { // We found the parameter result = paramcon; break; - } - else if ( - parsed[mline][parsed[mline].length - 1].l == ld.cls_langindex && parsed[mline][parsed[mline].length - 1].s == ld.cls_delim_attrindex && - doc.getText(Range.create( - mline, parsed[mline][parsed[mline].length - 1].p, - mline, parsed[mline][parsed[mline].length - 1].p + parsed[mline][parsed[mline].length - 1].c - )) !== "," + } else if ( + parsed[mline][parsed[mline].length - 1].l == ld.cls_langindex && + parsed[mline][parsed[mline].length - 1].s == ld.cls_delim_attrindex && + doc.getText( + Range.create( + mline, + parsed[mline][parsed[mline].length - 1].p, + mline, + parsed[mline][parsed[mline].length - 1].p + parsed[mline][parsed[mline].length - 1].c, + ), + ) !== "," ) { // We've reached the end of the method definition break; } } - } - else { + } else { // This is a single-line method definition const paramcon = await findMethodParameterClass(doc, parsed, j, server, varText); if (paramcon !== undefined) { @@ -1450,110 +1716,107 @@ async function determineParameterClass( /** * Determine the normalized name of the class for the declared local variable at (line,tkn). * If it's found, return its class. Helper method for getClassMemberContext() and onTypeDefinition(). - * + * * @param doc The TextDocument that the declared local variable is in. * @param parsed The tokenized representation of doc. * @param line The line that the declared local variable is in. * @param varText The name of the variable. * @param server The server that doc is associated with. - * + * * The following optional parameters are only provided when called via `onDiagnostics()`: * @param allfiles An array of all files in a database. * @param inheritedpackages An array containing packages imported by superclasses of this class. */ async function determineDeclaredLocalVarClass( - doc: TextDocument, parsed: compressedline[], line: number, varText: string, - server: ServerSpec, allfiles?: StudioOpenDialogFile[], inheritedpackages?: string[] + doc: TextDocument, + parsed: compressedline[], + line: number, + varText: string, + server: ServerSpec, + allfiles?: StudioOpenDialogFile[], + inheritedpackages?: string[], ): Promise { let result: ClassMemberContext | undefined = undefined; if (varText === "%request") { result = { baseclass: "%CSP.Request", - context: "instance" + context: "instance", }; - } - else if (varText === "%response") { + } else if (varText === "%response") { result = { baseclass: "%CSP.Response", - context: "instance" + context: "instance", }; - } - else if (varText === "%session") { + } else if (varText === "%session") { result = { baseclass: "%CSP.Session", - context: "instance" + context: "instance", }; - } - else if (varText === "%code") { + } else if (varText === "%code") { result = { baseclass: "%Stream.MethodGenerator", - context: "instance" + context: "instance", }; - } - else if (varText === "%class") { + } else if (varText === "%class") { result = { baseclass: "%Dictionary.ClassDefinition", - context: "instance" + context: "instance", }; - } - else if (varText === "%method") { + } else if (varText === "%method") { result = { baseclass: "%Dictionary.MethodDefinition", - context: "instance" + context: "instance", }; - } - else if (varText === "%compiledclass") { + } else if (varText === "%compiledclass") { result = { baseclass: "%Dictionary.CompiledClass", - context: "instance" + context: "instance", }; - } - else if (varText === "%compiledmethod" || varText === "%objcompiledmethod") { + } else if (varText === "%compiledmethod" || varText === "%objcompiledmethod") { result = { baseclass: "%Dictionary.CompiledMethod", - context: "instance" + context: "instance", }; - } - else if (varText === "%trigger") { + } else if (varText === "%trigger") { result = { baseclass: "%Dictionary.TriggerDefinition", - context: "instance" + context: "instance", }; - } - else if (varText === "%compiledtrigger") { + } else if (varText === "%compiledtrigger") { result = { baseclass: "%Dictionary.CompiledTrigger", - context: "instance" + context: "instance", }; - } - else if (varText === "%SourceControl") { + } else if (varText === "%SourceControl") { result = { baseclass: "%Studio.Extension.Base", - context: "instance" + context: "instance", }; - } - else if (varText === "%sqlcontext") { + } else if (varText === "%sqlcontext") { result = { baseclass: "%Library.ProcedureContext", - context: "instance" + context: "instance", }; - } - else { + } else { // Scan to the top of the method to find the #Dim let founddim = false; let firstLabel = true; for (let j = line; j >= 0; j--) { if (parsed[j].length === 0) { continue; - } - else if (doc.languageId === "objectscript-class" && parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { + } else if ( + doc.languageId === "objectscript-class" && + parsed[j][0].l == ld.cls_langindex && + parsed[j][0].s == ld.cls_keyword_attrindex + ) { // This is the definition for the class member that the variable is in break; - } - else if ( - ["objectscript", "objectscript-int"].includes(doc.languageId) && firstLabel && - parsed[j][0].l == ld.cos_langindex && parsed[j][0].s == ld.cos_label_attrindex + } else if ( + ["objectscript", "objectscript-int"].includes(doc.languageId) && + firstLabel && + parsed[j][0].l == ld.cos_langindex && + parsed[j][0].s == ld.cos_label_attrindex ) { // This is the first label above the variable @@ -1563,8 +1826,7 @@ async function determineDeclaredLocalVarClass( } // Scan the whole file firstLabel = false; - } - else if (parsed[j][0].l == ld.cos_langindex && parsed[j][0].s == ld.cos_ppc_attrindex) { + } else if (parsed[j][0].l == ld.cos_langindex && parsed[j][0].s == ld.cos_ppc_attrindex) { // This is a preprocessor command const command = doc.getText(Range.create(j, parsed[j][0].p, j, parsed[j][1].p + parsed[j][1].c)); if (command.toLowerCase() === "#dim") { @@ -1573,8 +1835,17 @@ async function determineDeclaredLocalVarClass( founddim = dimresult.founddim; if (founddim) { result = { - baseclass: await normalizeClassname(doc, parsed, dimresult.class, server, j, allfiles, undefined, inheritedpackages), - context: "instance" + baseclass: await normalizeClassname( + doc, + parsed, + dimresult.class, + server, + j, + allfiles, + undefined, + inheritedpackages, + ), + context: "instance", }; } } @@ -1593,11 +1864,11 @@ async function determineDeclaredLocalVarClass( * If so, attempt to determine the class of `selector`. If the token at * `[endLn,endTkn]` is reached, the function will immediately terminate * to prevent infinite recursion when encountering commands like: - * + * * ```objectscript * Set a = a.MyMethod() * ``` - * + * * @param doc The TextDocument that the Set is in. * @param parsed The tokenized representation of `doc`. * @param line The line the Set is in. @@ -1607,8 +1878,15 @@ async function determineDeclaredLocalVarClass( * @param diagnostic `true` if called via `onDiagnostics()`. */ async function parseSetCommand( - doc: TextDocument, parsed: compressedline[], line: number, token: number, - selector: string, server: ServerSpec, diagnostic: boolean, endLn: number, endTkn: number + doc: TextDocument, + parsed: compressedline[], + line: number, + token: number, + selector: string, + server: ServerSpec, + diagnostic: boolean, + endLn: number, + endTkn: number, ): Promise { let result = ""; let brk = false; @@ -1622,37 +1900,52 @@ async function parseSetCommand( let lastMemTuple: [number, number] | undefined = undefined; for (let ln = line; ln < parsed.length; ln++) { if (!parsed[ln]?.length) continue; - for (let tkn = (ln == line ? token + 1 : 0); tkn < parsed[ln].length; tkn++) { + for (let tkn = ln == line ? token + 1 : 0; tkn < parsed[ln].length; tkn++) { if (ln > endLn || (ln == endLn && tkn >= endTkn)) { // We reached the token of the variable that we are trying to // resolve the type for, so exit to prevent infinite recursion brk = true; break; } - if (parsed[ln][tkn].l == ld.cos_langindex && (parsed[ln][tkn].s === ld.cos_command_attrindex || parsed[ln][tkn].s === ld.cos_zcom_attrindex)) { + if ( + parsed[ln][tkn].l == ld.cos_langindex && + (parsed[ln][tkn].s === ld.cos_command_attrindex || parsed[ln][tkn].s === ld.cos_zcom_attrindex) + ) { // This is the next command, so stop looping brk = true; break; } if ( - ln == line && tkn == token + 1 && parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s === ld.cos_delim_attrindex && + ln == line && + tkn == token + 1 && + parsed[ln][tkn].l == ld.cos_langindex && + parsed[ln][tkn].s === ld.cos_delim_attrindex && doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) == ":" ) { // This Set has a postconditional inPostconditional = true; } - if (inPostconditional && pcParenCount == 0 && tkn > 0 && parsed[ln][tkn].p > (parsed[ln][tkn - 1].p + parsed[ln][tkn - 1].c)) { + if ( + inPostconditional && + pcParenCount == 0 && + tkn > 0 && + parsed[ln][tkn].p > parsed[ln][tkn - 1].p + parsed[ln][tkn - 1].c + ) { // We've hit the end of the postconditional inPostconditional = false; } if ( - inPostconditional && parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s === ld.cos_delim_attrindex && + inPostconditional && + parsed[ln][tkn].l == ld.cos_langindex && + parsed[ln][tkn].s === ld.cos_delim_attrindex && doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) == "(" ) { pcParenCount++; } if ( - inPostconditional && parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s === ld.cos_delim_attrindex && + inPostconditional && + parsed[ln][tkn].l == ld.cos_langindex && + parsed[ln][tkn].s === ld.cos_delim_attrindex && doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) == ")" ) { pcParenCount--; @@ -1662,20 +1955,23 @@ async function parseSetCommand( } } if ( - !inPostconditional && parsed[ln][tkn].l == ld.cos_langindex && - ( - parsed[ln][tkn].s == ld.cos_otw_attrindex || parsed[ln][tkn].s == ld.cos_localundec_attrindex || - parsed[ln][tkn].s == ld.cos_localdec_attrindex || parsed[ln][tkn].s == ld.cos_localvar_attrindex || - parsed[ln][tkn].s == ld.cos_param_attrindex - ) && + !inPostconditional && + parsed[ln][tkn].l == ld.cos_langindex && + (parsed[ln][tkn].s == ld.cos_otw_attrindex || + parsed[ln][tkn].s == ld.cos_localundec_attrindex || + parsed[ln][tkn].s == ld.cos_localdec_attrindex || + parsed[ln][tkn].s == ld.cos_localvar_attrindex || + parsed[ln][tkn].s == ld.cos_param_attrindex) && doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) == selector && // Variable isn't followed by a dot or a subscript - !(tkn + 1 < parsed[ln].length && parsed[ln][tkn + 1].l == ld.cos_langindex && ( - parsed[ln][tkn + 1].s == ld.cos_objdot_attrindex || ( - parsed[ln][tkn + 1].s == ld.cos_delim_attrindex && - doc.getText(Range.create(ln, parsed[ln][tkn + 1].p, ln, parsed[ln][tkn + 1].p + parsed[ln][tkn + 1].c)) == "(" - ) - )) && + !( + tkn + 1 < parsed[ln].length && + parsed[ln][tkn + 1].l == ld.cos_langindex && + (parsed[ln][tkn + 1].s == ld.cos_objdot_attrindex || + (parsed[ln][tkn + 1].s == ld.cos_delim_attrindex && + doc.getText(Range.create(ln, parsed[ln][tkn + 1].p, ln, parsed[ln][tkn + 1].p + parsed[ln][tkn + 1].c)) == + "(")) + ) && // Variable isn't preceded by the indirection operator !(tkn - 1 >= 0 && parsed[ln][tkn - 1].l == ld.cos_langindex && parsed[ln][tkn - 1].s == ld.cos_indir_attrindex) ) { @@ -1683,13 +1979,19 @@ async function parseSetCommand( foundVar = true; } if ( - foundVar && parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_oper_attrindex && + foundVar && + parsed[ln][tkn].l == ld.cos_langindex && + parsed[ln][tkn].s == ld.cos_oper_attrindex && doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) == "=" ) { // We found the assignment operator, so now we need to see what the value is operatorTuple = [ln, tkn]; } - if (operatorTuple && !firstExprTuple && ((tkn == operatorTuple[1] + 1) || (ln == operatorTuple[0] + 1 && tkn == 0))) { + if ( + operatorTuple && + !firstExprTuple && + (tkn == operatorTuple[1] + 1 || (ln == operatorTuple[0] + 1 && tkn == 0)) + ) { // This is the token immediately after the assignment operator or a leading parenthesis if ( @@ -1700,46 +2002,40 @@ async function parseSetCommand( // This is a leading open parenthesis. A Set value can be enclosed in an arbitrary number of these. exprLeadingParenCount++; operatorTuple = [ln, tkn]; - } - else if (parsed[ln][tkn].l == ld.cos_langindex && ( + } else if ( + parsed[ln][tkn].l == ld.cos_langindex && // ##class - parsed[ln][tkn].s == ld.cos_clsobj_attrindex || - // $SYSTEM followed by a class name - ( - parsed[ln][tkn].s == ld.cos_sysv_attrindex && - doc.getText( - Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c) - ).toLowerCase() == "$system" && - tkn < parsed[ln].length - 1 && parsed[ln][tkn + 1].l == ld.cos_langindex && - parsed[ln][tkn + 1].s == ld.cos_clsname_attrindex - ) || - // .. - ( - parsed[ln][tkn].s == ld.cos_objdot_attrindex && - doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) == ".." - ) || - // JSON bracket - parsed[ln][tkn].s == ld.cos_jsonb_attrindex || - // $THIS - ( - parsed[ln][tkn].s == ld.cos_sysv_attrindex && - doc.getText( - Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c) - ).toLowerCase() == "$this" - ) || - // i%var - parsed[ln][tkn].s == ld.cos_instvar_attrindex || - // variable - parsed[ln][tkn].s == ld.cos_param_attrindex || - parsed[ln][tkn].s == ld.cos_localdec_attrindex || - parsed[ln][tkn].s == ld.cos_localvar_attrindex || - parsed[ln][tkn].s == ld.cos_otw_attrindex || - parsed[ln][tkn].s == ld.cos_localundec_attrindex - )) { + (parsed[ln][tkn].s == ld.cos_clsobj_attrindex || + // $SYSTEM followed by a class name + (parsed[ln][tkn].s == ld.cos_sysv_attrindex && + doc + .getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) + .toLowerCase() == "$system" && + tkn < parsed[ln].length - 1 && + parsed[ln][tkn + 1].l == ld.cos_langindex && + parsed[ln][tkn + 1].s == ld.cos_clsname_attrindex) || + // .. + (parsed[ln][tkn].s == ld.cos_objdot_attrindex && + doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) == "..") || + // JSON bracket + parsed[ln][tkn].s == ld.cos_jsonb_attrindex || + // $THIS + (parsed[ln][tkn].s == ld.cos_sysv_attrindex && + doc + .getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)) + .toLowerCase() == "$this") || + // i%var + parsed[ln][tkn].s == ld.cos_instvar_attrindex || + // variable + parsed[ln][tkn].s == ld.cos_param_attrindex || + parsed[ln][tkn].s == ld.cos_localdec_attrindex || + parsed[ln][tkn].s == ld.cos_localvar_attrindex || + parsed[ln][tkn].s == ld.cos_otw_attrindex || + parsed[ln][tkn].s == ld.cos_localundec_attrindex) + ) { exprParenLevel = exprLeadingParenCount; firstExprTuple = [ln, tkn]; - } - else { + } else { // Exit the loop because we've already found // our variable and we can't determine the type brk = true; @@ -1749,10 +2045,9 @@ async function parseSetCommand( if (firstExprTuple) { if ( parsed[ln][tkn].l != ld.cos_langindex || - parsed[ln][tkn].s == ld.error_attrindex || ( - parsed[ln][tkn].s == ld.cos_label_attrindex && - tkn == 0 && parsed[ln][tkn].p == 0 - )) { + parsed[ln][tkn].s == ld.error_attrindex || + (parsed[ln][tkn].s == ld.cos_label_attrindex && tkn == 0 && parsed[ln][tkn].p == 0) + ) { // We've reached the end of the Set brk = true; break; @@ -1767,12 +2062,13 @@ async function parseSetCommand( brk = true; break; } - } else if (exprParenLevel == exprLeadingParenCount && ( - parsed[ln][tkn].s == ld.cos_prop_attrindex || - parsed[ln][tkn].s == ld.cos_method_attrindex || - parsed[ln][tkn].s == ld.cos_attr_attrindex || - parsed[ln][tkn].s == ld.cos_mem_attrindex - )) { + } else if ( + exprParenLevel == exprLeadingParenCount && + (parsed[ln][tkn].s == ld.cos_prop_attrindex || + parsed[ln][tkn].s == ld.cos_method_attrindex || + parsed[ln][tkn].s == ld.cos_attr_attrindex || + parsed[ln][tkn].s == ld.cos_mem_attrindex) + ) { lastMemTuple = [ln, tkn]; } } @@ -1788,70 +2084,128 @@ async function parseSetCommand( const [memLn, memTkn] = lastMemTuple; if (parsed[memLn][memTkn].s != ld.cos_prop_attrindex) { // Parameters don't have meaningful types - if (diagnostic && parsed[memLn][memTkn].s == ld.cos_method_attrindex && ["%New", "%Open", "%OpenId"].includes( - doc.getText(Range.create(memLn, parsed[memLn][memTkn].p, memLn, parsed[memLn][memTkn].p + parsed[memLn][memTkn].c)) - )) { + if ( + diagnostic && + parsed[memLn][memTkn].s == ld.cos_method_attrindex && + ["%New", "%Open", "%OpenId"].includes( + doc.getText( + Range.create(memLn, parsed[memLn][memTkn].p, memLn, parsed[memLn][memTkn].p + parsed[memLn][memTkn].c), + ), + ) + ) { // Don't query the server when calculating diagnostics for performance reasons // Check if this is %New/%Open/%OpenId without a chained reference, which doesn't need a server query if (parsed[exprLn][exprTkn].s == ld.cos_clsobj_attrindex) { // This is the start of a ##class // Find the class name and the method/parameter being referred to - if ((exprTkn + 6) < parsed[exprLn].length) { + if (exprTkn + 6 < parsed[exprLn].length) { // Need at least 6 more tokens (open paren, class, close paren, dot, method, open paren) for (let clstkn = exprTkn + 5; clstkn < parsed[exprLn].length; clstkn++) { if ( - parsed[exprLn][clstkn].l == ld.cos_langindex && parsed[exprLn][clstkn].s == ld.cos_method_attrindex && + parsed[exprLn][clstkn].l == ld.cos_langindex && + parsed[exprLn][clstkn].s == ld.cos_method_attrindex && ["%New", "%Open", "%OpenId"].includes( - doc.getText(Range.create(exprLn, parsed[exprLn][clstkn].p, exprLn, parsed[exprLn][clstkn].p + parsed[exprLn][clstkn].c)) + doc.getText( + Range.create( + exprLn, + parsed[exprLn][clstkn].p, + exprLn, + parsed[exprLn][clstkn].p + parsed[exprLn][clstkn].c, + ), + ), ) ) { // This is ##class(cls).%New/%Open/%OpenId( so save the class name result = doc.getText( - findFullRange(exprLn, parsed, exprTkn + 2, parsed[exprLn][exprTkn + 2].p, parsed[exprLn][exprTkn + 2].p + parsed[exprLn][exprTkn + 2].c) + findFullRange( + exprLn, + parsed, + exprTkn + 2, + parsed[exprLn][exprTkn + 2].p, + parsed[exprLn][exprTkn + 2].p + parsed[exprLn][exprTkn + 2].c, + ), ); break; } } } } else if ( - parsed[exprLn][exprTkn].l == ld.cos_langindex && parsed[exprLn][exprTkn].s == ld.cos_sysv_attrindex && - doc.getText( - Range.create(exprLn, parsed[exprLn][exprTkn].p, exprLn, parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c) - ).toLowerCase() == "$system" + parsed[exprLn][exprTkn].l == ld.cos_langindex && + parsed[exprLn][exprTkn].s == ld.cos_sysv_attrindex && + doc + .getText( + Range.create( + exprLn, + parsed[exprLn][exprTkn].p, + exprLn, + parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c, + ), + ) + .toLowerCase() == "$system" ) { // This is $SYSTEM followed by a class name // Check if the method being called is %New(), %Open() or %OpenId() - if ((exprTkn + 4) < parsed[exprLn].length) { + if (exprTkn + 4 < parsed[exprLn].length) { // Need at least 4 more tokens (class, dot, method, open paren) for (let clstkn = exprTkn + 3; clstkn < parsed[exprLn].length; clstkn++) { if ( - parsed[exprLn][clstkn].l == ld.cos_langindex && parsed[exprLn][clstkn].s == ld.cos_method_attrindex && + parsed[exprLn][clstkn].l == ld.cos_langindex && + parsed[exprLn][clstkn].s == ld.cos_method_attrindex && ["%New", "%Open", "%OpenId"].includes( - doc.getText(Range.create(exprLn, parsed[exprLn][clstkn].p, exprLn, parsed[exprLn][clstkn].p + parsed[exprLn][clstkn].c)) + doc.getText( + Range.create( + exprLn, + parsed[exprLn][clstkn].p, + exprLn, + parsed[exprLn][clstkn].p + parsed[exprLn][clstkn].c, + ), + ), ) ) { // This is $SYSTEM.cls.%New/%Open/%OpenId( so find the name of the class result = `%SYSTEM${doc.getText( - findFullRange(exprLn, parsed, exprTkn + 1, parsed[exprLn][exprTkn + 1].p, parsed[exprLn][exprTkn + 1].p + parsed[exprLn][exprTkn + 1].c) + findFullRange( + exprLn, + parsed, + exprTkn + 1, + parsed[exprLn][exprTkn + 1].p, + parsed[exprLn][exprTkn + 1].p + parsed[exprLn][exprTkn + 1].c, + ), )}`; break; } } } } else if ( - parsed[exprLn][exprTkn].l == ld.cos_langindex && parsed[exprLn][exprTkn].s == ld.cos_objdot_attrindex && - doc.getText(Range.create(exprLn, parsed[exprLn][exprTkn].p, exprLn, parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c)) == ".." + parsed[exprLn][exprTkn].l == ld.cos_langindex && + parsed[exprLn][exprTkn].s == ld.cos_objdot_attrindex && + doc.getText( + Range.create( + exprLn, + parsed[exprLn][exprTkn].p, + exprLn, + parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c, + ), + ) == ".." ) { // This is relative dot syntax // Check if the method being called is %New(), %Open() or %OpenId() if ( - (exprTkn + 2) < parsed[exprLn].length && - parsed[exprLn][exprTkn + 1].l == ld.cos_langindex && parsed[exprLn][exprTkn + 1].s == ld.cos_method_attrindex && + exprTkn + 2 < parsed[exprLn].length && + parsed[exprLn][exprTkn + 1].l == ld.cos_langindex && + parsed[exprLn][exprTkn + 1].s == ld.cos_method_attrindex && ["%New", "%Open", "%OpenId"].includes( - doc.getText(Range.create(exprLn, parsed[exprLn][exprTkn + 1].p, exprLn, parsed[exprLn][exprTkn + 1].p + parsed[exprLn][exprTkn + 1].c)) + doc.getText( + Range.create( + exprLn, + parsed[exprLn][exprTkn + 1].p, + exprLn, + parsed[exprLn][exprTkn + 1].p + parsed[exprLn][exprTkn + 1].c, + ), + ), ) ) { result = currentClass(doc, parsed); @@ -1859,7 +2213,13 @@ async function parseSetCommand( } } else if (!diagnostic) { // Get the class that this member is in, then query the server for the ReturnType - const memberrange = findFullRange(memLn, parsed, memTkn, parsed[memLn][memTkn].p, parsed[memLn][memTkn].p + parsed[memLn][memTkn].c); + const memberrange = findFullRange( + memLn, + parsed, + memTkn, + parsed[memLn][memTkn].p, + parsed[memLn][memTkn].p + parsed[memLn][memTkn].c, + ); const unquotedname = quoteUDLIdentifier(doc.getText(memberrange), 0); // Find the dot token let dottkn = 0; @@ -1881,9 +2241,16 @@ async function parseSetCommand( // There wasn't a member reference, so try to determine the type from the start of the expression const nextTkn = nextToken(parsed, exprLn, exprTkn); if (parsed[exprLn][exprTkn].s == ld.cos_jsonb_attrindex) { - switch (doc.getText( - Range.create(exprLn, parsed[exprLn][exprTkn].p, exprLn, parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c) - )) { + switch ( + doc.getText( + Range.create( + exprLn, + parsed[exprLn][exprTkn].p, + exprLn, + parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c, + ), + ) + ) { case "{": result = "%Library.DynamicObject"; break; @@ -1892,9 +2259,16 @@ async function parseSetCommand( } } else if ( parsed[exprLn][exprTkn].s == ld.cos_sysv_attrindex && - doc.getText( - Range.create(exprLn, parsed[exprLn][exprTkn].p, exprLn, parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c) - ).toLowerCase() == "$this" + doc + .getText( + Range.create( + exprLn, + parsed[exprLn][exprTkn].p, + exprLn, + parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c, + ), + ) + .toLowerCase() == "$this" ) { result = currentClass(doc, parsed); } else if (!diagnostic && parsed[exprLn][exprTkn].s == ld.cos_instvar_attrindex) { @@ -1902,32 +2276,56 @@ async function parseSetCommand( if (cls) { const respdata = await makeRESTRequest("POST", 1, "/action/query", server, { query: "SELECT RuntimeType FROM %Dictionary.CompiledProperty WHERE Parent = ? AND name = ?", - parameters: [cls, quoteUDLIdentifier(doc.getText( - Range.create(exprLn, parsed[exprLn][exprTkn].p, exprLn, parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c) - ).slice(2), 0)] + parameters: [ + cls, + quoteUDLIdentifier( + doc + .getText( + Range.create( + exprLn, + parsed[exprLn][exprTkn].p, + exprLn, + parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c, + ), + ) + .slice(2), + 0, + ), + ], }); if (Array.isArray(respdata?.data?.result?.content) && respdata.data.result.content.length > 0) { result = respdata.data.result.content[0].RuntimeType; } } - } else if (!diagnostic && ( - parsed[exprLn][exprTkn].s == ld.cos_param_attrindex || - parsed[exprLn][exprTkn].s == ld.cos_localdec_attrindex || - parsed[exprLn][exprTkn].s == ld.cos_localvar_attrindex || - parsed[exprLn][exprTkn].s == ld.cos_otw_attrindex || - parsed[exprLn][exprTkn].s == ld.cos_localundec_attrindex - ) && nextTkn && ( - parsed[nextTkn[0]][nextTkn[1]].s == ld.cos_command_attrindex || - parsed[nextTkn[0]][nextTkn[1]].s == ld.cos_zcom_attrindex || ( - parsed[nextTkn[0]][nextTkn[1]].s == ld.cos_delim_attrindex && - doc.getText(Range.create( - nextTkn[0], parsed[nextTkn[0]][nextTkn[1]].p, nextTkn[0], - parsed[nextTkn[0]][nextTkn[1]].p + parsed[nextTkn[0]][nextTkn[1]].c) - ) == "," - // Protect against infinite recursion - )) && doc.getText( - Range.create(exprLn, parsed[exprLn][exprTkn].p, exprLn, parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c) - ) != selector) { + } else if ( + !diagnostic && + (parsed[exprLn][exprTkn].s == ld.cos_param_attrindex || + parsed[exprLn][exprTkn].s == ld.cos_localdec_attrindex || + parsed[exprLn][exprTkn].s == ld.cos_localvar_attrindex || + parsed[exprLn][exprTkn].s == ld.cos_otw_attrindex || + parsed[exprLn][exprTkn].s == ld.cos_localundec_attrindex) && + nextTkn && + (parsed[nextTkn[0]][nextTkn[1]].s == ld.cos_command_attrindex || + parsed[nextTkn[0]][nextTkn[1]].s == ld.cos_zcom_attrindex || + (parsed[nextTkn[0]][nextTkn[1]].s == ld.cos_delim_attrindex && + doc.getText( + Range.create( + nextTkn[0], + parsed[nextTkn[0]][nextTkn[1]].p, + nextTkn[0], + parsed[nextTkn[0]][nextTkn[1]].p + parsed[nextTkn[0]][nextTkn[1]].c, + ), + ) == ",")) && + // Protect against infinite recursion + doc.getText( + Range.create( + exprLn, + parsed[exprLn][exprTkn].p, + exprLn, + parsed[exprLn][exprTkn].p + parsed[exprLn][exprTkn].c, + ), + ) != selector + ) { // The expression is an unsubscripted variable reference result = await determineVariableClass(doc, parsed, exprLn, exprTkn, server); } @@ -1940,21 +2338,27 @@ async function parseSetCommand( /** * Determine the normalized name of the class for the undeclared local variable at (line,tkn). * If it's found, return its class. Helper method for getClassMemberContext() and onTypeDefinition(). - * + * * @param doc The TextDocument that the undeclared local variable is in. * @param parsed The tokenized representation of doc. * @param line The line that the undeclared local variable is in. * @param tkn The token of the undeclared local variable in the line. * @param varText The name of the variable. * @param server The server that doc is associated with. - * + * * The following optional parameters are only provided when called via `onDiagnostics()`: * @param allfiles An array of all files in a database. * @param inheritedpackages An array containing packages imported by superclasses of this class. */ async function determineUndeclaredLocalVarClass( - doc: TextDocument, parsed: compressedline[], line: number, tkn: number, varText: string, - server: ServerSpec, allfiles?: StudioOpenDialogFile[], inheritedpackages?: string[] + doc: TextDocument, + parsed: compressedline[], + line: number, + tkn: number, + varText: string, + server: ServerSpec, + allfiles?: StudioOpenDialogFile[], + inheritedpackages?: string[], ): Promise { let result: ClassMemberContext | undefined = undefined; @@ -1963,14 +2367,18 @@ async function determineUndeclaredLocalVarClass( for (let j = line; j >= 0; j--) { if (parsed[j].length === 0) { continue; - } - else if (doc.languageId === "objectscript-class" && parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { + } else if ( + doc.languageId === "objectscript-class" && + parsed[j][0].l == ld.cls_langindex && + parsed[j][0].s == ld.cls_keyword_attrindex + ) { // This is the definition for the class member that the variable is in break; - } - else if ( - ["objectscript", "objectscript-int"].includes(doc.languageId) && firstLabel && - parsed[j][0].l == ld.cos_langindex && parsed[j][0].s == ld.cos_label_attrindex + } else if ( + ["objectscript", "objectscript-int"].includes(doc.languageId) && + firstLabel && + parsed[j][0].l == ld.cos_langindex && + parsed[j][0].s == ld.cos_label_attrindex ) { // This is the first label above the variable @@ -1980,83 +2388,114 @@ async function determineUndeclaredLocalVarClass( } // Scan the whole file firstLabel = false; - } - else { + } else { // Loop through the line looking for Sets or this variable passed by reference for (let k = 0; k < parsed[j].length; k++) { if ( - parsed[j][k].l == ld.cos_langindex && parsed[j][k].s === ld.cos_command_attrindex && - ["s", "set"].includes(doc.getText(Range.create(j, parsed[j][k].p, j, parsed[j][k].p + parsed[j][k].c)).toLowerCase()) + parsed[j][k].l == ld.cos_langindex && + parsed[j][k].s === ld.cos_command_attrindex && + ["s", "set"].includes( + doc.getText(Range.create(j, parsed[j][k].p, j, parsed[j][k].p + parsed[j][k].c)).toLowerCase(), + ) ) { // This is a Set command const setCls = await parseSetCommand(doc, parsed, j, k, varText, server, Array.isArray(allfiles), line, tkn); if (setCls) { result = { - baseclass: await normalizeClassname(doc, parsed, setCls, server, j, allfiles, undefined, inheritedpackages), - context: "instance" + baseclass: await normalizeClassname( + doc, + parsed, + setCls, + server, + j, + allfiles, + undefined, + inheritedpackages, + ), + context: "instance", }; break; } } // Don't check for by reference syntax if we're calculating diagnostics for performance reasons if ( - !allfiles && parsed[j][k].l == ld.cos_langindex && parsed[j][k].s == ld.cos_oper_attrindex && + !allfiles && + parsed[j][k].l == ld.cos_langindex && + parsed[j][k].s == ld.cos_oper_attrindex && doc.getText(Range.create(j, parsed[j][k].p, j, parsed[j][k].p + parsed[j][k].c)) == "." ) { const next = nextToken(parsed, j, k); // Check if the variable passed by reference is the one we care about - if (next && parsed[next[0]][next[1]].l == ld.cos_langindex && - ( - parsed[next[0]][next[1]].s == ld.cos_otw_attrindex || parsed[next[0]][next[1]].s == ld.cos_localundec_attrindex || - parsed[next[0]][next[1]].s == ld.cos_localdec_attrindex || parsed[next[0]][next[1]].s == ld.cos_localvar_attrindex || - parsed[next[0]][next[1]].s == ld.cos_param_attrindex - ) && - doc.getText(Range.create( - next[0], parsed[next[0]][next[1]].p, - next[0], parsed[next[0]][next[1]].p + parsed[next[0]][next[1]].c - )) == varText + if ( + next && + parsed[next[0]][next[1]].l == ld.cos_langindex && + (parsed[next[0]][next[1]].s == ld.cos_otw_attrindex || + parsed[next[0]][next[1]].s == ld.cos_localundec_attrindex || + parsed[next[0]][next[1]].s == ld.cos_localdec_attrindex || + parsed[next[0]][next[1]].s == ld.cos_localvar_attrindex || + parsed[next[0]][next[1]].s == ld.cos_param_attrindex) && + doc.getText( + Range.create( + next[0], + parsed[next[0]][next[1]].p, + next[0], + parsed[next[0]][next[1]].p + parsed[next[0]][next[1]].c, + ), + ) == varText ) { // Find the start of the method const [startLn, startTkn] = findOpenParen(doc, parsed, j, k); - if (startLn != -1 && startTkn != -1 && + if ( + startLn != -1 && + startTkn != -1 && parsed[startLn][startTkn - 1].l == ld.cos_langindex && - ( - parsed[startLn][startTkn - 1].s == ld.cos_method_attrindex || - parsed[startLn][startTkn - 1].s == ld.cos_mem_attrindex - ) + (parsed[startLn][startTkn - 1].s == ld.cos_method_attrindex || + parsed[startLn][startTkn - 1].s == ld.cos_mem_attrindex) ) { // Determine which argument number this is - const argNum = determineActiveParam(doc.getText(Range.create( - startLn, parsed[startLn][startTkn].p + 1, - j, parsed[j][k].p - ))) + 1; + const argNum = + determineActiveParam( + doc.getText(Range.create(startLn, parsed[startLn][startTkn].p + 1, j, parsed[j][k].p)), + ) + 1; // Get the full text of the member - const member = doc.getText(Range.create( - startLn, parsed[startLn][startTkn - 1].p, - startLn, parsed[startLn][startTkn - 1].p + parsed[startLn][startTkn - 1].c - )); + const member = doc.getText( + Range.create( + startLn, + parsed[startLn][startTkn - 1].p, + startLn, + parsed[startLn][startTkn - 1].p + parsed[startLn][startTkn - 1].c, + ), + ); const unquotedname = quoteUDLIdentifier(member, 0); // Get the base class that this member is in const membercontext = await getClassMemberContext(doc, parsed, startTkn - 2, startLn, server); if (membercontext.baseclass != "" && argNum > 0) { // Get the method signature - const querydata = member == "%New" ? { - // Get the information for both %New and %OnNew - query: "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND (Name = ? OR Name = ?)", - parameters: [argNum, membercontext.baseclass, unquotedname, "%OnNew"] - } : { - query: "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND Name = ?", - parameters: [argNum, membercontext.baseclass, unquotedname] - }; + const querydata = + member == "%New" + ? { + // Get the information for both %New and %OnNew + query: + "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND (Name = ? OR Name = ?)", + parameters: [argNum, membercontext.baseclass, unquotedname, "%OnNew"], + } + : { + query: + "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND Name = ?", + parameters: [argNum, membercontext.baseclass, unquotedname], + }; const respdata = await makeRESTRequest("POST", 1, "/action/query", server, querydata); if (Array.isArray(respdata?.data?.result?.content) && respdata.data.result.content.length > 0) { // We got data back - let formalSpecObj: { FormalSpec: string, Type: string } = { FormalSpec: "", Type: "" }; + let formalSpecObj: { FormalSpec: string; Type: string } = { FormalSpec: "", Type: "" }; if (member == "%New") { - if (respdata.data.result.content.length == 2 && respdata.data.result.content[1].Origin != "%Library.RegisteredObject") { + if ( + respdata.data.result.content.length == 2 && + respdata.data.result.content[1].Origin != "%Library.RegisteredObject" + ) { // %OnNew has been overridden for this class formalSpecObj = respdata.data.result.content[1]; } else { @@ -2071,26 +2510,33 @@ async function determineUndeclaredLocalVarClass( let stubquery = ""; if (stubarr[2] == "i") { // This is a method generated from an index - stubquery = "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type FROM %Dictionary.CompiledIndexMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type FROM %Dictionary.CompiledIndexMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] == "q") { // This is a method generated from a query - stubquery = "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] == "a") { // This is a method generated from a property - stubquery = "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] == "n") { // This is a method generated from a constraint - stubquery = "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT FormalSpec, $LISTGET($LISTGET(FormalSpecParsed,?),2) AS Type FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubquery != "") { const stubrespdata = await makeRESTRequest("POST", 1, "/action/query", server, { query: stubquery, - parameters: [argNum, stubarr[1], membercontext.baseclass, stubarr[0]] + parameters: [argNum, stubarr[1], membercontext.baseclass, stubarr[0]], }); - if (Array.isArray(stubrespdata?.data?.result?.content) && stubrespdata.data.result.content.length > 0) { + if ( + Array.isArray(stubrespdata?.data?.result?.content) && + stubrespdata.data.result.content.length > 0 + ) { // We got data back formalSpecObj = stubrespdata.data.result.content[0]; } @@ -2100,7 +2546,11 @@ async function determineUndeclaredLocalVarClass( if (formalSpecObj.FormalSpec != "" && formalSpecObj.Type != "") { // If the type is %Library.String, validate that the user really declared that type if (formalSpecObj.Type == "%Library.String") { - let currentArg = 1, openParenCount = 0, openBraceCount = 0, inQuote = false, typeDeclared = false; + let currentArg = 1, + openParenCount = 0, + openBraceCount = 0, + inQuote = false, + typeDeclared = false; for (const char of formalSpecObj.FormalSpec) { switch (char) { case "{": @@ -2115,11 +2565,12 @@ async function determineUndeclaredLocalVarClass( case ")": if (!inQuote) openParenCount--; break; - case "\"": + case '"': inQuote = !inQuote; break; case ":": - if (!inQuote && !openBraceCount && !openParenCount && currentArg == argNum) typeDeclared = true; + if (!inQuote && !openBraceCount && !openParenCount && currentArg == argNum) + typeDeclared = true; break; case ",": if (!inQuote && !openBraceCount && !openParenCount) currentArg++; @@ -2129,14 +2580,14 @@ async function determineUndeclaredLocalVarClass( if (typeDeclared) { result = { baseclass: formalSpecObj.Type, - context: "instance" + context: "instance", }; break; } } else { result = { baseclass: formalSpecObj.Type, - context: "instance" + context: "instance", }; break; } @@ -2155,16 +2606,18 @@ async function determineUndeclaredLocalVarClass( /** * Expand a minified FormalSpec returned by a query to be more user friendly. - * + * * @param FormalSpec The value of the FormalSpec column in %Dictionary.CompiledMethod. * @param markdown If the result should include Markdown. */ export function beautifyFormalSpec(FormalSpec: string, markdown = false): string { - let result = "", inParen = 0, inQuote = false, inParam = true, inCls = false, inDefault = false; - const markdownChars = [ - "\\", "`", "*", "_", "{", "}", "(", ")", "[", - "]", "<", ">", "#", "+", "-", ".", "!", "|" - ]; + let result = "", + inParen = 0, + inQuote = false, + inParam = true, + inCls = false, + inDefault = false; + const markdownChars = ["\\", "`", "*", "_", "{", "}", "(", ")", "[", "]", "<", ">", "#", "+", "-", ".", "!", "|"]; for (const c of FormalSpec) { if (!inParen && !inQuote) { // In the argument list @@ -2210,7 +2663,7 @@ export function beautifyFormalSpec(FormalSpec: string, markdown = false): string inDefault = true; result += " = "; break; - case "\"": + case '"': inQuote = true; result += c; break; @@ -2236,7 +2689,7 @@ export function beautifyFormalSpec(FormalSpec: string, markdown = false): string case "=": result += inDefault ? "=" : " = "; break; - case "\"": + case '"': inQuote = true; result += c; break; @@ -2256,7 +2709,7 @@ export function beautifyFormalSpec(FormalSpec: string, markdown = false): string } } else { // In a quoted string - if (c == "\"") inQuote = false; + if (c == '"') inQuote = false; if (markdown && markdownChars.includes(c)) result += "\\"; result += c; } @@ -2268,14 +2721,19 @@ export function beautifyFormalSpec(FormalSpec: string, markdown = false): string /** * Find the open parenthesis token that corresponds to the close parenthesis token at [`line`,`token`]. - * + * * @param doc The TextDocument that the close parenthesis is in. * @param parsed The tokenized representation of doc. * @param line The line that the close parenthesis is in. * @param token The offset of the close parenthesis in the line. * @returns A tuple containing the line and token number of the open parenthesis, or -1 for both if it wasn't found. */ -export function findOpenParen(doc: TextDocument, parsed: compressedline[], line: number, token: number): [number, number] { +export function findOpenParen( + doc: TextDocument, + parsed: compressedline[], + line: number, + token: number, +): [number, number] { let numclosed = 0; let openln = -1; let opentkn = -1; @@ -2286,18 +2744,21 @@ export function findOpenParen(doc: TextDocument, parsed: compressedline[], line: } for (let tkn = starttkn; tkn >= 0; tkn--) { if (parsed[ln][tkn].l === ld.cos_langindex && parsed[ln][tkn].s === ld.cos_delim_attrindex) { - const delimtext = doc.getText(Range.create(Position.create(ln, parsed[ln][tkn].p), Position.create(ln, parsed[ln][tkn].p + parsed[ln][tkn].c))); + const delimtext = doc.getText( + Range.create( + Position.create(ln, parsed[ln][tkn].p), + Position.create(ln, parsed[ln][tkn].p + parsed[ln][tkn].c), + ), + ); if (delimtext === "(") { if (numclosed === 0) { openln = ln; opentkn = tkn; break; - } - else { + } else { numclosed--; } - } - else if (delimtext === ")") { + } else if (delimtext === ")") { numclosed++; } } @@ -2311,7 +2772,7 @@ export function findOpenParen(doc: TextDocument, parsed: compressedline[], line: /** * Convert a class documentation string to Markdown. - * + * * @param html The class documentation HTML string to convert. */ export function documaticHtmlToMarkdown(html: string): string { @@ -2330,55 +2791,69 @@ export function documaticHtmlToMarkdown(html: string): string { * return the raw name of that class. If the class couldn't be * determined, or this parameter is a method, query or trigger * argument, the empty string is returned. - * + * * @param doc The TextDocument that the class parameter is in. * @param parsed The tokenized representation of `doc`. * @param line The line that the class parameter is on. * @param token The offset of the class parameter in the line. * @param completion `true` if called from the completion provider. */ -export function determineClassNameParameterClass(doc: TextDocument, parsed: compressedline[], line: number, token: number, completion = false): string { +export function determineClassNameParameterClass( + doc: TextDocument, + parsed: compressedline[], + line: number, + token: number, + completion = false, +): string { if ( completion && [ld.error_attrindex, ld.cls_delim_attrindex].includes(parsed[line][token].s) && - ["(", "()"].includes(doc.getText(Range.create( - line, - parsed[line][token].p, - line, - parsed[line][token].p + parsed[line][token].c - ))) && token > 0 && + ["(", "()"].includes( + doc.getText(Range.create(line, parsed[line][token].p, line, parsed[line][token].p + parsed[line][token].c)), + ) && + token > 0 && parsed[line][token - 1].l == ld.cls_langindex && parsed[line][token - 1].s == ld.cls_clsname_attrindex ) { // When doing completion for (, the ( may be an // error token, or the () can be a single delimiter token, // so we need to handle those special cases - return doc.getText(findFullRange( - line, parsed, token - 1, - parsed[line][token - 1].p, - parsed[line][token - 1].p + parsed[line][token - 1].c - )); - } - let openCount = 1, clsName = ""; + return doc.getText( + findFullRange( + line, + parsed, + token - 1, + parsed[line][token - 1].p, + parsed[line][token - 1].p + parsed[line][token - 1].c, + ), + ); + } + let openCount = 1, + clsName = ""; for (let tkn = token; tkn >= 0; tkn--) { if (parsed[line][tkn].l == ld.cls_langindex && parsed[line][tkn].s == ld.cls_delim_attrindex) { - const delimText = doc.getText(Range.create( - line, - parsed[line][tkn].p, - line, - parsed[line][tkn].p + parsed[line][tkn].c - )); + const delimText = doc.getText( + Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c), + ); if (delimText == ")") { openCount++; } else if (delimText == "(") { openCount--; if (openCount == 0) { - if (tkn > 0 && parsed[line][tkn - 1].l == ld.cls_langindex && parsed[line][tkn - 1].s == ld.cls_clsname_attrindex) { - clsName = doc.getText(findFullRange( - line, parsed, tkn - 1, - parsed[line][tkn - 1].p, - parsed[line][tkn - 1].p + parsed[line][tkn - 1].c - )); + if ( + tkn > 0 && + parsed[line][tkn - 1].l == ld.cls_langindex && + parsed[line][tkn - 1].s == ld.cls_clsname_attrindex + ) { + clsName = doc.getText( + findFullRange( + line, + parsed, + tkn - 1, + parsed[line][tkn - 1].p, + parsed[line][tkn - 1].p + parsed[line][tkn - 1].c, + ), + ); } break; } @@ -2390,7 +2865,7 @@ export function determineClassNameParameterClass(doc: TextDocument, parsed: comp /** * Determine the nesting level for this token within a Storage definition. - * + * * @param doc The TextDocument. * @param parsed The tokenized representation of doc. * @param line The line this token is on. @@ -2398,7 +2873,12 @@ export function determineClassNameParameterClass(doc: TextDocument, parsed: comp * @returns The key in the `storageKeywords` object for this nesting level, * or the empty string if the token is not in a Storage definition. */ -export function storageKeywordsKeyForToken(doc: TextDocument, parsed: compressedline[], line: number, token: number): string { +export function storageKeywordsKeyForToken( + doc: TextDocument, + parsed: compressedline[], + line: number, + token: number, +): string { let result: string = ""; // Check that this token is in a Storage definition let storageStart = -1; @@ -2409,10 +2889,14 @@ export function storageKeywordsKeyForToken(doc: TextDocument, parsed: compressed if (parsed[j][0].l == ld.cls_langindex && parsed[j][0].s == ld.cls_keyword_attrindex) { const keytext = doc.getText(Range.create(j, parsed[j][0].p, j, parsed[j][0].p + parsed[j][0].c)); if (keytext.toLowerCase() == "storage") { - if (doc.getText(Range.create(j, 0, j + 1, 0)).trim().endsWith("{")) { + if ( + doc + .getText(Range.create(j, 0, j + 1, 0)) + .trim() + .endsWith("{") + ) { storageStart = j + 1; - } - else { + } else { storageStart = j + 2; } } @@ -2429,10 +2913,14 @@ export function storageKeywordsKeyForToken(doc: TextDocument, parsed: compressed line = prevline; token = prevtkn; if ( - doc.getText(Range.create( - prevline, parsed[prevline][prevtkn].p, - prevline, parsed[prevline][prevtkn].p + parsed[prevline][prevtkn].c - )) == "/" + doc.getText( + Range.create( + prevline, + parsed[prevline][prevtkn].p, + prevline, + parsed[prevline][prevtkn].p + parsed[prevline][prevtkn].c, + ), + ) == "/" ) { // This is a close element, so we need to ignore the last open element ignoreLastOpen = true; @@ -2447,37 +2935,67 @@ export function storageKeywordsKeyForToken(doc: TextDocument, parsed: compressed } for (let xmltkn = 0; xmltkn <= endtkn; xmltkn++) { if (parsed[xmlline][xmltkn].l == ld.cls_langindex && parsed[xmlline][xmltkn].s == ld.cls_delim_attrindex) { - // This is a UDL delimiter - const tokentext = doc.getText(Range.create( - xmlline, parsed[xmlline][xmltkn].p, - xmlline, parsed[xmlline][xmltkn].p + parsed[xmlline][xmltkn].c - )); + // This is a UDL delimiter + const tokentext = doc.getText( + Range.create( + xmlline, + parsed[xmlline][xmltkn].p, + xmlline, + parsed[xmlline][xmltkn].p + parsed[xmlline][xmltkn].c, + ), + ); if (tokentext === "<" && xmltkn != endtkn && parsed[xmlline][xmltkn + 1].s == ld.cls_xmlelemname_attrindex) { - open.push(doc.getText(Range.create( - xmlline, parsed[xmlline][xmltkn + 1].p, - xmlline, parsed[xmlline][xmltkn + 1].p + parsed[xmlline][xmltkn + 1].c - ))); - } - else if (tokentext === "/") { + open.push( + doc.getText( + Range.create( + xmlline, + parsed[xmlline][xmltkn + 1].p, + xmlline, + parsed[xmlline][xmltkn + 1].p + parsed[xmlline][xmltkn + 1].c, + ), + ), + ); + } else if (tokentext === "/") { if (xmltkn != endtkn && parsed[xmlline][xmltkn + 1].s == ld.cls_delim_attrindex) { - if (doc.getText(Range.create( - xmlline, parsed[xmlline][xmltkn + 1].p, - xmlline, parsed[xmlline][xmltkn + 1].p + parsed[xmlline][xmltkn + 1].c - )) === ">") { + if ( + doc.getText( + Range.create( + xmlline, + parsed[xmlline][xmltkn + 1].p, + xmlline, + parsed[xmlline][xmltkn + 1].p + parsed[xmlline][xmltkn + 1].c, + ), + ) === ">" + ) { // The previous element has been closed open.pop(); } - } - else if (xmltkn != 0 && parsed[xmlline][xmltkn - 1].s == ld.cls_delim_attrindex) { - if (xmltkn != endtkn && doc.getText(Range.create( - xmlline, parsed[xmlline][xmltkn - 1].p, - xmlline, parsed[xmlline][xmltkn - 1].p + parsed[xmlline][xmltkn - 1].c - )) === "<") { + } else if (xmltkn != 0 && parsed[xmlline][xmltkn - 1].s == ld.cls_delim_attrindex) { + if ( + xmltkn != endtkn && + doc.getText( + Range.create( + xmlline, + parsed[xmlline][xmltkn - 1].p, + xmlline, + parsed[xmlline][xmltkn - 1].p + parsed[xmlline][xmltkn - 1].c, + ), + ) === "<" + ) { // The upcoming element is being closed - open.splice(open.lastIndexOf(doc.getText(Range.create( - xmlline, parsed[xmlline][xmltkn + 1].p, - xmlline, parsed[xmlline][xmltkn + 1].p + parsed[xmlline][xmltkn + 1].c - ))), 1); + open.splice( + open.lastIndexOf( + doc.getText( + Range.create( + xmlline, + parsed[xmlline][xmltkn + 1].p, + xmlline, + parsed[xmlline][xmltkn + 1].p + parsed[xmlline][xmltkn + 1].c, + ), + ), + ), + 1, + ); } } } @@ -2491,7 +3009,7 @@ export function storageKeywordsKeyForToken(doc: TextDocument, parsed: compressed /** * Wait for the updated semantic tokens for `uri` to be stored, then return them. - * + * * @param uri The uri of the document to get semantic tokens for. * @returns The semantic tokens, or `undefined` if `uri` is not a key of `parsedDocuments` or retrieval took too long. */ @@ -2502,25 +3020,28 @@ export async function getParsedDocument(uri: string): Promise void) { const result = parsedDocuments.get(uri); - if (result != undefined || ((Date.now() - start) >= 5000)) { + if (result != undefined || Date.now() - start >= 5000) { resolve(result); - } - else { + } else { setTimeout(waitForTokens, 25, resolve); } - }; + } return new Promise(waitForTokens); } /** * Check if label on `line` of `doc` is a procedure block. - * + * * @param doc The TextDocument. * @param parsed The tokenized representation of doc. * @param line The line that this label is in. * @returns A line, token tuple of the first brace after the procedure definition, else `undefined`. */ -export function labelIsProcedureBlock(doc: TextDocument, parsed: compressedline[], line: number): [number, number] | undefined { +export function labelIsProcedureBlock( + doc: TextDocument, + parsed: compressedline[], + line: number, +): [number, number] | undefined { const lastLabelTkn = parsed[line].length > 1 && parsed[line][1].s == ld.cos_label_attrindex ? 1 : 0; let currentLabelIsProcedureBlock: boolean = false; let result: [number, number] | undefined = undefined; @@ -2530,9 +3051,11 @@ export function labelIsProcedureBlock(doc: TextDocument, parsed: compressedline[ parsed[line][lastLabelTkn + 1].s == ld.cos_delim_attrindex && doc.getText( Range.create( - line, parsed[line][lastLabelTkn + 1].p, - line, parsed[line][lastLabelTkn + 1].p + parsed[line][lastLabelTkn + 1].c - ) + line, + parsed[line][lastLabelTkn + 1].p, + line, + parsed[line][lastLabelTkn + 1].p + parsed[line][lastLabelTkn + 1].c, + ), ) == "(" ) { // Walk the parsed document until we hit the end of the procedure definition @@ -2540,57 +3063,48 @@ export function labelIsProcedureBlock(doc: TextDocument, parsed: compressedline[ let openparen = 0; let inparam = true; let brk = false; - for (let ln = (parsed[line].length == lastLabelTkn + 2 ? line + 1 : line); ln < parsed.length; ln++) { - for (let tkn = (ln == line ? lastLabelTkn + 2 : 0); tkn < parsed[ln].length; tkn++) { + for (let ln = parsed[line].length == lastLabelTkn + 2 ? line + 1 : line; ln < parsed.length; ln++) { + for (let tkn = ln == line ? lastLabelTkn + 2 : 0; tkn < parsed[ln].length; tkn++) { if (parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_comment_attrindex) { // Comments are allowed anywhere in the procedure definition, so ignore them continue; - } - else if (parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_delim_attrindex) { + } else if (parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_delim_attrindex) { const delim = doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)); if (inparam) { if (delim == "(") { openparen++; - } - else if (delim == ")") { + } else if (delim == ")") { if (openparen == 0) { // Found the end of the parameter list inparam = false; - } - else { + } else { openparen--; } } - } - else { + } else { if (delim == "[") { // We hit the public list, which means this label is a procedure block currentLabelIsProcedureBlock = true; - } - else if (currentLabelIsProcedureBlock && (delim == "]" || delim == ",")) { + } else if (currentLabelIsProcedureBlock && (delim == "]" || delim == ",")) { // These are delimiters inside the public list, so ignore them continue; - } - else { + } else { // This is some other delimiter, so this label is not a procedure block brk = true; break; } } - } - else if (!inparam && parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_command_attrindex) { + } else if (!inparam && parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_command_attrindex) { const command = doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)); if (["public", "private"].includes(command.toLowerCase())) { // The access modifier can be present with our without a brace, so ignore it continue; - } - else { + } else { // This is some other command, so this label is not a procedure block brk = true; break; } - } - else if (!inparam && parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_brace_attrindex) { + } else if (!inparam && parsed[ln][tkn].l == ld.cos_langindex && parsed[ln][tkn].s == ld.cos_brace_attrindex) { const brace = doc.getText(Range.create(ln, parsed[ln][tkn].p, ln, parsed[ln][tkn].p + parsed[ln][tkn].c)); if (brace == "{") { // This is an open brace, so this label is procedure block @@ -2603,8 +3117,7 @@ export function labelIsProcedureBlock(doc: TextDocument, parsed: compressedline[ brk = true; break; } - } - else if (!inparam && !currentLabelIsProcedureBlock) { + } else if (!inparam && !currentLabelIsProcedureBlock) { // This is some other token, so this label is not a procedure block brk = true; break; @@ -2628,8 +3141,7 @@ export function currentClass(doc: TextDocument, parsed: compressedline[]): strin for (let i = 0; i < parsed.length; i++) { if (parsed[i].length === 0) { continue; - } - else if (parsed[i][0].l == ld.cls_langindex && parsed[i][0].s == ld.cls_keyword_attrindex) { + } else if (parsed[i][0].l == ld.cls_langindex && parsed[i][0].s == ld.cls_keyword_attrindex) { // This line starts with a UDL keyword const keyword = doc.getText(Range.create(i, parsed[i][0].p, i, parsed[i][0].p + parsed[i][0].c)); @@ -2644,7 +3156,7 @@ export function currentClass(doc: TextDocument, parsed: compressedline[]): strin /** * Ask the client for the text of the file at `uri`. - * + * * @param uri The uri of the file. * @param server The server that doc `uri` is associated with. */ @@ -2655,27 +3167,33 @@ export async function getTextForUri(uri: string, server: ServerSpec): Promise { - const varText = doc.getText(parsed[line][tkn].s == ld.cos_macro_attrindex ? - // Can't use findFullRange() on a macro token because it will capture - // everything, including the trailing dot that triggered the completion. - // A macro token should only occur here for completion requests. - Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c) : - findFullRange(line, parsed, tkn, parsed[line][tkn].p, parsed[line][tkn].p + parsed[line][tkn].c) + const varText = doc.getText( + parsed[line][tkn].s == ld.cos_macro_attrindex + ? // Can't use findFullRange() on a macro token because it will capture + // everything, including the trailing dot that triggered the completion. + // A macro token should only occur here for completion requests. + Range.create(line, parsed[line][tkn].p, line, parsed[line][tkn].p + parsed[line][tkn].c) + : findFullRange(line, parsed, tkn, parsed[line][tkn].p, parsed[line][tkn].p + parsed[line][tkn].c), ); if ([ld.cos_param_attrindex, ld.cos_macro_attrindex].includes(parsed[line][tkn].s)) { // Check if the parameter has a declared type in the formal spec @@ -2685,23 +3203,40 @@ export async function determineVariableClass( } if (parsed[line][tkn].s != ld.cos_localundec_attrindex && parsed[line][tkn].s != ld.cos_param_attrindex) { // Check if the variable is #Dim'd or a known percent variable - const varContext = await determineDeclaredLocalVarClass(doc, parsed, line, varText, server, allfiles, inheritedpackages); + const varContext = await determineDeclaredLocalVarClass( + doc, + parsed, + line, + varText, + server, + allfiles, + inheritedpackages, + ); if (varContext?.baseclass) return varContext.baseclass; } // Fall back to inferring the type from a Set or pass by reference - const localundeccon = await determineUndeclaredLocalVarClass(doc, parsed, line, tkn, varText, server, allfiles, inheritedpackages); + const localundeccon = await determineUndeclaredLocalVarClass( + doc, + parsed, + line, + tkn, + varText, + server, + allfiles, + inheritedpackages, + ); return localundeccon?.baseclass ?? ""; } /** Returns `true` if `keyword` is a valid class member type. */ export function isClassMember(keyword: string): boolean { const keywordUpper = keyword.toUpperCase(); - return classMemberTypes.some(t => t.toUpperCase() == keywordUpper); + return classMemberTypes.some((t) => t.toUpperCase() == keywordUpper); } /** * Get the return type of a method or runtime type of a property. - * + * * @param parsed The semantic tokens of the document. * @param line The line that the member is in. * @param tkn The token of the member in the line. @@ -2709,7 +3244,14 @@ export function isClassMember(keyword: string): boolean { * @param member The name of the member. * @param server The server that this document is associated with. */ -export async function getMemberType(parsed: compressedline[], line: number, tkn: number, cls: string, member: string, server: ServerSpec): Promise { +export async function getMemberType( + parsed: compressedline[], + line: number, + tkn: number, + cls: string, + member: string, + server: ServerSpec, +): Promise { if ( // We assume these methods always return an instance of the class (["%New", "%Open", "%OpenId"].includes(member) && parsed[line][tkn].s != ld.cos_attr_attrindex) || @@ -2721,29 +3263,30 @@ export async function getMemberType(parsed: compressedline[], line: number, tkn: let result = ""; const data: QueryData = { query: "", - parameters: [] + parameters: [], }; if (parsed[line][tkn].s == ld.cos_method_attrindex) { // This is a method data.query = "SELECT ReturnType AS Type, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND name = ?"; data.parameters = [cls, member]; - } - else if (parsed[line][tkn].s == ld.cos_attr_attrindex) { + } else if (parsed[line][tkn].s == ld.cos_attr_attrindex) { // This is a property - data.query = "SELECT RuntimeType AS Type, NULL AS Stub FROM %Dictionary.CompiledProperty WHERE Parent = ? AND name = ?"; + data.query = + "SELECT RuntimeType AS Type, NULL AS Stub FROM %Dictionary.CompiledProperty WHERE Parent = ? AND name = ?"; data.parameters = [cls, member]; - } - else { + } else { // This is a generic member if (cls.startsWith("%SYSTEM.")) { // This is always a method data.query = "SELECT ReturnType AS Type, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND name = ?"; data.parameters = [cls, member]; - } - else { + } else { // This can be a method or property - data.query = "SELECT ReturnType AS Type, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND name = ? UNION ALL "; - data.query = data.query.concat("SELECT RuntimeType AS Type, NULL AS Stub FROM %Dictionary.CompiledProperty WHERE Parent = ? AND name = ?"); + data.query = + "SELECT ReturnType AS Type, Stub FROM %Dictionary.CompiledMethod WHERE Parent = ? AND name = ? UNION ALL "; + data.query = data.query.concat( + "SELECT RuntimeType AS Type, NULL AS Stub FROM %Dictionary.CompiledProperty WHERE Parent = ? AND name = ?", + ); data.parameters = [cls, member, cls, member]; } } @@ -2759,24 +3302,28 @@ export async function getMemberType(parsed: compressedline[], line: number, tkn: let stubquery = ""; if (stubarr[2] == "i") { // This is a method generated from an index - stubquery = "SELECT ReturnType AS Type FROM %Dictionary.CompiledIndexMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT ReturnType AS Type FROM %Dictionary.CompiledIndexMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] == "q") { // This is a method generated from a query - stubquery = "SELECT ReturnType AS Type FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT ReturnType AS Type FROM %Dictionary.CompiledQueryMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] == "a") { // This is a method generated from a property - stubquery = "SELECT ReturnType AS Type FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT ReturnType AS Type FROM %Dictionary.CompiledPropertyMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubarr[2] == "n") { // This is a method generated from a constraint - stubquery = "SELECT ReturnType AS Type FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; + stubquery = + "SELECT ReturnType AS Type FROM %Dictionary.CompiledConstraintMethod WHERE Name = ? AND parent->Parent = ? AND parent->Name = ?"; } if (stubquery != "") { const stubrespdata = await makeRESTRequest("POST", 1, "/action/query", server, { query: stubquery, - parameters: [stubarr[1], cls, stubarr[0]] + parameters: [stubarr[1], cls, stubarr[0]], }); if (Array.isArray(stubrespdata?.data?.result?.content) && stubrespdata.data.result.content.length > 0) { // We got data back @@ -2797,7 +3344,7 @@ export async function getMemberType(parsed: compressedline[], line: number, tkn: /** Find the token immediately following the one at [`ln`, `tkn`] */ function nextToken(parsed: compressedline[], ln: number, tkn: number): [number, number] | undefined { - if (tkn < (parsed[ln].length - 1)) return [ln, tkn + 1]; + if (tkn < parsed[ln].length - 1) return [ln, tkn + 1]; let result: [number, number] | undefined; for (let i = ln + 1; i < parsed.length; i++) { if (!parsed[i]?.length) continue; @@ -2808,7 +3355,12 @@ function nextToken(parsed: compressedline[], ln: number, tkn: number): [number, } /** Return the attribute of this XML string if it's Call or Forward in a UrlMap. Else return the empty string. */ -export function urlMapAttribute(doc: TextDocument, parsed: compressedline[], line: number, token: number): "Call" | "Forward" | "" { +export function urlMapAttribute( + doc: TextDocument, + parsed: compressedline[], + line: number, + token: number, +): "Call" | "Forward" | "" { // Determine if we're in a UrlMap XData block let inUrlMap = false; for (let ln = line; ln >= 0; ln--) { @@ -2831,17 +3383,29 @@ export function urlMapAttribute(doc: TextDocument, parsed: compressedline[], lin const prev1 = prevToken(parsed, line, token); const prev2 = prev1 ? prevToken(parsed, prev1[0], prev1[1]) : undefined; if ( - prev1 && prev2 && - parsed[prev1[0]][prev1[1]].l == ld.xml_langindex && parsed[prev1[0]][prev1[1]].s == ld.xml_tagdelim_attrindex && doc.getText(Range.create( - prev1[0], parsed[prev1[0]][prev1[1]].p, - prev1[0], parsed[prev1[0]][prev1[1]].p + parsed[prev1[0]][prev1[1]].c - )) == "=" && - parsed[prev2[0]][prev2[1]].l == ld.xml_langindex && parsed[prev2[0]][prev2[1]].s == ld.xml_attr_attrindex + prev1 && + prev2 && + parsed[prev1[0]][prev1[1]].l == ld.xml_langindex && + parsed[prev1[0]][prev1[1]].s == ld.xml_tagdelim_attrindex && + doc.getText( + Range.create( + prev1[0], + parsed[prev1[0]][prev1[1]].p, + prev1[0], + parsed[prev1[0]][prev1[1]].p + parsed[prev1[0]][prev1[1]].c, + ), + ) == "=" && + parsed[prev2[0]][prev2[1]].l == ld.xml_langindex && + parsed[prev2[0]][prev2[1]].s == ld.xml_attr_attrindex ) { - attr = doc.getText(Range.create( - prev2[0], parsed[prev2[0]][prev2[1]].p, - prev2[0], parsed[prev2[0]][prev2[1]].p + parsed[prev2[0]][prev2[1]].c - )); + attr = doc.getText( + Range.create( + prev2[0], + parsed[prev2[0]][prev2[1]].p, + prev2[0], + parsed[prev2[0]][prev2[1]].p + parsed[prev2[0]][prev2[1]].c, + ), + ); } if (!["Call", "Forward"].includes(attr)) return ""; } @@ -2873,20 +3437,30 @@ export function macroDefToDoc(def: string[], header = false): MarkupContent { const firstLine = stripMppContinue(parts.slice(pound ? 2 : 1).join(" ")); return { kind: MarkupKind.Markdown, - value: `${header ? headerStr : ""}\`\`\`\n${firstLine.length ? firstLine + "\n" : ""}${def.slice(1).map(e => stripMppContinue(e)).join("\n")}\n\`\`\`` + value: `${header ? headerStr : ""}\`\`\`\n${firstLine.length ? firstLine + "\n" : ""}${def + .slice(1) + .map((e) => stripMppContinue(e)) + .join("\n")}\n\`\`\``, }; } /** Return a `RegExp` that can be used to test if a line matches a class member definition */ export function memberRegex(keywords: string, member: string): RegExp { - return new RegExp(`^(?:${keywords.split("").map( - c => /[a-z]/i.test(c) ? `[${c.toUpperCase()}${c.toLowerCase()}]` : c - ).join("")}) ${member}(?:\\(|;| )`); + return new RegExp( + `^(?:${keywords + .split("") + .map((c) => (/[a-z]/i.test(c) ? `[${c.toUpperCase()}${c.toLowerCase()}]` : c)) + .join("")}) ${member}(?:\\(|;| )`, + ); } /** Determine the active parameter number */ export function determineActiveParam(text: string): number { - let activeParam = 0, openParenCount = 0, openBraceCount = 0, inQuote = false, inComment = false; + let activeParam = 0, + openParenCount = 0, + openBraceCount = 0, + inQuote = false, + inComment = false; Array.from(text).forEach((char: string, idx: number) => { switch (char) { case "{": @@ -2901,14 +3475,14 @@ export function determineActiveParam(text: string): number { case ")": if (!inQuote && !inComment) openParenCount--; break; - case "\"": + case '"': if (!inComment) inQuote = !inQuote; break; case "/": - if (!inQuote && !inComment && (idx < text.length - 1) && text[idx + 1] == "*") inComment = true; + if (!inQuote && !inComment && idx < text.length - 1 && text[idx + 1] == "*") inComment = true; break; case "*": - if (inComment && (idx < text.length - 1) && text[idx + 1] == "/") inComment = false; + if (inComment && idx < text.length - 1 && text[idx + 1] == "/") inComment = false; break; case ",": if (!inQuote && !inComment && !openBraceCount && !openParenCount) activeParam++; @@ -2926,8 +3500,10 @@ export async function showInternalForServer(server: ServerSpec): Promise respdata?.data?.result?.content?.length > 0).catch(() => false); + parameters: ["%Library.ConstraintRelationship.cls", 1, 1, 1, 1, 0, 0], + }) + .then((respdata) => respdata?.data?.result?.content?.length > 0) + .catch(() => false); showInternalCache.set(key, result); return result; } diff --git a/server/src/utils/languageDefinitions.ts b/server/src/utils/languageDefinitions.ts index 8d4af8c..74120e8 100644 --- a/server/src/utils/languageDefinitions.ts +++ b/server/src/utils/languageDefinitions.ts @@ -1,12 +1,11 @@ - export const cos_langindex = 0x01; export const sql_langindex = 0x02; export const cls_langindex = 0x03; export const html_langindex = 0x05; export const py_langindex = 0x07; export const xml_langindex = 0x09; -export const javascript_langindex = 0x0B; -export const css_langindex = 0x0F; +export const javascript_langindex = 0x0b; +export const css_langindex = 0x0f; export const error_attrindex = 0x00; export const normal_attrindex = 0x01; @@ -17,11 +16,11 @@ export const cos_clsobj_attrindex = 0x05; export const cos_str_attrindex = 0x06; export const cos_comment_attrindex = 0x07; export const cos_objdot_attrindex = 0x08; -export const cos_ppf_attrindex = 0x0A; -export const cos_ppc_attrindex = 0x0B; -export const cos_macro_attrindex = 0x0C; -export const cos_delim_attrindex = 0x0D; -export const cos_extrfn_attrindex = 0x0F; +export const cos_ppf_attrindex = 0x0a; +export const cos_ppc_attrindex = 0x0b; +export const cos_macro_attrindex = 0x0c; +export const cos_delim_attrindex = 0x0d; +export const cos_extrfn_attrindex = 0x0f; export const cos_sysf_attrindex = 0x11; export const cos_global_attrindex = 0x12; export const cos_indir_attrindex = 0x13; @@ -30,20 +29,20 @@ export const cos_name_attrindex = 0x16; export const cos_number_attrindex = 0x17; export const cos_oper_attrindex = 0x18; export const cos_rtnname_attrindex = 0x19; -export const cos_ssysv_attrindex = 0x1B; -export const cos_sysv_attrindex = 0x1C; -export const cos_html_attrindex = 0x1D; -export const cos_prop_attrindex = 0x1E; -export const cos_clsname_attrindex = 0x1F; +export const cos_ssysv_attrindex = 0x1b; +export const cos_sysv_attrindex = 0x1c; +export const cos_html_attrindex = 0x1d; +export const cos_prop_attrindex = 0x1e; +export const cos_clsname_attrindex = 0x1f; export const cos_command_attrindex = 0x20; export const cos_instvar_attrindex = 0x21; export const cos_method_attrindex = 0x23; export const cos_attr_attrindex = 0x24; export const cos_brace_attrindex = 0x2a; export const cos_js_attrindex = 0x2b; -export const cos_super_attrindex = 0x2D; -export const cos_localdec_attrindex = 0x2E; -export const cos_otw_attrindex = 0x2F; +export const cos_super_attrindex = 0x2d; +export const cos_localdec_attrindex = 0x2e; +export const cos_otw_attrindex = 0x2f; export const cos_param_attrindex = 0x30; export const cos_localundec_attrindex = 0x31; export const cos_dcom_attrindex = 0x33; @@ -66,10 +65,10 @@ export const cls_comment_attrindex = 0x06; export const cls_desc_attrindex = 0x07; export const cls_delim_attrindex = 0x08; export const cls_num_attrindex = 0x09; -export const cls_str_attrindex = 0x0A; -export const cls_iden_attrindex = 0x0B; -export const cls_sqliden_attrindex = 0x0C; -export const cls_rtnname_attrindex = 0x0D; +export const cls_str_attrindex = 0x0a; +export const cls_iden_attrindex = 0x0b; +export const cls_sqliden_attrindex = 0x0c; +export const cls_rtnname_attrindex = 0x0d; export const cls_cparam_attrindex = 0x17; export const cls_param_attrindex = 0x18; export const cls_xmlattrname_attrindex = 0x19; @@ -77,8 +76,8 @@ export const cls_xmlelemname_attrindex = 0x1a; export const html_delim_attrindex = 0x05; export const html_name_attrindex = 0x07; -export const html_str_attrindex = 0x0A; -export const html_tag_attrindex = 0x0B; +export const html_str_attrindex = 0x0a; +export const html_tag_attrindex = 0x0b; export const xml_tagdelim_attrindex = 0x03; export const xml_attr_attrindex = 0x06; diff --git a/server/src/utils/types.ts b/server/src/utils/types.ts index 43a5933..e9c4fdf 100644 --- a/server/src/utils/types.ts +++ b/server/src/utils/types.ts @@ -1,4 +1,4 @@ -import { MarkupContent } from 'vscode-languageserver'; +import { MarkupContent } from "vscode-languageserver"; /// ------ Language Feature Types @@ -8,174 +8,178 @@ import { MarkupContent } from 'vscode-languageserver'; export type LanguageServerConfiguration = { formatting: { commands: { - case: "upper" | "lower" | "word", - length: "short" | "long" - }, + case: "upper" | "lower" | "word"; + length: "short" | "long"; + }; system: { - case: "upper" | "lower" | "word", - length: "short" | "long" - }, - expandClassNames: boolean - }, + case: "upper" | "lower" | "word"; + length: "short" | "long"; + }; + expandClassNames: boolean; + }; hover: { - commands: boolean, - system: boolean, - preprocessor: boolean - }, + commands: boolean; + system: boolean; + preprocessor: boolean; + }; diagnostics: { - routines: boolean, - parameters: boolean, - classes: boolean, - deprecation: boolean, - zutil: boolean, - suppressSyntaxErrors: ("COS" | "SQL" | "CLS" | "HTML" | "PYTHON" | "XML" | "JAVA" | "JAVASCRIPT" | "CSS")[], - sqlReserved: boolean, - undefinedVariables: boolean - }, + routines: boolean; + parameters: boolean; + classes: boolean; + deprecation: boolean; + zutil: boolean; + suppressSyntaxErrors: ("COS" | "SQL" | "CLS" | "HTML" | "PYTHON" | "XML" | "JAVA" | "JAVASCRIPT" | "CSS")[]; + sqlReserved: boolean; + undefinedVariables: boolean; + }; signaturehelp: { - documentation: boolean - }, + documentation: boolean; + }; refactor: { - exceptionVariable: string - }, + exceptionVariable: string; + }; completion: { - showGenerated: boolean, - showDeprecated: boolean - } + showGenerated: boolean; + showDeprecated: boolean; + }; }; /** * Data returned by a query of %Library.RoutineMgr_StudioOpenDialog. */ export type StudioOpenDialogFile = { - Name: string + Name: string; }; /** * Schema of an element in the command documentation file. */ export type CommandDoc = { - label: string; - alias: string[]; - documentation: string[]; - link: string; - insertText?: string; + label: string; + alias: string[]; + documentation: string[]; + link: string; + insertText?: string; }; /** * Structure of request body for HTTP POST /action/query. */ export type QueryData = { - query: string, - parameters: any[] + query: string; + parameters: any[]; }; /** * Context of the method/routine that a macro is in. */ export type MacroContext = { - docname: string, - superclasses: string[], - includes: string[], - includegenerators: string[], - imports: string[], - mode: "" | "generator", - cursor?: string // Only needed for /action/getmacrolist + docname: string; + superclasses: string[]; + includes: string[]; + includegenerators: string[]; + imports: string[]; + mode: "" | "generator"; + cursor?: string; // Only needed for /action/getmacrolist }; /** * Result of a call to parseDimLime(). */ export type DimResult = { - founddim: boolean, - class: string + founddim: boolean; + class: string; }; /** * Class that a member is in and how that class was determined. */ export type ClassMemberContext = { - baseclass: string, - context: "instance" | "class" | "system" | "" + baseclass: string; + context: "instance" | "class" | "system" | ""; }; /** * Schema of an element in a UDL keyword documentation file. */ export type KeywordDoc = { - name: string, - description: string, - type: string, - constraint?: string | string[] + name: string; + description: string; + type: string; + constraint?: string | string[]; }; /** * IRIS server information received from an 'intersystems/server/resolveFromUri' request. */ export type ServerSpec = { - scheme: string, - host: string, - port: number, - pathPrefix: string, - apiVersion: number, - namespace: string, - username: string, - serverName: string, - serverVersion: string, - password: string, - active: boolean + scheme: string; + host: string; + port: number; + pathPrefix: string; + apiVersion: number; + namespace: string; + username: string; + serverName: string; + serverVersion: string; + password: string; + active: boolean; }; /** * Context of the method/routine that a macro is in, including extra information needed for macro expansion. */ export type SignatureHelpMacroContext = { - docname: string, - macroname: string, - superclasses: string[], - includes: string[], - includegenerators: string[], - imports: string[], - mode: string, - arguments: string + docname: string; + macroname: string; + superclasses: string[]; + includes: string[]; + includegenerators: string[]; + imports: string[]; + mode: string; + arguments: string; }; /** * The content of the last SignatureHelp documentation sent and the type of signature that it applies to. */ export type SignatureHelpDocCache = { - doc: MarkupContent, - type: "macro" | "method" + doc: MarkupContent; + type: "macro" | "method"; }; /** * The number of possible classes that this short class name could map to. */ export type PossibleClasses = { - num: number + num: number; }; /// ------ Parser Types export type compresseditem = { /** The numerical index of the language. */ - l: number; - /** The index of the attribute within the array returned by `GetLanguageAttributes()`. */ - s: number; - /** The starting position of this token in the source line. */ - p: number; - /** The length of this token's source. */ - c: number; - /** A short description of the syntax error. It will only be defined if `l` is `1` (ObjectScript) and `s` is `0`. */ - e?: string; + l: number; + /** The index of the attribute within the array returned by `GetLanguageAttributes()`. */ + s: number; + /** The starting position of this token in the source line. */ + p: number; + /** The length of this token's source. */ + c: number; + /** A short description of the syntax error. It will only be defined if `l` is `1` (ObjectScript) and `s` is `0`. */ + e?: string; }; export type compressedline = compresseditem[]; -export type compressedresult = { compressedlinearray: compressedline[], routineheaderinfo?: routineheaderinfotype }; +export type compressedresult = { compressedlinearray: compressedline[]; routineheaderinfo?: routineheaderinfotype }; export type compressedcolors = { compressedcolors: compressedline[] }; - // routine header (if present 'generated' is just set to '') -export type routineheaderinfotype = { routinename: string, routinetype?: string, languagemode?: number, generated?: string }; +export type routineheaderinfotype = { + routinename: string; + routinetype?: string; + languagemode?: number; + generated?: string; +}; diff --git a/server/src/utils/variables.ts b/server/src/utils/variables.ts index 4bf7fcc..55cd5be 100644 --- a/server/src/utils/variables.ts +++ b/server/src/utils/variables.ts @@ -1,6 +1,6 @@ -import { createConnection, SemanticTokensBuilder, TextDocuments } from 'vscode-languageserver/node'; -import { TextDocument } from 'vscode-languageserver-textdocument'; -import { compressedline, LanguageServerConfiguration, ServerSpec } from './types'; +import { createConnection, SemanticTokensBuilder, TextDocuments } from "vscode-languageserver/node"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import { compressedline, LanguageServerConfiguration, ServerSpec } from "./types"; /** * TextDocument URI's mapped to the tokenized representation of the document. @@ -33,24 +33,24 @@ export const serverSpecs: Map = new Map(); export const corePropertyParams = [ { name: "CALCSELECTIVITY", - desc: `Controls whether the Tune Table facility calculates the *selectivity* for a property. Usually it is best to leave this parameter as the default (1).` + desc: `Controls whether the Tune Table facility calculates the *selectivity* for a property. Usually it is best to leave this parameter as the default (1).`, }, { name: "CAPTION", - desc: `Caption to use for this property in client applications.` + desc: `Caption to use for this property in client applications.`, }, { name: "EXTERNALSQLNAME", - desc: `Used in linked tables, this parameter specifies the name of the field in the external table to which this property is linked.` + desc: `Used in linked tables, this parameter specifies the name of the field in the external table to which this property is linked.`, }, { name: "EXTERNALSQLTYPE", - desc: `Used in linked tables, this parameter specifies the SQL type of the field in the external table to which this property is linked.` + desc: `Used in linked tables, this parameter specifies the SQL type of the field in the external table to which this property is linked.`, }, { name: "JAVATYPE", - desc: `The Java data type to which this property is projected.` - } + desc: `The Java data type to which this property is projected.`, + }, ]; /** @@ -61,81 +61,243 @@ export const languageServerSettings: Map = /** * Notable `$ZUTIL` functions. */ -export const zutilFunctions: { deprecated: string[]; replace: { [func: string]: string }; noReplace: string[]; } = { +export const zutilFunctions: { deprecated: string[]; replace: { [func: string]: string }; noReplace: string[] } = { /** Functions that are deprecated. */ deprecated: [ - "67,1,","68,6,","68,27,","68,39,","68,55,","69,6,","69,13,","69,14,","69,19,","69,20,","69,27,", - "69,28,","69,31,","69,35,","69,39,","69,55,","69,67,","78,28,","90,4,","100)","113)","130,","133," + "67,1,", + "68,6,", + "68,27,", + "68,39,", + "68,55,", + "69,6,", + "69,13,", + "69,14,", + "69,19,", + "69,20,", + "69,27,", + "69,28,", + "69,31,", + "69,35,", + "69,39,", + "69,55,", + "69,67,", + "78,28,", + "90,4,", + "100)", + "113)", + "130,", + "133,", ], /** Functions that can be replaced by ClassMethods. */ replace: { - "4,":"%SYSTEM.Process_Terminate","18,":"%SYSTEM.Process_Undefined","18)":"%SYSTEM.Process_Undefined", - "20,":"%SYSTEM.Process_UserRoutinePath","20)":"%SYSTEM.Process_UserRoutinePath","21)":"%SYSTEM.Process_PrivateGlobalLocation", - "21,0)":"%SYSTEM.Process_PrivateGlobalLocation","21,1)":"%SYSTEM.Process_KillAllPrivateGlobals","21,2)":"%SYSTEM.Process_KillAllPrivateGlobals", - "22,0,":"%Device_SetFFBS","22,0)":"%Device_SetFFBS","28,":"%SYSTEM.Util_Collation","39,":"%SYSTEM.Process_SysRoutinePath", - "39)":"%SYSTEM.Process_SysRoutinePath","53)":"%SYSTEM.INetInfo_TCPName","53,":"%SYSTEM.INetInfo_TCPStats", - "55,":"%SYSTEM.Process_LanguageMode","55)":"%SYSTEM.Process_LanguageMode","56,2)":"%SYSTEM.Process_ErrorLine", - "56,6)":"%SYSTEM.Process_OSError","67,0,":"%SYSTEM.Process_IsGhost","67,4,":"%SYSTEM.Process_State", - "67,5,":"%SYSTEM.Process_Routine","67,6,":"%SYSTEM.Process_NameSpace","67,7,":"%SYSTEM.Process_CurrentDevice", - "67,8,":"%SYSTEM.Process_LinesExecuted","67,5)":"%SYSTEM.Process_Routine","67,6)":"%SYSTEM.Process_NameSpace", - "67,7)":"%SYSTEM.Process_CurrentDevice","67,8)":"%SYSTEM.Process_LinesExecuted","67,9,":"%SYSTEM.Process_GlobalReferences", - "67,10,":"%SYSTEM.Process_JobType","67,11,":"%SYSTEM.Process_UserName","67,12,":"%SYSTEM.Process_ClientNodeName", - "67,13,":"%SYSTEM.Process_ClientExecutableName","67,14,":"%SYSTEM.Process_CSPSessionID","67,15,":"%SYSTEM.Process_ClientIPAddress", - "67,9)":"%SYSTEM.Process_GlobalReferences","67,10)":"%SYSTEM.Process_JobType","67,11)":"%SYSTEM.Process_UserName", - "67,12)":"%SYSTEM.Process_ClientNodeName","67,13)":"%SYSTEM.Process_ClientExecutableName","67,14)":"%SYSTEM.Process_CSPSessionID", - "67,15)":"%SYSTEM.Process_ClientIPAddress","71,":"%SYSTEM.Process_FixedDate","71)":"%SYSTEM.Process_FixedDate", - "78,23,":"%SYS.Journal.File_PurgeOne","78,29)":"%SYS.Journal.System_Sync","78,40)":"%SYS.Journal.System_WhereCommitted", - "82,12,":"%Device_ReDirectIO","82,12)":"%Device_ReDirectIO","90,10,":"%SYS.Namespace_Exists", - "94,":"%SYSTEM.Process_Broadcast","96,3,":"%SYSTEM.Process_ThrowError","96,4,":"%SYSTEM.Process_IODollarTest", - "96,9)":"%SYSTEM.Process_CallingRoutine","96,10)":"%SYSTEM.Process_CallingDatabase","96,14)":"%Device_GetType", - "110)":"%SYS.System_GetNodeName","114,":"%SYSTEM.INetInfo_EthernetAddress","128,1)":"%SYSTEM.Process_StepInfo", - "132)":"%Device_ChangePrincipal","140,7,":"%File_Attributes","147,":"%File_NormalizeFilenameWithSpaces", - "158,0)":"%Device_InstalledPrinters","158,1,":"%Device_InstalledPrinters","168,":"%SYSTEM.Process_CurrentDirectory", - "168)":"%SYSTEM.Process_CurrentDirectory","186,":"%SYSTEM.Process_TerminalPrompt","186)":"%SYSTEM.Process_TerminalPrompt", - "189)":"%SYSTEM.INetInfo_Connected","68,1,":"%SYSTEM.Process_NullSubscripts","68,1)":"%SYSTEM.Process_NullSubscripts", - "68,2,":"%SYSTEM.Process_OpenMode","68,2)":"%SYSTEM.Process_OpenMode","68,3,":"%SYSTEM.Process_FileMode", - "68,3)":"%SYSTEM.Process_FileMode","68,5,":"%SYSTEM.Process_BreakMode","68,5)":"%SYSTEM.Process_BreakMode", - "68,7,":"%SYSTEM.Process_RefInKind","68,7)":"%SYSTEM.Process_RefInKind","68,11,":"%SYSTEM.Process_LineRecall", - "68,11)":"%SYSTEM.Process_LineRecall","68,15,":"%SYSTEM.Process_DisconnectErr","68,15)":"%SYSTEM.Process_DisconnectErr", - "68,21,":"%SYSTEM.Process_SynchCommit","68,21)":"%SYSTEM.Process_SynchCommit","68,22,":"%SYSTEM.Process_DX", - "68,22)":"%SYSTEM.Process_DX","68,25,":"%SYSTEM.Process_BatchFlag","68,25)":"%SYSTEM.Process_BatchFlag", - "68,28,":"%SYSTEM.Process_GlobalKillDisabled","68,28)":"%SYSTEM.Process_GlobalKillDisabled","68,30,":"%SYSTEM.Process_PopError", - "68,30)":"%SYSTEM.Process_PopError","68,32,":"%SYSTEM.Process_ZDateNull","68,32)":"%SYSTEM.Process_ZDateNull", - "68,34,":"%SYSTEM.Process_AsynchError","68,34)":"%SYSTEM.Process_AsynchError","68,40,":"%SYSTEM.Process_SetZEOF", - "68,40)":"%SYSTEM.Process_SetZEOF","68,42,":"%SYSTEM.Process_NodeNameInPid","68,42)":"%SYSTEM.Process_NodeNameInPid", - "68,43,":"%SYSTEM.Process_OldZU5","68,43)":"%SYSTEM.Process_OldZU5","68,45,":"%SYSTEM.Process_TruncateOverflow", - "68,45)":"%SYSTEM.Process_TruncateOverflow","68,51,":"%SYSTEM.Process_SwitchOSdir","68,51)":"%SYSTEM.Process_SwitchOSdir", - "68,60,":"%SYSTEM.Process_AsyncDisconnectErr","68,60)":"%SYSTEM.Process_AsyncDisconnectErr","68,63,":"%SYSTEM.Process_ScientificNotation", - "68,63)":"%SYSTEM.Process_ScientificNotation","68,66,":"%SYSTEM.Process_TelnetNUL","68,66)":"%SYSTEM.Process_TelnetNUL", - "68,67,":"%SYSTEM.Process_ExceptionLog","68,67)":"%SYSTEM.Process_ExceptionLog","68,70,":"%SYSTEM.Process_IEEEError", - "68,70)":"%SYSTEM.Process_IEEEError","68,71,":"%SYSTEM.Process_IPv6Format","68,71)":"%SYSTEM.Process_IPv6Format", - "68,72,":"%SYSTEM.Process_MVUndefined","68,72)":"%SYSTEM.Process_MVUndefined" + "4,": "%SYSTEM.Process_Terminate", + "18,": "%SYSTEM.Process_Undefined", + "18)": "%SYSTEM.Process_Undefined", + "20,": "%SYSTEM.Process_UserRoutinePath", + "20)": "%SYSTEM.Process_UserRoutinePath", + "21)": "%SYSTEM.Process_PrivateGlobalLocation", + "21,0)": "%SYSTEM.Process_PrivateGlobalLocation", + "21,1)": "%SYSTEM.Process_KillAllPrivateGlobals", + "21,2)": "%SYSTEM.Process_KillAllPrivateGlobals", + "22,0,": "%Device_SetFFBS", + "22,0)": "%Device_SetFFBS", + "28,": "%SYSTEM.Util_Collation", + "39,": "%SYSTEM.Process_SysRoutinePath", + "39)": "%SYSTEM.Process_SysRoutinePath", + "53)": "%SYSTEM.INetInfo_TCPName", + "53,": "%SYSTEM.INetInfo_TCPStats", + "55,": "%SYSTEM.Process_LanguageMode", + "55)": "%SYSTEM.Process_LanguageMode", + "56,2)": "%SYSTEM.Process_ErrorLine", + "56,6)": "%SYSTEM.Process_OSError", + "67,0,": "%SYSTEM.Process_IsGhost", + "67,4,": "%SYSTEM.Process_State", + "67,5,": "%SYSTEM.Process_Routine", + "67,6,": "%SYSTEM.Process_NameSpace", + "67,7,": "%SYSTEM.Process_CurrentDevice", + "67,8,": "%SYSTEM.Process_LinesExecuted", + "67,5)": "%SYSTEM.Process_Routine", + "67,6)": "%SYSTEM.Process_NameSpace", + "67,7)": "%SYSTEM.Process_CurrentDevice", + "67,8)": "%SYSTEM.Process_LinesExecuted", + "67,9,": "%SYSTEM.Process_GlobalReferences", + "67,10,": "%SYSTEM.Process_JobType", + "67,11,": "%SYSTEM.Process_UserName", + "67,12,": "%SYSTEM.Process_ClientNodeName", + "67,13,": "%SYSTEM.Process_ClientExecutableName", + "67,14,": "%SYSTEM.Process_CSPSessionID", + "67,15,": "%SYSTEM.Process_ClientIPAddress", + "67,9)": "%SYSTEM.Process_GlobalReferences", + "67,10)": "%SYSTEM.Process_JobType", + "67,11)": "%SYSTEM.Process_UserName", + "67,12)": "%SYSTEM.Process_ClientNodeName", + "67,13)": "%SYSTEM.Process_ClientExecutableName", + "67,14)": "%SYSTEM.Process_CSPSessionID", + "67,15)": "%SYSTEM.Process_ClientIPAddress", + "71,": "%SYSTEM.Process_FixedDate", + "71)": "%SYSTEM.Process_FixedDate", + "78,23,": "%SYS.Journal.File_PurgeOne", + "78,29)": "%SYS.Journal.System_Sync", + "78,40)": "%SYS.Journal.System_WhereCommitted", + "82,12,": "%Device_ReDirectIO", + "82,12)": "%Device_ReDirectIO", + "90,10,": "%SYS.Namespace_Exists", + "94,": "%SYSTEM.Process_Broadcast", + "96,3,": "%SYSTEM.Process_ThrowError", + "96,4,": "%SYSTEM.Process_IODollarTest", + "96,9)": "%SYSTEM.Process_CallingRoutine", + "96,10)": "%SYSTEM.Process_CallingDatabase", + "96,14)": "%Device_GetType", + "110)": "%SYS.System_GetNodeName", + "114,": "%SYSTEM.INetInfo_EthernetAddress", + "128,1)": "%SYSTEM.Process_StepInfo", + "132)": "%Device_ChangePrincipal", + "140,7,": "%File_Attributes", + "147,": "%File_NormalizeFilenameWithSpaces", + "158,0)": "%Device_InstalledPrinters", + "158,1,": "%Device_InstalledPrinters", + "168,": "%SYSTEM.Process_CurrentDirectory", + "168)": "%SYSTEM.Process_CurrentDirectory", + "186,": "%SYSTEM.Process_TerminalPrompt", + "186)": "%SYSTEM.Process_TerminalPrompt", + "189)": "%SYSTEM.INetInfo_Connected", + "68,1,": "%SYSTEM.Process_NullSubscripts", + "68,1)": "%SYSTEM.Process_NullSubscripts", + "68,2,": "%SYSTEM.Process_OpenMode", + "68,2)": "%SYSTEM.Process_OpenMode", + "68,3,": "%SYSTEM.Process_FileMode", + "68,3)": "%SYSTEM.Process_FileMode", + "68,5,": "%SYSTEM.Process_BreakMode", + "68,5)": "%SYSTEM.Process_BreakMode", + "68,7,": "%SYSTEM.Process_RefInKind", + "68,7)": "%SYSTEM.Process_RefInKind", + "68,11,": "%SYSTEM.Process_LineRecall", + "68,11)": "%SYSTEM.Process_LineRecall", + "68,15,": "%SYSTEM.Process_DisconnectErr", + "68,15)": "%SYSTEM.Process_DisconnectErr", + "68,21,": "%SYSTEM.Process_SynchCommit", + "68,21)": "%SYSTEM.Process_SynchCommit", + "68,22,": "%SYSTEM.Process_DX", + "68,22)": "%SYSTEM.Process_DX", + "68,25,": "%SYSTEM.Process_BatchFlag", + "68,25)": "%SYSTEM.Process_BatchFlag", + "68,28,": "%SYSTEM.Process_GlobalKillDisabled", + "68,28)": "%SYSTEM.Process_GlobalKillDisabled", + "68,30,": "%SYSTEM.Process_PopError", + "68,30)": "%SYSTEM.Process_PopError", + "68,32,": "%SYSTEM.Process_ZDateNull", + "68,32)": "%SYSTEM.Process_ZDateNull", + "68,34,": "%SYSTEM.Process_AsynchError", + "68,34)": "%SYSTEM.Process_AsynchError", + "68,40,": "%SYSTEM.Process_SetZEOF", + "68,40)": "%SYSTEM.Process_SetZEOF", + "68,42,": "%SYSTEM.Process_NodeNameInPid", + "68,42)": "%SYSTEM.Process_NodeNameInPid", + "68,43,": "%SYSTEM.Process_OldZU5", + "68,43)": "%SYSTEM.Process_OldZU5", + "68,45,": "%SYSTEM.Process_TruncateOverflow", + "68,45)": "%SYSTEM.Process_TruncateOverflow", + "68,51,": "%SYSTEM.Process_SwitchOSdir", + "68,51)": "%SYSTEM.Process_SwitchOSdir", + "68,60,": "%SYSTEM.Process_AsyncDisconnectErr", + "68,60)": "%SYSTEM.Process_AsyncDisconnectErr", + "68,63,": "%SYSTEM.Process_ScientificNotation", + "68,63)": "%SYSTEM.Process_ScientificNotation", + "68,66,": "%SYSTEM.Process_TelnetNUL", + "68,66)": "%SYSTEM.Process_TelnetNUL", + "68,67,": "%SYSTEM.Process_ExceptionLog", + "68,67)": "%SYSTEM.Process_ExceptionLog", + "68,70,": "%SYSTEM.Process_IEEEError", + "68,70)": "%SYSTEM.Process_IEEEError", + "68,71,": "%SYSTEM.Process_IPv6Format", + "68,71)": "%SYSTEM.Process_IPv6Format", + "68,72,": "%SYSTEM.Process_MVUndefined", + "68,72)": "%SYSTEM.Process_MVUndefined", }, /** Functions that cannot be easily replaced automatically. */ noReplace: [ - "5,","9,","12,","15,","49,","62,","67,0)","67,4)","78,21)","86)","78,22,","96,5,","115,11,", - "140,1,","188)","193,","69,0,","69,1,","69,2,","69,3,","69,5,","69,7,","69,8,","69,10,", - "69,11,","69,15,","69,21,","69,22,","69,26,","69,30,","69,32,","69,34,","69,37,","69,40,", - "69,42,","69,43,","69,44,","69,45,","69,49,","69,51,","69,60,","69,63,","69,66,","69,68,", - "69,69,","69,70,","69,71,","69,72,","68,26,","68,26)" - ] + "5,", + "9,", + "12,", + "15,", + "49,", + "62,", + "67,0)", + "67,4)", + "78,21)", + "86)", + "78,22,", + "96,5,", + "115,11,", + "140,1,", + "188)", + "193,", + "69,0,", + "69,1,", + "69,2,", + "69,3,", + "69,5,", + "69,7,", + "69,8,", + "69,10,", + "69,11,", + "69,15,", + "69,21,", + "69,22,", + "69,26,", + "69,30,", + "69,32,", + "69,34,", + "69,37,", + "69,40,", + "69,42,", + "69,43,", + "69,44,", + "69,45,", + "69,49,", + "69,51,", + "69,60,", + "69,63,", + "69,66,", + "69,68,", + "69,69,", + "69,70,", + "69,71,", + "69,72,", + "68,26,", + "68,26)", + ], }; /** Languages supported by `isclexer.node` */ -export const lexerLanguages: { moniker: string; index: number; }[] = [ - { moniker: 'CLS', index: 3 }, - { moniker: 'COS', index: 1 }, - { moniker: 'XML', index: 9 }, - { moniker: 'CSS', index: 15 }, - { moniker: 'HTML', index: 5 }, - { moniker: 'JAVA', index: 13 }, - { moniker: 'JAVASCRIPT', index: 11 }, - { moniker: 'SQL', index: 2 }, - { moniker: 'PYTHON', index: 7 } +export const lexerLanguages: { moniker: string; index: number }[] = [ + { moniker: "CLS", index: 3 }, + { moniker: "COS", index: 1 }, + { moniker: "XML", index: 9 }, + { moniker: "CSS", index: 15 }, + { moniker: "HTML", index: 5 }, + { moniker: "JAVA", index: 13 }, + { moniker: "JAVASCRIPT", index: 11 }, + { moniker: "SQL", index: 2 }, + { moniker: "PYTHON", index: 7 }, ]; /** All class member types */ -export const classMemberTypes: string[] = ["Parameter","Property","Relationship","ForeignKey","Index","Query","Storage","Trigger","XData","Projection","Method","ClassMethod","ClientMethod"]; +export const classMemberTypes: string[] = [ + "Parameter", + "Property", + "Relationship", + "ForeignKey", + "Index", + "Query", + "Storage", + "Trigger", + "XData", + "Projection", + "Method", + "ClassMethod", + "ClientMethod", +]; /** Regex for testing if a MPP directive is `##Continue` */ export const mppContinue: RegExp = /^(?:##)?continue$/i; From 63e6a3010e3fdc4520793d35fccc9c77ab7f3b55 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 14:23:40 -0400 Subject: [PATCH 09/13] line breaks --- .gitignore | 2 +- .prettierignore | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 71dd22a..90456bc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ **/out/ *.vsix /server/lib/isclexer.node -**/tsconfig.tsbuildinfo \ No newline at end of file +**/tsconfig.tsbuildinfo diff --git a/.prettierignore b/.prettierignore index fcf999d..42eaec0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,4 @@ **/*.md **/*.js **/*.mjs -**/*.yml \ No newline at end of file +**/*.yml diff --git a/package.json b/package.json index d0b76d2..204e186 100644 --- a/package.json +++ b/package.json @@ -1819,4 +1819,4 @@ "useTabs": true, "printWidth": 120 } -} \ No newline at end of file +} From 9361c61d14d778fdeef6b2d1ed46e3afed42f84a Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 14:24:24 -0400 Subject: [PATCH 10/13] line break --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index a092747..e5aa540 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,4 +8,4 @@ "path": "./server" } ], -} \ No newline at end of file +} From a8cc10837748b8e967725ac06e393f5cc506a854 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 14:27:06 -0400 Subject: [PATCH 11/13] rm globals --- package-lock.json | 20 -------------------- package.json | 1 - 2 files changed, 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc414ef..528508d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "@types/turndown": "^5.0.6", "@vscode/vsce": "^3.7.1", "eslint": "^10.2.1", - "globals": "^17.5.0", "merge-options": "^3.0.4", "node-loader": "^2.1.0", "ovsx": "^0.10.11", @@ -3656,19 +3655,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globals": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -9265,12 +9251,6 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true }, - "globals": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", - "dev": true - }, "globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", diff --git a/package.json b/package.json index 204e186..ccaecad 100644 --- a/package.json +++ b/package.json @@ -1800,7 +1800,6 @@ "@types/turndown": "^5.0.6", "@vscode/vsce": "^3.7.1", "eslint": "^10.2.1", - "globals": "^17.5.0", "merge-options": "^3.0.4", "node-loader": "^2.1.0", "ovsx": "^0.10.11", From 1163fd4633a0c7f17cfecfb5be5c19ab92e3fbb6 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Thu, 23 Apr 2026 14:28:50 -0400 Subject: [PATCH 12/13] newline char --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 29e90f7..bd0725c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -13,4 +13,4 @@ export default tseslint.config( "no-control-regex": "off", }, } -); \ No newline at end of file +); From e641ea6a1587d812f8236e3caa015757cde01972 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Fri, 24 Apr 2026 09:40:30 -0400 Subject: [PATCH 13/13] add .prettierignore to .vscodeignore --- .vscodeignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscodeignore b/.vscodeignore index 4248701..ae09c7a 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -18,3 +18,4 @@ images/*.gif **/*.vsix **/package-lock.json eslint.config.mjs +.prettierignore \ No newline at end of file