Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance parser for reference collection and testing improvements #344

Merged
merged 64 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5da4d56
Initial work to collect references in parser
worksofliam Dec 5, 2024
ebd3f31
Refactor parser setup and enhance tokenise function; add comprehensiv…
worksofliam Dec 6, 2024
e0f3615
Mostly working test cases
worksofliam Dec 6, 2024
03d079c
Test cases all working
worksofliam Dec 6, 2024
5dfbbf9
Improvements to C spec parsing
worksofliam Dec 6, 2024
5f941ff
First test case passing
worksofliam Dec 6, 2024
41ce1a3
New fixed references test
worksofliam Dec 6, 2024
e3438b2
Working language server
worksofliam Dec 6, 2024
6312389
Additional fixed tests
worksofliam Dec 6, 2024
67745a3
Convert parser to TS
worksofliam Dec 6, 2024
94975af
Speed up performance of test cases
worksofliam Dec 6, 2024
975ceb2
Fixes to language server based on parser API changes
worksofliam Dec 6, 2024
fce08f2
P spec correctly generates tokens
worksofliam Dec 6, 2024
b2c2420
Fix to handling when not to scan fixed line
worksofliam Dec 6, 2024
8c22027
New fixed test
worksofliam Dec 6, 2024
4c73d75
Error catchers for resolvers
worksofliam Dec 6, 2024
5af9a3f
Refactor cache.find method and update tests for indicator handling
worksofliam Dec 6, 2024
bda3061
Fix null check for opcode in parser switch statement
worksofliam Dec 6, 2024
b241c0c
Add parser setup functions and tests for source files
worksofliam Dec 6, 2024
80d99db
Additional reference tests
worksofliam Dec 6, 2024
47ac094
Turn on reference collecting by default
worksofliam Dec 6, 2024
ef95ddc
Improvements to const keywords
worksofliam Dec 6, 2024
739a8ba
Fixes to parsing extended field
worksofliam Dec 6, 2024
0f3c5fc
Improvements to supporting multi-line statements in fixed-format
worksofliam Dec 7, 2024
1ff1abe
Parser lookup for specials can only find global indicators
worksofliam Dec 7, 2024
d7b2a81
Fix ignore file for test sources
worksofliam Dec 7, 2024
771b672
Add broken test case
worksofliam Dec 7, 2024
31a7534
Fix for parsing logic for fixed format fields
worksofliam Dec 7, 2024
d178ebf
Fix for free-format statements spread over multiple lines with space …
worksofliam Dec 7, 2024
6ff9213
Source folder testing
worksofliam Dec 7, 2024
0131b81
Example test cases
worksofliam Dec 7, 2024
e63cee9
Remove logs and include resolving from mass parser tests
worksofliam Dec 7, 2024
4ef74cb
Use submodules instead of hardcoded files
worksofliam Dec 7, 2024
90abc8b
Improvements to references across files
worksofliam Dec 7, 2024
5dfe263
Refactor linter and reference handling to improve efficiency and clarity
worksofliam Dec 7, 2024
1a6c10b
Remove log
worksofliam Dec 7, 2024
728e963
Show references in hover
worksofliam Dec 7, 2024
675dbb9
Fix tests to support checking include references too
worksofliam Dec 8, 2024
54dc3e5
Change glob include resolver for test cases
worksofliam Dec 9, 2024
f0d46ff
Support for fixed-format SQL references
worksofliam Dec 9, 2024
4d7c5da
Refactor line ending handling in parser to improve readability and ma…
worksofliam Dec 9, 2024
fc5e1e5
Enhance reference collection in parser by remove reference lookup (se…
worksofliam Dec 9, 2024
462ffb5
Fix for directives inside a fixed exec statement
worksofliam Dec 9, 2024
996fcb3
Better partial logging
worksofliam Dec 9, 2024
1e0a4cc
Optimize find method in Cache class for improved performance and read…
worksofliam Dec 9, 2024
eaf0f30
Refactor getObjectName function in parser for improved clarity and ef…
worksofliam Dec 9, 2024
e4ff871
Refactor parser inner-functions to remove the use of the pieces variable
worksofliam Dec 9, 2024
74f2c51
Logging for larger test cases
worksofliam Dec 9, 2024
9c25ab8
Refactor getNames method in Cache class to use Set for unique name co…
worksofliam Dec 9, 2024
82e141c
Default parameter
worksofliam Dec 9, 2024
1524a7d
Fix issue for tabs and long total-free lines
worksofliam Dec 9, 2024
df3ecb3
Correct spacing for extended lines
worksofliam Dec 9, 2024
9f25251
Fix to parser circular include
worksofliam Dec 9, 2024
9655dc7
Fixes for lines with semi-colon
worksofliam Dec 9, 2024
5356262
Additional logging for imports
worksofliam Dec 9, 2024
58f957c
Improve logging message for circular includes in parser
worksofliam Dec 9, 2024
57fc34e
Additional test cases
worksofliam Dec 9, 2024
2112ca8
Fix for strings with two slashes
worksofliam Dec 9, 2024
43152f1
Fix broken submodules
worksofliam Dec 9, 2024
cb0abd6
Run tests when test directory changes
worksofliam Dec 9, 2024
f21cdf6
Improvements to LSP to support current document context better
worksofliam Dec 11, 2024
680c6d5
Improvement to reference lookup to support GOTO
worksofliam Dec 11, 2024
eac20e7
Reference tests
worksofliam Dec 11, 2024
a90995f
Rename reference function to clarify scope and improve consistency
worksofliam Dec 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ on:
pull_request:
paths:
- 'language/**'
- 'tests/**'

jobs:
release:
Expand All @@ -11,6 +12,7 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 1
submodules: true
- run: npm install
- run: node ./node_modules/eslint/bin/eslint src/** --no-error-on-unmatched-pattern
- run: npm run test
24 changes: 24 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[submodule "tests/sources/bob-recursive-example"]
path = tests/sources/bob-recursive-example
url = [email protected]:IBM/bob-recursive-example.git
[submodule "tests/sources/BBS400"]
path = tests/sources/BBS400
url = [email protected]:worksofliam/BBS400.git
[submodule "tests/sources/noxDB"]
path = tests/sources/noxDB
url = [email protected]:sitemule/noxDB.git
[submodule "tests/sources/xmlservice"]
path = tests/sources/xmlservice
url = [email protected]:IBM/xmlservice.git
[submodule "tests/sources/I_builder"]
path = tests/sources/I_builder
url = [email protected]:ibmiiste/I_builder.git
[submodule "tests/sources/ibmi-company_system"]
path = tests/sources/ibmi-company_system
url = [email protected]:IBM/ibmi-company_system.git
[submodule "tests/sources/httpapi"]
path = tests/sources/httpapi
url = [email protected]:ScottKlement/httpapi.git
[submodule "tests/sources/rpgle-repl"]
path = tests/sources/rpgle-repl
url = [email protected]:tom-writes-code/rpgle-repl.git
42 changes: 29 additions & 13 deletions extension/server/src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,23 @@ export async function memberResolve(baseUri: string, member: string, file: strin

if (resolvedMembers[baseUri] && resolvedMembers[baseUri][fileKey]) return resolvedMembers[baseUri][fileKey];

const resolvedMember = await queue.add(() => {return connection.sendRequest("memberResolve", [member, file])}) as IBMiMember|undefined;
// const resolvedMember = await connection.sendRequest("memberResolve", [member, file]) as IBMiMember|undefined;

if (resolvedMember) {
if (!resolvedMembers[baseUri]) resolvedMembers[baseUri] = {};
resolvedMembers[baseUri][fileKey] = resolvedMember;
try {
const resolvedMember = await queue.add(() => {return connection.sendRequest("memberResolve", [member, file])}) as IBMiMember|undefined;
// const resolvedMember = await connection.sendRequest("memberResolve", [member, file]) as IBMiMember|undefined;

if (resolvedMember) {
if (!resolvedMembers[baseUri]) resolvedMembers[baseUri] = {};
resolvedMembers[baseUri][fileKey] = resolvedMember;
}

return resolvedMember;
} catch (e) {
console.log(`Member resolve failed.`);
console.log(JSON.stringify({baseUri, member, file}));
console.log(e);
}

return resolvedMember;
return undefined;
}

export async function streamfileResolve(baseUri: string, base: string[]): Promise<string|undefined> {
Expand All @@ -82,15 +90,23 @@ export async function streamfileResolve(baseUri: string, base: string[]): Promis

const paths = (workspace ? includePath[workspace.uri] : []) || [];

const resolvedPath = await queue.add(() => {return connection.sendRequest("streamfileResolve", [base, paths])}) as string|undefined;
// const resolvedPath = await connection.sendRequest("streamfileResolve", [base, paths]) as string|undefined;
try {
const resolvedPath = await queue.add(() => {return connection.sendRequest("streamfileResolve", [base, paths])}) as string|undefined;
// const resolvedPath = await connection.sendRequest("streamfileResolve", [base, paths]) as string|undefined;

if (resolvedPath) {
if (!resolvedStreamfiles[baseUri]) resolvedStreamfiles[baseUri] = {};
resolvedStreamfiles[baseUri][baseString] = resolvedPath;
}

if (resolvedPath) {
if (!resolvedStreamfiles[baseUri]) resolvedStreamfiles[baseUri] = {};
resolvedStreamfiles[baseUri][baseString] = resolvedPath;
return resolvedPath;
} catch (e) {
console.log(`Streamfile resolve failed.`);
console.log(JSON.stringify({baseUri, base, paths}));
console.log(e);
}

return resolvedPath;
return undefined;
}

export function getWorkingDirectory(): Promise<string|undefined> {
Expand Down
2 changes: 2 additions & 0 deletions extension/server/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export function dspffdToRecordFormats(data: any, aliases = false): Declaration[]
len: digits === 0 ? strLength : digits,
decimals: decimals,
keywords,
field: ``,
pos: ``
});
currentSubfield.description = text.trim();

Expand Down
12 changes: 6 additions & 6 deletions extension/server/src/providers/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ import Cache from '../../../../language/models/cache';
import Declaration from '../../../../language/models/declaration';

export default async function definitionProvider(handler: DefinitionParams): Promise<Definition|null> {
const currentPath = handler.textDocument.uri;
const currentUri = handler.textDocument.uri;
const lineNumber = handler.position.line;
const document = documents.get(currentPath);
const document = documents.get(currentUri);

if (document) {
const doc = await parser.getDocs(currentPath, document.getText());
const doc = await parser.getDocs(currentUri, document.getText());
if (doc) {
const editingLine = document.getText(Range.create(lineNumber, 0, lineNumber, 200));
const possibleInclude = Parser.getIncludeFromDirective(editingLine);

if (possibleInclude) {
const include = await parser.includeFileFetch(currentPath, possibleInclude);
if (possibleInclude && parser.includeFileFetch) {
const include = await parser.includeFileFetch(currentUri, possibleInclude);
if (include.found && include.uri) {
return Location.create(include.uri, Range.create(0, 0, 0, 0));
}
Expand All @@ -25,7 +25,7 @@ export default async function definitionProvider(handler: DefinitionParams): Pro
let def: Declaration|undefined;

// First, we try and get the reference by offset
def = Cache.referenceByOffset(doc, document.offsetAt(handler.position));
def = Cache.referenceByOffset(currentUri, doc, document.offsetAt(handler.position));

if (def) {
return Location.create(
Expand Down
6 changes: 4 additions & 2 deletions extension/server/src/providers/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ export default async function hoverProvider(params: HoverParams): Promise<Hover|

if (theVariable) {
// Variable definition found
let markdown = `\`${theVariable.name}\`: \`${prettyKeywords(theVariable.keyword)}\``;
const refs = theVariable.references.length;

let markdown = `\`${theVariable.name} ${prettyKeywords(theVariable.keyword)}\` (${refs} reference${refs === 1 ? `` : `s`})`;

if (theVariable.position && currentPath !== theVariable.position.path) {
markdown += `\n\n*@file* \`${theVariable.position.path}:${theVariable.position.line+1}\``;
Expand All @@ -101,7 +103,7 @@ export default async function hoverProvider(params: HoverParams): Promise<Hover|

const includeDirective = Parser.getIncludeFromDirective(lineContent);

if (includeDirective) {
if (includeDirective && parser.includeFileFetch) {
const include = await parser.includeFileFetch(currentPath, includeDirective);
let displayName = includeDirective;

Expand Down
6 changes: 3 additions & 3 deletions extension/server/src/providers/linter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export function initialise(connection: _Connection) {
document.getText(),
{
withIncludes: true,
ignoreCache: true
ignoreCache: true,
collectReferences: true
}
).then(cache => {
if (cache) {
Expand Down Expand Up @@ -198,7 +199,6 @@ export async function refreshLinterDiagnostics(document: TextDocument, docs: Cac

let availableIncludes: string[] | undefined;
if (Project.isEnabled) {
options.CollectReferences = true;
const headers = await Project.getIncludes(document.uri);
availableIncludes = headers.map(header => header.relative);
}
Expand Down Expand Up @@ -538,7 +538,7 @@ function caseInsensitiveReplaceAll(text: string, search: string, replace: string

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 references = docs.referencesInRange(document.uri, {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
Expand Down
12 changes: 1 addition & 11 deletions extension/server/src/providers/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,17 +141,7 @@ async function loadLocalFile(uri: string) {

if (document) {
const content = document?.getText();
const cache = await parser.getDocs(uri, content);
if (cache) {
if (content.length >= 6 && content.substring(0, 6).toUpperCase() === `**FREE`) {
Linter.getErrors({
uri,
content,
}, {
CollectReferences: true
}, cache);
}
}
await parser.getDocs(uri, content);
}
}

Expand Down
56 changes: 1 addition & 55 deletions extension/server/src/providers/project/references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,11 @@ import { calculateOffset } from '../linter';
import { documents, parser } from '..';
import { getTextDoc, isEnabled } from '.';

export async function findAllLocalReferences(def: Declaration): Promise<Location[]> {
export async function findAllProjectReferences(def: Declaration): Promise<Location[]> {
let locations: Location[] = [];

if (isEnabled) {
const parsedFiles = Object.keys(parser.parsedCache);

const document = documents.get(def.position.path);

if (document) {
locations.push(
...def.references.map(ref => Location.create(
def.position.path,
calculateOffset(document, ref)
))
);
}

if (def.keyword[`EXPORT`]) {
// If we are looking for references to an export function
Expand Down Expand Up @@ -57,10 +46,6 @@ export async function findAllLocalReferences(def: Declaration): Promise<Location
// Don't add duplicates
if (!locations.some(loc => loc.uri === keyPath)) {
locations.push(
// First, we push the copybook where it is brought in.
// We do this because we don't have references for non-**free
Location.create(proc.position.path, Range.create(proc.position.line, 0, proc.position.line, 0)),

// Then we push the references. Empty for non-**free
...proc.references.map(ref => Location.create(
keyPath,
Expand All @@ -74,45 +59,6 @@ export async function findAllLocalReferences(def: Declaration): Promise<Location
}
}

} else {
// Otherwise, we are looking for references to the current definition
const baseUri = def.position.path;

for (const uri of parsedFiles) {
const document = await getTextDoc(uri);
if (document) {
const cache = parser.getParsedCache(uri);

// It's only a valid reference if it is imported somewhere else
const foundInclude = cache.includes.find(include => include.toPath === baseUri)
if (foundInclude) {
const possibleDef = cache.find(def.name);

// Okay, we found something with a similar name in another file...
if (possibleDef) {
if (possibleDef.position.path === def.position.path) {
if (document.getText(Range.create(0, 0, 0, 6)).toUpperCase() !== `**FREE` || possibleDef.references.length === 0) {
locations.push(
// First, we push the copybook where it is brought in.
// We do this because we don't have references for non-**free
Location.create(uri, Range.create(foundInclude.line, 0, foundInclude.line, 0)),
);
} else {
// But since it's **free, and we probably have referneces...
locations.push(
// Then we push the references. Empty for non-**free
...possibleDef.references.map(ref => Location.create(
uri,
calculateOffset(document, ref)
))
);
}

}
}
}
}
}
}
}

Expand Down
40 changes: 17 additions & 23 deletions extension/server/src/providers/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Linter from '../../../../language/linter';
import { calculateOffset } from './linter';

import * as Project from "./project";
import { findAllLocalReferences } from './project/references';
import { findAllProjectReferences as getAllProcedureReferences } from './project/references';
import Cache from '../../../../language/models/cache';

export async function referenceProvider(params: ReferenceParams): Promise<Location[]|undefined> {
Expand All @@ -18,35 +18,29 @@ export async function referenceProvider(params: ReferenceParams): Promise<Locati
const document = documents.get(uri);

if (document) {
const isFree = (document.getText(Range.create(0, 0, 0, 6)).toUpperCase() === `**FREE`);

const doc = await parser.getDocs(uri, document.getText());

if (doc) {
if (isFree) {
Linter.getErrors(
{
uri,
content: document.getText()
},
{
CollectReferences: true
},
doc
);
}

const def = Cache.referenceByOffset(doc, document.offsetAt(currentPos));
const def = Cache.referenceByOffset(uri, doc, document.offsetAt(currentPos));

if (def) {
let locations: Location[] = [];
if (Project.isEnabled) {
return await findAllLocalReferences(def);
} else {
return def.references.map(ref => Location.create(
def.position.path,
calculateOffset(document, ref)
));
const procRefs = await getAllProcedureReferences(def);
locations.push(...procRefs);
}

for (const ref of def.references) {
let refDoc = documents.get(ref.uri);
if (refDoc) {
locations.push(Location.create(
ref.uri,
calculateOffset(refDoc, ref)
));
}
}

return locations;
}
}
}
Expand Down
Loading
Loading