diff --git a/language/parser.ts b/language/parser.ts index 198c0dd..10ae0fc 100644 --- a/language/parser.ts +++ b/language/parser.ts @@ -7,7 +7,6 @@ import Declaration from "./models/declaration"; import oneLineTriggers from "./models/oneLineTriggers"; import { parseFLine, parseCLine, parsePLine, parseDLine, getPrettyType, prettyTypeFromToken } from "./models/fixed"; -import path from "path"; import { Token } from "./types"; import { Keywords } from "./parserTypes"; @@ -357,20 +356,67 @@ export default class Parser { return directIfScope.length === 0 || directIfScope.every(scope => scope.condition); } + /** + * Removes the trailing line comment (//) but keeps the semi-colon if found. + */ const stripComment = (inputLine: string) => { - const comment = inputLine.indexOf(`//`); - const quote = inputLine.lastIndexOf(`'`); - if (comment >= 0 && comment < quote) { - return inputLine; + let comment = -1; + let inString = false; + for (let i = inputLine.length - 1; i >= 0; i--) { + switch (inputLine[i]) { + case '/': + if (inputLine[i-1] === `/`) { + // It's a comment! + inString = false; + comment = i-1; + i--; + + return inputLine.substring(0, comment).trimEnd(); + } + break; + case `'`: + inString = !inString; + break; + case ';': + if (!inString) { + return inputLine.substring(0, i+1).trimEnd(); + } + break; + } } - - return (comment >= 0 ? inputLine.substring(0, comment).trimEnd() : inputLine); + + return inputLine; } + /** + * Removes the trailing comment and optionally removes the semi-colon. + */ const getValidStatement = (inputLine: string, withSep?: boolean) => { - const comment = inputLine.indexOf(`//`); - const quote = inputLine.lastIndexOf(`'`); - const sep = inputLine.indexOf(`;`, quote >= 0 ? quote : 0); + if (!inputLine.includes(`;`)) return inputLine; + let comment = -1; + let inString = false; + let sep = -1; + for (let i = inputLine.length - 1; i >= 0; i--) { + switch (inputLine[i]) { + case '/': + if (inputLine[i-1] === `/`) { + // It's a comment! + inString = false; + comment = i-1; + i--; + } + break; + case `'`: + inString = !inString; + break; + case ';': + if (!inString) { + sep = i; + break; + } + break; + } + } if (comment >= 0 && comment < sep) { return inputLine; diff --git a/tests/suite/basics.test.ts b/tests/suite/basics.test.ts index 9471bcb..8679aa8 100644 --- a/tests/suite/basics.test.ts +++ b/tests/suite/basics.test.ts @@ -1297,4 +1297,27 @@ test(`const keyword check`, async () => { const hello = cache.find(`hello`); expect(hello.name).toBe(`hello`); expect(hello.keyword[`CONST`]).toBe(`556`); +}); + +test('issue_353_comments', async () => { + const lines = [ + `**free`, + `dcl-ds HEDINF based(p1@);`, + ` HRLEN Int(10:0); // Record length`, + ` HCRRN Int(10:0); // Cursor's RRN`, + ` HCPOS Int(10:0); // Cursor's column`, + ` HCCSID Int(10:0); // CCSID of source`, + ` HRECI Int(10:0); // Nbr of input rcds`, + `end-ds;`, + `dcl-s p2@ Pointer;`, + ].join(`\n`); + + const cache = await parser.getDocs(uri, lines, { ignoreCache: true, withIncludes: false }); + + const hedinf = cache.find(`HEDINF`); + expect(hedinf).toBeDefined(); + console.log(hedinf.subItems.map(s => s.name)); + expect(hedinf.subItems.length).toBe(5); + const p2at = cache.find(`p2@`); + expect(p2at).toBeDefined(); }); \ No newline at end of file diff --git a/tests/suite/linter.test.ts b/tests/suite/linter.test.ts index b73dc8c..47e95ab 100644 --- a/tests/suite/linter.test.ts +++ b/tests/suite/linter.test.ts @@ -3186,4 +3186,87 @@ test('Linter running on member rpgleinc', async () => { type: 'SpecificCasing', newValue: 'DCL-S' }); +}); + +test('issue_353_indent_1', async () => { + const lines = [ + `**free`, + `dcl-ds HEDINF based(p1@);`, + ` HRLEN Int(10:0); // Record length`, + ` HCRRN Int(10:0); // Cursor's RRN`, + ` HCPOS Int(10:0); // Cursor's column`, + ` HCCSID Int(10:0); // CCSID of source`, + ` HRECI Int(10:0); // Nbr of input rcds`, + `end-ds;`, + `dcl-s p2@ Pointer;`, + ].join(`\n`); + + const cache = await parser.getDocs(uri, lines, { ignoreCache: true, withIncludes: false }); + + const hedinf = cache.find(`HEDINF`); + expect(hedinf).toBeDefined(); + expect(hedinf.subItems.length).toBe(5); + const p2at = cache.find(`p2@`); + expect(p2at).toBeDefined(); + + const { indentErrors, errors } = Linter.getErrors({ uri, content: lines }, { + indent: 2 + }, cache); + + expect(errors.length).toBe(0); + expect(indentErrors.length).toBe(0); +}); + +test('issue_353_indent_2', async () => { + const lines = [ + `**free`, + `dcl-ds HEDINF based(p1@);`, + ` HRLEN Int(10:0); // Record length`, + ` HCRRN Int(10:0); // Cursor's RRN`, + ` HCPOS Int(10:0); // Cursor's column`, + ` HCCSID Int(10:0); // CCSID of source`, + ` HRECI Int(10:0); // Nbr of input rcds`, + `end-ds;`, + `dcl-s p2@ Pointer;`, + ].join(`\n`); + + const cache = await parser.getDocs(uri, lines, { ignoreCache: true, withIncludes: false }); + + const hedinf = cache.find(`HEDINF`); + expect(hedinf).toBeDefined(); + expect(hedinf.subItems.length).toBe(5); + const p2at = cache.find(`p2@`); + expect(p2at).toBeDefined(); + + const { indentErrors, errors } = Linter.getErrors({ uri, content: lines }, { + indent: 2 + }, cache); + + expect(indentErrors.length).toBe(1); + expect(indentErrors[0]).toMatchObject({ + line: 3, + expectedIndent: 2, + currentIndent: 3 + }); +}); + +test('issue_353_indent_3', async () => { + const lines = [ + `**free`, + `begsr displayHelp;`, + ` // Do something with this program's`, + ` // name and library ... assume the program is running`, + ` // from the same library as contains the source.`, + ` fileName = 'QRPGLESRC';`, + ` library = pgSts.lib;`, + `endsr;`, + ].join(`\n`); + + const cache = await parser.getDocs(uri, lines, { ignoreCache: true, withIncludes: false }); + const { indentErrors, errors } = Linter.getErrors({ uri, content: lines }, { + indent: 2 + }, cache); + + expect(errors.length).toBe(0); + expect(indentErrors.length).toBe(0); }); \ No newline at end of file