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/extension/client/src/linter.ts b/extension/client/src/linter.ts index 378e4758..277979a6 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 content.testStreamFile(configPath, `r`); } 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] diff --git a/extension/server/src/providers/linter/codeActions.ts b/extension/server/src/providers/linter/codeActions.ts index 0001c91c..d3359769 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, getSubroutineActions, refreshLinterDiagnostics } from '.'; import { documents, parser } from '..'; export default async function codeActionsProvider(params: CodeActionParams): Promise { @@ -13,6 +13,16 @@ 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]; + } + 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 87f8cd71..415d7547 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 '..'; @@ -432,4 +432,158 @@ export function getActions(document: TextDocument, errors: IssueRange[]) { }); return actions; +} + +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 (currentGlobalSubroutine) { + const subroutineRange = Range.create( + Position.create(currentGlobalSubroutine.range.start, 0), + Position.create(currentGlobalSubroutine.range.end, 1000) + ); + + const bodyRange = Range.create( + Position.create(currentGlobalSubroutine.range.start + 1, 0), + Position.create(currentGlobalSubroutine.range.end - 1, 0) + ); + + // First, let's create the extract data + const extracted = createExtract(document, bodyRange, docs); + + // 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;`, + ``, + caseInsensitiveReplaceAll(extracted.newBody, `leavesr`, `return`), + `End-Proc;` + ].join(`\n`) + + // 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;`, + ...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`) + + const newAction = CodeAction.create(`Extract to new procedure`, CodeActionKind.RefactorExtract); + + // First do the exit + newAction.edit = { + changes: { + [document.uri]: [ + TextEdit.replace(extracted.range, `NewProcedure(${extracted.references.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; + } +} + +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)}); + 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 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; 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 } }); } } 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 e4a68173..80b622b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index 2afa9bf2..139a7717 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.26.0", "engines": { "vscode": "^1.70.0" }, 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", diff --git a/tests/suite/linter.js b/tests/suite/linter.js index c3c1d4a7..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` }); }; @@ -3463,4 +3456,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