From c79a4a1f39cb07c812626ce5d73fc75434daf16c Mon Sep 17 00:00:00 2001 From: Adrian Morales Date: Fri, 17 Nov 2023 20:33:05 -0600 Subject: [PATCH 01/17] File definition is added --- package-lock.json | 4 ++-- schemas/rpgle.code-snippets | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e2c6570..9fd07fa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-rpgle", - "version": "0.20.2", + "version": "0.23.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-rpgle", - "version": "0.20.2", + "version": "0.23.0", "hasInstallScript": true, "license": "MIT", "devDependencies": { diff --git a/schemas/rpgle.code-snippets b/schemas/rpgle.code-snippets index d03d17e1..07672c97 100644 --- a/schemas/rpgle.code-snippets +++ b/schemas/rpgle.code-snippets @@ -213,6 +213,13 @@ " CLOSE $1;", ] }, + "File definition": { + "prefix": "dcl-f", + "description": "File definition statement", + "body": [ + "dcl-f ${1:name} usage(${2|*input,*output,*update,*delete|}) keyed;", + ] + }, "workstation-basics": { "prefix": "workstation-basics", "description": "The basic definitions for a display file with a subfield", From 12d1bbbc9f52f9e40f9710c1fd88d8b0f1ee26bb Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 20 Jan 2024 11:40:05 -0500 Subject: [PATCH 02/17] Initial work on extract procedure --- .../src/providers/linter/codeActions.ts | 9 ++- .../server/src/providers/linter/index.ts | 77 +++++++++++++++++++ language/models/cache.ts | 31 ++++++-- package-lock.json | 4 +- 4 files changed, 111 insertions(+), 10 deletions(-) diff --git a/extension/server/src/providers/linter/codeActions.ts b/extension/server/src/providers/linter/codeActions.ts index 0001c91c..50c4aa29 100644 --- a/extension/server/src/providers/linter/codeActions.ts +++ b/extension/server/src/providers/linter/codeActions.ts @@ -1,5 +1,5 @@ -import { CodeAction, CodeActionParams, Range } from 'vscode-languageserver'; -import { getActions, refreshLinterDiagnostics } from '.'; +import { CodeAction, CodeActionKind, CodeActionParams, Range } from 'vscode-languageserver'; +import { getActions, getExtractProcedureAction, refreshLinterDiagnostics } from '.'; import { documents, parser } from '..'; export default async function codeActionsProvider(params: CodeActionParams): Promise { @@ -13,6 +13,11 @@ export default async function codeActionsProvider(params: CodeActionParams): Pro const docs = await parser.getDocs(document.uri); if (docs) { + const extractOption = getExtractProcedureAction(document, docs, range); + if (extractOption) { + return [extractOption]; + } + const detail = await refreshLinterDiagnostics(document, docs, false); if (detail) { const fixErrors = detail.errors.filter(error => diff --git a/extension/server/src/providers/linter/index.ts b/extension/server/src/providers/linter/index.ts index a7c1fcd8..5100256b 100644 --- a/extension/server/src/providers/linter/index.ts +++ b/extension/server/src/providers/linter/index.ts @@ -431,4 +431,81 @@ export function getActions(document: TextDocument, errors: IssueRange[]) { }); return actions; +} + +export function getExtractProcedureAction(document: TextDocument, docs: Cache, range: Range): CodeAction|undefined { + if (range.end.line > range.start.line) { + const references = docs.referencesInRange({position: document.offsetAt(range.start), end: document.offsetAt(range.end)}); + const validRefs = references.filter(ref => [`struct`, `subitem`, `variable`].includes(ref.dec.type)); + + if (validRefs.length > 0) { + const linesRange = Range.create(range.start.line, 0, range.end.line, 1000); + const lastLine = document.offsetAt({line: document.lineCount, character: 0}); + + const nameDiffSize = 1; // Always once since we only add 'p' at the start + const newParamNames = validRefs.map(ref => `p${ref.dec.name}`); + let procedureBody = document.getText(range); + + // Fix the found offset lengths to be relative to the new procedure + for (let i = validRefs.length - 1; i >= 0; i--) { + for (let y = validRefs[i].refs.length - 1; y >= 0; y--) { + validRefs[i].refs[y] = { + position: validRefs[i].refs[y].position - document.offsetAt(range.start), + end: validRefs[i].refs[y].end - document.offsetAt(range.start) + }; + } + } + + // Then let's fix the references to use the new names + for (let i = validRefs.length - 1; i >= 0; i--) { + for (let y = validRefs[i].refs.length - 1; y >= 0; y--) { + const ref = validRefs[i].refs[y]; + + procedureBody = procedureBody.slice(0, ref.position) + newParamNames[i] + procedureBody.slice(ref.end); + + // Then we need to update the offset of the next references + for (let z = i + 1; z < validRefs.length; z++) { + for (let x = validRefs[z].refs.length - 1; x >= 0; x--) { + if (validRefs[z].refs[x].position > ref.position) { + validRefs[z].refs[x] = { + position: validRefs[z].refs[x].position + (newParamNames[i].length - nameDiffSize), + end: validRefs[z].refs[x].end + (newParamNames[i].length - nameDiffSize) + }; + } + } + } + } + } + + const newProcedure = [ + `Dcl-Proc NewProcedure;`, + ` Dcl-Pi *N;`, + ...validRefs.map((ref, i) => ` ${newParamNames[i]} ${ref.dec.type === `struct` ? `LikeDS` : `Like`}(${ref.dec.name});`), + ` End-Pi;`, + ``, + procedureBody, + `End-Proc;` + ].join(`\n`) + + const newAction = CodeAction.create(`Extract to new procedure`, CodeActionKind.RefactorExtract); + + // First do the exit + newAction.edit = { + changes: { + [document.uri]: [ + TextEdit.replace(linesRange, `NewProcedure(${validRefs.map(r => r.dec.name).join(`:`)});`), + TextEdit.insert(document.positionAt(lastLine), `\n\n`+newProcedure) + ] + }, + }; + + // Then format the document + newAction.command = { + command: `editor.action.formatDocument`, + title: `Format` + }; + + return newAction; + } + } } \ No newline at end of file diff --git a/language/models/cache.ts b/language/models/cache.ts index d2f723e7..1d013d44 100644 --- a/language/models/cache.ts +++ b/language/models/cache.ts @@ -1,5 +1,5 @@ import { indicators1 } from "../../tests/suite"; -import { CacheProps, IncludeStatement, Keywords } from "../parserTypes"; +import { CacheProps, IncludeStatement, Keywords, Offset } from "../parserTypes"; import Declaration from "./declaration"; const newInds = () => { @@ -199,18 +199,37 @@ export default class Cache { } } - static referenceByOffset(scope: Cache, offset: number): Declaration|undefined { + referencesInRange(range: Offset): { dec: Declaration, refs: Offset[] }[] { + let list: { dec: Declaration, refs: Offset[] }[] = []; + + for (let i = range.position; i <= range.end; i++) { + const ref = Cache.referenceByOffset(this, i); + if (ref) { + // No duplicates allowed + if (list.some(item => item.dec.name === ref.name)) continue; + + list.push({ + dec: ref, + refs: ref.references.filter(r => r.offset.position >= range.position && r.offset.end <= range.end).map(r => r.offset) + }) + }; + } + + return list; + } + + static referenceByOffset(scope: Cache, offset: number): Declaration | undefined { const props: (keyof Cache)[] = [`parameters`, `subroutines`, `procedures`, `files`, `variables`, `structs`, `constants`, `indicators`]; - + for (const prop of props) { const list = scope[prop] as unknown as Declaration[]; for (const def of list) { let possibleRef: boolean; - + // Search top level possibleRef = def.references.some(r => offset >= r.offset.position && offset <= r.offset.end); if (possibleRef) return def; - + // Search any subitems if (def.subItems.length > 0) { for (const subItem of def.subItems) { @@ -218,7 +237,7 @@ export default class Cache { if (possibleRef) return subItem; } } - + // Search scope if any if (def.scope) { const inScope = Cache.referenceByOffset(def.scope, offset); diff --git a/package-lock.json b/package-lock.json index 2e2c6570..e4a68173 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-rpgle", - "version": "0.20.2", + "version": "0.24.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-rpgle", - "version": "0.20.2", + "version": "0.24.0", "hasInstallScript": true, "license": "MIT", "devDependencies": { From af780ea00afd2b91d516da89b6038eecf9457ca1 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 20 Jan 2024 12:46:02 -0500 Subject: [PATCH 03/17] Package upgrades --- extension/client/package-lock.json | 14 +++++++------- extension/client/package.json | 2 +- package-lock.json | 28 ++++++++++++++-------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/extension/client/package-lock.json b/extension/client/package-lock.json index b96a7bf3..1d167961 100644 --- a/extension/client/package-lock.json +++ b/extension/client/package-lock.json @@ -12,7 +12,7 @@ "vscode-languageclient": "^7.0.0" }, "devDependencies": { - "@halcyontech/vscode-ibmi-types": "^2.1.0", + "@halcyontech/vscode-ibmi-types": "^2.6.5", "@types/vscode": "^1.63.0", "@vscode/test-electron": "^2.1.2" }, @@ -21,9 +21,9 @@ } }, "node_modules/@halcyontech/vscode-ibmi-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@halcyontech/vscode-ibmi-types/-/vscode-ibmi-types-2.1.0.tgz", - "integrity": "sha512-vmz3CFHoJT8m0iQhghnSY9LC/BIKDP0Dr/CcOVRUS16uVtWP82Sf+lICV4XeIcak4f3FRklDCcIbgkywwv/9lQ==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@halcyontech/vscode-ibmi-types/-/vscode-ibmi-types-2.6.5.tgz", + "integrity": "sha512-5LNp2FuHlufJDl5IREqdHJ942DWi2O9/ysSElV8FLB3I0Dpvg8LyzISwV9OZ+O97yk8G45OClXMAtF3w+tWEeQ==", "dev": true }, "node_modules/@tootallnate/once": { @@ -508,9 +508,9 @@ }, "dependencies": { "@halcyontech/vscode-ibmi-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@halcyontech/vscode-ibmi-types/-/vscode-ibmi-types-2.1.0.tgz", - "integrity": "sha512-vmz3CFHoJT8m0iQhghnSY9LC/BIKDP0Dr/CcOVRUS16uVtWP82Sf+lICV4XeIcak4f3FRklDCcIbgkywwv/9lQ==", + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@halcyontech/vscode-ibmi-types/-/vscode-ibmi-types-2.6.5.tgz", + "integrity": "sha512-5LNp2FuHlufJDl5IREqdHJ942DWi2O9/ysSElV8FLB3I0Dpvg8LyzISwV9OZ+O97yk8G45OClXMAtF3w+tWEeQ==", "dev": true }, "@tootallnate/once": { diff --git a/extension/client/package.json b/extension/client/package.json index e69d6c8e..415273f9 100644 --- a/extension/client/package.json +++ b/extension/client/package.json @@ -12,7 +12,7 @@ "vscode-languageclient": "^7.0.0" }, "devDependencies": { - "@halcyontech/vscode-ibmi-types": "^2.1.0", + "@halcyontech/vscode-ibmi-types": "^2.6.5", "@types/vscode": "^1.63.0", "@vscode/test-electron": "^2.1.2" } diff --git a/package-lock.json b/package-lock.json index 2e2c6570..80b622b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-rpgle", - "version": "0.20.2", + "version": "0.24.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-rpgle", - "version": "0.20.2", + "version": "0.24.0", "hasInstallScript": true, "license": "MIT", "devDependencies": { @@ -3589,9 +3589,9 @@ } }, "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4078,9 +4078,9 @@ "dev": true }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -6598,9 +6598,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -6939,9 +6939,9 @@ "dev": true }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "workerpool": { From 5cf6248a4bc2d714b6f9a047573fb6b74b4f8e81 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 20 Jan 2024 13:09:20 -0500 Subject: [PATCH 04/17] Improved logic for checking if rpglint exists (#292) --- extension/client/src/linter.ts | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/extension/client/src/linter.ts b/extension/client/src/linter.ts index 378e4758..2f25251d 100644 --- a/extension/client/src/linter.ts +++ b/extension/client/src/linter.ts @@ -10,6 +10,8 @@ export function initialise(context: ExtensionContext) { const instance = getInstance(); const editor = window.activeTextEditor; + let exists = false; + if (editor && ![`member`, `streamfile`].includes(editor.document.uri.scheme)) { const workspaces = workspace.workspaceFolders; if (workspaces && workspaces.length > 0) { @@ -44,15 +46,23 @@ export function initialise(context: ExtensionContext) { } } else if (instance && instance.getConnection()) { + const connection = instance.getConnection(); + const content = instance.getContent(); + /** @type {"member"|"streamfile"} */ let type = `member`; let configPath: string | undefined; if (filter && filter.description) { // Bad way to get the library for the filter .. - const library = filter.description.split(`/`)[0]; + const library = (filter.description.split(`/`)[0]).toLocaleUpperCase(); configPath = `${library}/VSCODE/RPGLINT.JSON`; + exists = (await connection.runCommand({ + command: `CHKOBJ OBJ(${library}/VSCODE) OBJTYPE(*FILE) MBR(RPGLINT)`, + noLibList: true + })).code === 0; + } else if (editor) { //@ts-ignore type = editor.document.uri.scheme; @@ -74,12 +84,18 @@ export function initialise(context: ExtensionContext) { }); configPath = memberUri.path; + + exists = (await connection.runCommand({ + command: `CHKOBJ OBJ(${memberPath.library!.toLocaleUpperCase()}/VSCODE) OBJTYPE(*FILE) MBR(RPGLINT)`, + noLibList: true + })).code === 0; break; case `streamfile`: const config = instance.getConfig(); if (config.homeDirectory) { configPath = path.posix.join(config.homeDirectory, `.vscode`, `rpglint.json`) + exists = (await connection.sendCommand({ command: `test -e ${configPath}` })).code === 0; } break; } @@ -90,12 +106,9 @@ export function initialise(context: ExtensionContext) { if (configPath) { console.log(`Current path: ${configPath}`); - const exists = await commands.executeCommand(`code-for-ibmi.openEditable`, configPath, 1); - - if (!exists) { - const connection = instance.getConnection(); - const content = instance.getContent(); - + if (exists) { + await commands.executeCommand(`code-for-ibmi.openEditable`, configPath); + } else { window.showErrorMessage(`RPGLE linter config doesn't exist for this file. Would you like to create a default at ${configPath}?`, `Yes`, `No`).then (async (value) => { if (value === `Yes`) { @@ -157,10 +170,10 @@ export function initialise(context: ExtensionContext) { ) } -function parseMemberUri(path: string): {asp?: string, library?: string, file?: string, name: string} { - const parts = path.split(`/`).map(s => s.split(`,`)).flat().filter(s => s.length >= 1); +function parseMemberUri(fullPath: string): {asp?: string, library?: string, file?: string, name: string} { + const parts = fullPath.split(`/`).map(s => s.split(`,`)).flat().filter(s => s.length >= 1); return { - name: parts[parts.length - 1], + name: path.parse(parts[parts.length - 1]).name, file: parts[parts.length - 2], library: parts[parts.length - 3], asp: parts[parts.length - 4] From f8522c11e93bc3c6868d3ee24e97b1e91aea585a Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sat, 20 Jan 2024 13:43:19 -0500 Subject: [PATCH 05/17] Correct broken logic for replacing variables --- extension/server/src/providers/linter/index.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/extension/server/src/providers/linter/index.ts b/extension/server/src/providers/linter/index.ts index 5100256b..365a481a 100644 --- a/extension/server/src/providers/linter/index.ts +++ b/extension/server/src/providers/linter/index.ts @@ -462,14 +462,15 @@ export function getExtractProcedureAction(document: TextDocument, docs: Cache, r const ref = validRefs[i].refs[y]; procedureBody = procedureBody.slice(0, ref.position) + newParamNames[i] + procedureBody.slice(ref.end); + ref.end += nameDiffSize; // Then we need to update the offset of the next references - for (let z = i + 1; z < validRefs.length; z++) { + for (let z = i - 1; z >= 0; z--) { for (let x = validRefs[z].refs.length - 1; x >= 0; x--) { - if (validRefs[z].refs[x].position > ref.position) { + if (validRefs[z].refs[x].position > ref.end) { validRefs[z].refs[x] = { - position: validRefs[z].refs[x].position + (newParamNames[i].length - nameDiffSize), - end: validRefs[z].refs[x].end + (newParamNames[i].length - nameDiffSize) + position: validRefs[z].refs[x].position + nameDiffSize, + end: validRefs[z].refs[x].end + nameDiffSize }; } } From ce1020b613e060fb20b720f0e203bc9cdfce6f26 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 21 Jan 2024 10:19:39 -0500 Subject: [PATCH 06/17] Use line offset for all ranges --- extension/server/src/providers/linter/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extension/server/src/providers/linter/index.ts b/extension/server/src/providers/linter/index.ts index 365a481a..38313231 100644 --- a/extension/server/src/providers/linter/index.ts +++ b/extension/server/src/providers/linter/index.ts @@ -444,14 +444,16 @@ export function getExtractProcedureAction(document: TextDocument, docs: Cache, r const nameDiffSize = 1; // Always once since we only add 'p' at the start const newParamNames = validRefs.map(ref => `p${ref.dec.name}`); - let procedureBody = document.getText(range); + let procedureBody = document.getText(linesRange); + + const rangeStartOffset = document.offsetAt(linesRange.start); // Fix the found offset lengths to be relative to the new procedure for (let i = validRefs.length - 1; i >= 0; i--) { for (let y = validRefs[i].refs.length - 1; y >= 0; y--) { validRefs[i].refs[y] = { - position: validRefs[i].refs[y].position - document.offsetAt(range.start), - end: validRefs[i].refs[y].end - document.offsetAt(range.start) + position: validRefs[i].refs[y].position - rangeStartOffset, + end: validRefs[i].refs[y].end - rangeStartOffset }; } } From 18278d36990d39b2f426b5f5d712edf0c38b02f7 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 21 Jan 2024 10:35:14 -0500 Subject: [PATCH 07/17] New range test --- tests/suite/linter.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/suite/linter.js b/tests/suite/linter.js index 4add3ec3..894b3b66 100644 --- a/tests/suite/linter.js +++ b/tests/suite/linter.js @@ -3463,4 +3463,41 @@ exports.on_excp_2 = async () => { expectedIndent: 2, currentIndent: 0 }); +} + +exports.range_1 = async () => { + const lines = [ + `**free`, + `ctl-opt debug option(*nodebugio: *srcstmt) dftactgrp(*no) actgrp(*caller)`, + `main(Main);`, + `dcl-s x timestamp;`, + `dcl-s y timestamp;`, + `dcl-proc Main;`, + ` dsply %CHAR(CalcDiscount(10000));`, + ` dsply %char(CalcDiscount(1000));`, + ` x = %TIMESTAMP(y);`, + ` y = %TimeStamp(x);`, + ` return;`, + `end-proc;`, + ].join(`\n`); + + const cache = await parser.getDocs(uri, lines, {ignoreCache: true, withIncludes: true}); + Linter.getErrors({ uri, content: lines }, { + CollectReferences: true + }, cache); + + const rangeRefs = cache.referencesInRange({position: 220, end: 260}); + assert.strictEqual(rangeRefs.length, 2); + assert.ok(rangeRefs[0].dec.name === `x`); + assert.ok(rangeRefs[1].dec.name === `y`); + + assert.deepStrictEqual(rangeRefs[0].refs, [ + { position: 220, end: 221 }, + { position: 256, end: 257 } + ]); + + assert.deepStrictEqual(rangeRefs[1].refs, [ + { position: 235, end: 236 }, + { position: 241, end: 242 } + ]); } \ No newline at end of file From 511d8c3b72872b37764fb33033e3b3ce47325934 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Sun, 21 Jan 2024 10:36:46 -0500 Subject: [PATCH 08/17] Bump to 0.24.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2afa9bf2..cff248d8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/halcyon-tech/vscode-rpgle" }, "license": "MIT", - "version": "0.24.0", + "version": "0.24.1", "engines": { "vscode": "^1.70.0" }, From e4cc1d1002df97ca380f114d6228039b19c815e5 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 22 Jan 2024 10:34:02 -0500 Subject: [PATCH 09/17] Correct range to get references --- extension/server/src/providers/linter/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/server/src/providers/linter/index.ts b/extension/server/src/providers/linter/index.ts index 38313231..ed4018e3 100644 --- a/extension/server/src/providers/linter/index.ts +++ b/extension/server/src/providers/linter/index.ts @@ -435,11 +435,11 @@ export function getActions(document: TextDocument, errors: IssueRange[]) { export function getExtractProcedureAction(document: TextDocument, docs: Cache, range: Range): CodeAction|undefined { if (range.end.line > range.start.line) { - const references = docs.referencesInRange({position: document.offsetAt(range.start), end: document.offsetAt(range.end)}); + const linesRange = Range.create(range.start.line, 0, range.end.line, 1000); + const references = docs.referencesInRange({position: document.offsetAt(linesRange.start), end: document.offsetAt(linesRange.end)}); const validRefs = references.filter(ref => [`struct`, `subitem`, `variable`].includes(ref.dec.type)); if (validRefs.length > 0) { - const linesRange = Range.create(range.start.line, 0, range.end.line, 1000); const lastLine = document.offsetAt({line: document.lineCount, character: 0}); const nameDiffSize = 1; // Always once since we only add 'p' at the start From 4dc401fccf2bccf559875dc89d25d5e4816c8947 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 22 Jan 2024 10:53:36 -0500 Subject: [PATCH 10/17] Bump to 0.25.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cff248d8..a6cef0f0 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/halcyon-tech/vscode-rpgle" }, "license": "MIT", - "version": "0.24.1", + "version": "0.25.0", "engines": { "vscode": "^1.70.0" }, From 7493412a3bfadd471547662f7b1978b040d2e245 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 23 Jan 2024 11:44:38 -0500 Subject: [PATCH 11/17] Remove hacks to change subroutines --- language/linter.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/language/linter.ts b/language/linter.ts index 6dc9acf4..306a0b08 100644 --- a/language/linter.ts +++ b/language/linter.ts @@ -387,8 +387,7 @@ export default class Linter { if (rules.NoGlobalSubroutines) { errors.push({ offset: { position: statement[0].range.start, end: statement[0].range.end }, - type: `NoGlobalSubroutines`, - newValue: `Dcl-Proc` + type: `NoGlobalSubroutines` }); } } @@ -584,8 +583,7 @@ export default class Linter { if (rules.NoGlobalSubroutines) { errors.push({ offset: { position: statement[0].range.start, end: statement[0].range.end }, - type: `NoGlobalSubroutines`, - newValue: `End-Proc` + type: `NoGlobalSubroutines` }); } } @@ -661,8 +659,7 @@ export default class Linter { if (rules.NoGlobalSubroutines && !inProcedure) { errors.push({ type: `NoGlobalSubroutines`, - offset: { position: statement[0].range.start, end: statement[statement.length - 1].range.end }, - newValue: `return` + offset: { position: statement[0].range.start, end: statement[statement.length - 1].range.end } }); } break; @@ -672,8 +669,7 @@ export default class Linter { if (globalScope.subroutines.find(sub => sub.name.toUpperCase() === statement[1].value.toUpperCase())) { errors.push({ type: `NoGlobalSubroutines`, - offset: { position: statement[0].range.start, end: statement[statement.length - 1].range.end }, - newValue: `${statement[1].value}()` + offset: { position: statement[0].range.start, end: statement[statement.length - 1].range.end } }); } } From b9e06cd1500c9cf9ca1167c32fba31edda9972d7 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 23 Jan 2024 12:36:16 -0500 Subject: [PATCH 12/17] Ability to refactor subroutine with parameters --- .../src/providers/linter/codeActions.ts | 7 +- .../server/src/providers/linter/index.ts | 157 +++++++++++++----- 2 files changed, 119 insertions(+), 45 deletions(-) diff --git a/extension/server/src/providers/linter/codeActions.ts b/extension/server/src/providers/linter/codeActions.ts index 50c4aa29..d3359769 100644 --- a/extension/server/src/providers/linter/codeActions.ts +++ b/extension/server/src/providers/linter/codeActions.ts @@ -1,5 +1,5 @@ import { CodeAction, CodeActionKind, CodeActionParams, Range } from 'vscode-languageserver'; -import { getActions, getExtractProcedureAction, refreshLinterDiagnostics } from '.'; +import { getActions, getExtractProcedureAction, getSubroutineActions, refreshLinterDiagnostics } from '.'; import { documents, parser } from '..'; export default async function codeActionsProvider(params: CodeActionParams): Promise { @@ -13,6 +13,11 @@ export default async function codeActionsProvider(params: CodeActionParams): Pro const docs = await parser.getDocs(document.uri); if (docs) { + const subroutineOption = getSubroutineActions(document, docs, range); + if (subroutineOption) { + return [subroutineOption]; + } + const extractOption = getExtractProcedureAction(document, docs, range); if (extractOption) { return [extractOption]; diff --git a/extension/server/src/providers/linter/index.ts b/extension/server/src/providers/linter/index.ts index 943dc90d..ac2e1438 100644 --- a/extension/server/src/providers/linter/index.ts +++ b/extension/server/src/providers/linter/index.ts @@ -1,5 +1,5 @@ import path = require('path'); -import { CodeAction, CodeActionKind, Diagnostic, DiagnosticSeverity, DidChangeWatchedFilesParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, Range, TextDocumentChangeEvent, TextEdit, WorkspaceEdit, _Connection } from 'vscode-languageserver'; +import { CodeAction, CodeActionKind, Diagnostic, DiagnosticSeverity, DidChangeWatchedFilesParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, Position, Range, TextDocumentChangeEvent, TextEdit, WorkspaceEdit, _Connection } from 'vscode-languageserver'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { URI } from 'vscode-uri'; import { documents, parser } from '..'; @@ -434,60 +434,79 @@ export function getActions(document: TextDocument, errors: IssueRange[]) { return actions; } -export function getExtractProcedureAction(document: TextDocument, docs: Cache, range: Range): CodeAction|undefined { - if (range.end.line > range.start.line) { - const linesRange = Range.create(range.start.line, 0, range.end.line, 1000); - const references = docs.referencesInRange({position: document.offsetAt(linesRange.start), end: document.offsetAt(linesRange.end)}); - const validRefs = references.filter(ref => [`struct`, `subitem`, `variable`].includes(ref.dec.type)); +export function getSubroutineActions(document: TextDocument, docs: Cache, range: Range): CodeAction|undefined { + if (range.start.line === range.end.line) { + const currentGlobalSubroutine = docs.subroutines.find(sub => sub.position.line === range.start.line); - if (validRefs.length > 0) { - const lastLine = document.offsetAt({line: document.lineCount, character: 0}); + if (currentGlobalSubroutine) { + const subroutineRange = Range.create( + Position.create(currentGlobalSubroutine.range.start, 0), + Position.create(currentGlobalSubroutine.range.end, 1000) + ); - const nameDiffSize = 1; // Always once since we only add 'p' at the start - const newParamNames = validRefs.map(ref => `p${ref.dec.name}`); - let procedureBody = document.getText(linesRange); + const bodyRange = Range.create( + Position.create(currentGlobalSubroutine.range.start + 1, 0), + Position.create(currentGlobalSubroutine.range.end - 1, 0) + ); - const rangeStartOffset = document.offsetAt(linesRange.start); + // First, let's create the extract data + const extracted = createExtract(document, bodyRange, docs); - // Fix the found offset lengths to be relative to the new procedure - for (let i = validRefs.length - 1; i >= 0; i--) { - for (let y = validRefs[i].refs.length - 1; y >= 0; y--) { - validRefs[i].refs[y] = { - position: validRefs[i].refs[y].position - rangeStartOffset, - end: validRefs[i].refs[y].end - rangeStartOffset - }; - } - } + // Create the new procedure body + const newProcedure = [ + `Dcl-Proc ${currentGlobalSubroutine.name};`, + ` Dcl-Pi *N;`, + ...extracted.references.map((ref, i) => ` ${extracted.newParamNames[i]} ${ref.dec.type === `struct` ? `LikeDS` : `Like`}(${ref.dec.name});`), + ` End-Pi;`, + ``, + extracted.newBody, + `End-Proc;` + ].join(`\n`) - // Then let's fix the references to use the new names - for (let i = validRefs.length - 1; i >= 0; i--) { - for (let y = validRefs[i].refs.length - 1; y >= 0; y--) { - const ref = validRefs[i].refs[y]; - - procedureBody = procedureBody.slice(0, ref.position) + newParamNames[i] + procedureBody.slice(ref.end); - ref.end += nameDiffSize; - - // Then we need to update the offset of the next references - for (let z = i - 1; z >= 0; z--) { - for (let x = validRefs[z].refs.length - 1; x >= 0; x--) { - if (validRefs[z].refs[x].position > ref.end) { - validRefs[z].refs[x] = { - position: validRefs[z].refs[x].position + nameDiffSize, - end: validRefs[z].refs[x].end + nameDiffSize - }; - } - } - } + // Then update the references that invokes this subroutine + const referenceUpdates: TextEdit[] = currentGlobalSubroutine.references.map(ref => { + const lineNumber = document.positionAt(ref.offset.position).line; + // If this reference is outside of the subroutine + if (lineNumber < currentGlobalSubroutine.range.start || lineNumber > currentGlobalSubroutine.range.end) { + return TextEdit.replace( + Range.create( + // - 5 `EXSR ` + document.positionAt(ref.offset.position - 5), + document.positionAt(ref.offset.end) + ), + currentGlobalSubroutine.name + `(${extracted.references.map(r => r.dec.name).join(`:`)})` + ); } - } + }).map(x => x) as TextEdit[]; + + const refactorAction = CodeAction.create(`Convert to procedure`, CodeActionKind.RefactorExtract); + refactorAction.edit = { + changes: { + [document.uri]: [ + ...referenceUpdates, + TextEdit.replace(subroutineRange, newProcedure) + ] + }, + }; + + return refactorAction; + } + } +} + +export function getExtractProcedureAction(document: TextDocument, docs: Cache, range: Range): CodeAction|undefined { + if (range.end.line > range.start.line) { + const lastLine = document.offsetAt({line: document.lineCount, character: 0}); + + const extracted = createExtract(document, range, docs); const newProcedure = [ `Dcl-Proc NewProcedure;`, ` Dcl-Pi *N;`, - ...validRefs.map((ref, i) => ` ${newParamNames[i]} ${ref.dec.type === `struct` ? `LikeDS` : `Like`}(${ref.dec.name});`), + ...extracted.references.map((ref, i) => ` ${extracted.newParamNames[i]} ${ref.dec.type === `struct` ? `LikeDS` : `Like`}(${ref.dec.name});`), ` End-Pi;`, ``, - procedureBody, + extracted.newBody, `End-Proc;` ].join(`\n`) @@ -497,7 +516,7 @@ export function getExtractProcedureAction(document: TextDocument, docs: Cache, r newAction.edit = { changes: { [document.uri]: [ - TextEdit.replace(linesRange, `NewProcedure(${validRefs.map(r => r.dec.name).join(`:`)});`), + TextEdit.replace(extracted.range, `NewProcedure(${extracted.references.map(r => r.dec.name).join(`:`)});`), TextEdit.insert(document.positionAt(lastLine), `\n\n`+newProcedure) ] }, @@ -510,6 +529,56 @@ export function getExtractProcedureAction(document: TextDocument, docs: Cache, r }; return newAction; + } +} + +function createExtract(document: TextDocument, userRange: Range, docs: Cache) { + const range = Range.create(userRange.start.line, 0, userRange.end.line, 1000); + const references = docs.referencesInRange({position: document.offsetAt(range.start), end: document.offsetAt(range.end)}); + const validRefs = references.filter(ref => [`struct`, `subitem`, `variable`].includes(ref.dec.type)); + + const nameDiffSize = 1; // Always once since we only add 'p' at the start + const newParamNames = validRefs.map(ref => `p${ref.dec.name}`); + let newBody = document.getText(range); + + const rangeStartOffset = document.offsetAt(range.start); + + // Fix the found offset lengths to be relative to the new procedure + for (let i = validRefs.length - 1; i >= 0; i--) { + for (let y = validRefs[i].refs.length - 1; y >= 0; y--) { + validRefs[i].refs[y] = { + position: validRefs[i].refs[y].position - rangeStartOffset, + end: validRefs[i].refs[y].end - rangeStartOffset + }; } } + + // Then let's fix the references to use the new names + for (let i = validRefs.length - 1; i >= 0; i--) { + for (let y = validRefs[i].refs.length - 1; y >= 0; y--) { + const ref = validRefs[i].refs[y]; + + newBody = newBody.slice(0, ref.position) + newParamNames[i] + newBody.slice(ref.end); + ref.end += nameDiffSize; + + // Then we need to update the offset of the next references + for (let z = i - 1; z >= 0; z--) { + for (let x = validRefs[z].refs.length - 1; x >= 0; x--) { + if (validRefs[z].refs[x].position > ref.end) { + validRefs[z].refs[x] = { + position: validRefs[z].refs[x].position + nameDiffSize, + end: validRefs[z].refs[x].end + nameDiffSize + }; + } + } + } + } + } + + return { + newBody, + newParamNames, + references: validRefs, + range + } } \ No newline at end of file From 4c63eb167ca680b6ca00e6d83e16a9bce072fa91 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Tue, 23 Jan 2024 12:38:19 -0500 Subject: [PATCH 13/17] Fix tests to support changed methods --- tests/suite/linter.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/tests/suite/linter.js b/tests/suite/linter.js index aca984de..f257f513 100644 --- a/tests/suite/linter.js +++ b/tests/suite/linter.js @@ -1305,20 +1305,17 @@ exports.linter16 = async () => { assert.deepStrictEqual(errors[0], { type: `NoGlobalSubroutines`, - offset: { position: 36, end: 54 }, - newValue: `theSubroutine()` + offset: { position: 36, end: 54 } }); assert.deepStrictEqual(errors[1], { offset: { position: 76, end: 81 }, - type: `NoGlobalSubroutines`, - newValue: `Dcl-Proc` + type: `NoGlobalSubroutines` }); assert.deepStrictEqual(errors[2], { offset: { position: 128, end: 133 }, - type: `NoGlobalSubroutines`, - newValue: `End-Proc` + type: `NoGlobalSubroutines` }); }; @@ -1349,26 +1346,22 @@ exports.linter16_with_leavesr = async () => { assert.deepStrictEqual(errors[0], { type: `NoGlobalSubroutines`, - offset: { position: 71, end: 89 }, - newValue: `theSubroutine()` + offset: { position: 71, end: 89 } }); assert.deepStrictEqual(errors[1], { offset: { position: 111, end: 116 }, - type: `NoGlobalSubroutines`, - newValue: `Dcl-Proc` + type: `NoGlobalSubroutines` }); assert.deepStrictEqual(errors[2], { type: `NoGlobalSubroutines`, - offset: { position: 156, end: 163 }, - newValue: `return` + offset: { position: 156, end: 163 } }); assert.deepStrictEqual(errors[3], { offset: { position: 205, end: 210 }, - type: `NoGlobalSubroutines`, - newValue: `End-Proc` + type: `NoGlobalSubroutines` }); }; From 1c611f32888fd3fcb8ee0d6aed5ae14b3490977c Mon Sep 17 00:00:00 2001 From: worksofliam Date: Thu, 25 Jan 2024 14:16:14 -0500 Subject: [PATCH 14/17] Replace leavesr correctly --- extension/server/src/providers/linter/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extension/server/src/providers/linter/index.ts b/extension/server/src/providers/linter/index.ts index ac2e1438..415d7547 100644 --- a/extension/server/src/providers/linter/index.ts +++ b/extension/server/src/providers/linter/index.ts @@ -459,7 +459,7 @@ export function getSubroutineActions(document: TextDocument, docs: Cache, range: ...extracted.references.map((ref, i) => ` ${extracted.newParamNames[i]} ${ref.dec.type === `struct` ? `LikeDS` : `Like`}(${ref.dec.name});`), ` End-Pi;`, ``, - extracted.newBody, + caseInsensitiveReplaceAll(extracted.newBody, `leavesr`, `return`), `End-Proc;` ].join(`\n`) @@ -532,6 +532,11 @@ export function getExtractProcedureAction(document: TextDocument, docs: Cache, r } } +function caseInsensitiveReplaceAll(text: string, search: string, replace: string) { + return text.replace(new RegExp(search, `gi`), replace); +} + + function createExtract(document: TextDocument, userRange: Range, docs: Cache) { const range = Range.create(userRange.start.line, 0, userRange.end.line, 1000); const references = docs.referencesInRange({position: document.offsetAt(range.start), end: document.offsetAt(range.end)}); From 49003c30a8a2ccd30da0ed10cd46d1cdb9defbd5 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Fri, 26 Jan 2024 13:22:13 -0500 Subject: [PATCH 15/17] Remove space trigger --- extension/server/src/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/server/src/server.ts b/extension/server/src/server.ts index 9e747798..d041348c 100644 --- a/extension/server/src/server.ts +++ b/extension/server/src/server.ts @@ -68,7 +68,7 @@ connection.onInitialize((params: InitializeParams) => { result.capabilities.documentSymbolProvider = true; result.capabilities.definitionProvider = true; result.capabilities.completionProvider = { - triggerCharacters: [` `, `.`, `:`] + triggerCharacters: [`.`, `:`] }; result.capabilities.hoverProvider = true; result.capabilities.referencesProvider = true; From 223bdcf38e8b716d305e7511fc97e12e235506e0 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 29 Jan 2024 10:52:50 -0500 Subject: [PATCH 16/17] Bump to 0.26.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a6cef0f0..139a7717 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/halcyon-tech/vscode-rpgle" }, "license": "MIT", - "version": "0.25.0", + "version": "0.26.0", "engines": { "vscode": "^1.70.0" }, From a0444911eb9f1c301d73609915237b404f569342 Mon Sep 17 00:00:00 2001 From: worksofliam Date: Mon, 29 Jan 2024 23:57:08 -0500 Subject: [PATCH 17/17] Replace custom test command with API call --- extension/client/src/linter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/client/src/linter.ts b/extension/client/src/linter.ts index 2f25251d..277979a6 100644 --- a/extension/client/src/linter.ts +++ b/extension/client/src/linter.ts @@ -95,7 +95,7 @@ export function initialise(context: ExtensionContext) { const config = instance.getConfig(); if (config.homeDirectory) { configPath = path.posix.join(config.homeDirectory, `.vscode`, `rpglint.json`) - exists = (await connection.sendCommand({ command: `test -e ${configPath}` })).code === 0; + exists = await content.testStreamFile(configPath, `r`); } break; }