From 8a4b8362f4ca83539837d1039e97418cbf2cca8f Mon Sep 17 00:00:00 2001 From: Liam Barry Allan Date: Wed, 22 Dec 2021 23:24:10 -0500 Subject: [PATCH] NoCTDATA configuration (#8) --- .vscode/launch.json | 3 - src/linter.js | 189 +++++++++++++++++++++++---------------- src/models/default.js | 3 +- src/schemas/rpglint.json | 5 ++ src/statement.js | 6 +- src/worker.js | 1 - tests/suite/index.js | 48 ++++++++++ 7 files changed, 168 insertions(+), 87 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index ffedcc79..9d28c3a6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,9 +12,6 @@ "skipFiles": [ "/**" ], - "args": [ - "linter9" - ], "type": "pwa-node", }, { diff --git a/src/linter.js b/src/linter.js index 4ccaddc5..e40a44bd 100644 --- a/src/linter.js +++ b/src/linter.js @@ -26,6 +26,7 @@ const errorText = { 'UppercaseDirectives': `Directives must be in uppercase.`, 'NoSQLJoins': `SQL joins are not allowed. Consider creating a view instead.`, 'NoGlobalsInProcedures': `Global variables should not be referenced in procedures.`, + 'NoCTDATA': `\`CTDATA\` is not allowed.`, } module.exports = class Linter { @@ -55,6 +56,7 @@ module.exports = class Linter { * NoSQLJoins?: boolean, * NoGlobalsInProcedures?: boolean, * SpecificCasing?: {operation: string, expected: string}[], + * NoCTDATA?: boolean * }} rules * @param {Cache|null} [globalScope] */ @@ -99,7 +101,8 @@ module.exports = class Linter { * "NoOCCURS"|"NoSELECTAll"|"UselessOperationCheck"|"UppercaseConstants"|"SpecificCasing"| * "InvalidDeclareNumber"|"IncorrectVariableCase"|"RequiresParameter"| * "RequiresProcedureDescription"|"StringLiteralDupe"|"RequireBlankSpecial"| - * "CopybookDirective"|"UppercaseDirectives"|"NoSQLJoins"|"NoGlobalsInProcedures", + * "CopybookDirective"|"UppercaseDirectives"|"NoSQLJoins"|"NoGlobalsInProcedures" + * |"NoCTDATA", * newValue?: string}[] * } */ let errors = []; @@ -181,7 +184,7 @@ module.exports = class Linter { } // Ignore free directive. - if (upperLine === `**FREE`) { + if (upperLine.startsWith(`**`)) { continuedStatement = false; } @@ -198,6 +201,21 @@ module.exports = class Linter { if (statement.length >= 1) { switch (statement[0].type) { + case `format`: + if (statement[0].value.toUpperCase() === `**CTDATA`) { + if (rules.NoCTDATA) { + errors.push({ + range: new vscode.Range( + statementStart, + statementEnd + ), + offset: {position: statement[0].position, length: statement[0].position + statement[0].value.length}, + type: `NoCTDATA` + }); + } + } + break; + case `directive`: value = statement[0].value; if (rules.UppercaseDirectives) { @@ -321,13 +339,22 @@ module.exports = class Linter { } if (rules.BlankStructNamesCheck) { - if (statement.some(part => part.type === `special`)) { + if (statement.some(part => part.type === `special` && part.value.toUpperCase() === `*N`)) { errors.push({ range: new vscode.Range(statementStart, statementEnd), type: `BlankStructNamesCheck` }); } } + + if (rules.NoCTDATA) { + if (statement.some(part => [`CTDATA`, `*CTDATA`].includes(part.value.toUpperCase()))) { + errors.push({ + range: new vscode.Range(statementStart, statementEnd), + type: `NoCTDATA` + }); + } + } break; } @@ -428,100 +455,104 @@ module.exports = class Linter { for (let i = 0; i < statement.length; i++) { part = statement[i]; - if (part.type === `word` && part.value) { - const upperName = part.value.toUpperCase(); - - if (rules.NoGlobalsInProcedures) { - if (inProcedure && !inPrototype) { - const existingVariable = globalScope.variables.find(variable => variable.name.toUpperCase() === upperName); - if (existingVariable) { + if (part.value) { + switch (part.type) { + case `word`: + const upperName = part.value.toUpperCase(); + + if (rules.NoGlobalsInProcedures) { + if (inProcedure && !inPrototype) { + const existingVariable = globalScope.variables.find(variable => variable.name.toUpperCase() === upperName); + if (existingVariable) { + errors.push({ + range: new vscode.Range( + statementStart, + statementEnd + ), + offset: {position: part.position, length: part.position + part.value.length}, + type: `NoGlobalsInProcedures` + }); + } + } + } + + if (rules.IncorrectVariableCase) { + // Check the casing of the reference matches the definition + const definedNames = currentScope.getNames(); + const definedName = definedNames.find(defName => defName.toUpperCase() === upperName); + if (definedName && definedName !== part.value) { errors.push({ range: new vscode.Range( statementStart, statementEnd ), offset: {position: part.position, length: part.position + part.value.length}, - type: `NoGlobalsInProcedures` + type: `IncorrectVariableCase`, + newValue: definedName }); } } - } - - if (rules.IncorrectVariableCase) { - // Check the casing of the reference matches the definition - const definedNames = currentScope.getNames(); - const definedName = definedNames.find(defName => defName.toUpperCase() === upperName); - if (definedName && definedName !== part.value) { + + if (rules.RequiresParameter) { + // Check the procedure reference has a block following it + const definedProcedure = globalProcs.find(proc => proc.name.toUpperCase() === upperName); + if (definedProcedure) { + let requiresBlock = false; + if (statement.length <= i+1) { + requiresBlock = true; + } else if (statement[i+1].type !== `openbracket`) { + requiresBlock = true; + } + + if (requiresBlock) { + errors.push({ + range: new vscode.Range( + statementStart, + statementEnd + ), + offset: {position: part.position, length: part.position + part.value.length}, + type: `RequiresParameter`, + }); + } + } + } + break; + + case `string`: + if (part.value.substring(1, part.value.length-1).trim() === `` && rules.RequireBlankSpecial) { errors.push({ range: new vscode.Range( statementStart, statementEnd ), offset: {position: part.position, length: part.position + part.value.length}, - type: `IncorrectVariableCase`, - newValue: definedName + type: `RequireBlankSpecial`, + newValue: `*BLANK` }); - } - } - - if (rules.RequiresParameter) { - // Check the procedure reference has a block following it - const definedProcedure = globalProcs.find(proc => proc.name.toUpperCase() === upperName); - if (definedProcedure) { - let requiresBlock = false; - if (statement.length <= i+1) { - requiresBlock = true; - } else if (statement[i+1].type !== `openbracket`) { - requiresBlock = true; + + } else if (rules.StringLiteralDupe) { + let foundBefore = stringLiterals.find(literal => literal.value === part.value); + + // If it does not exist on our list, we can add it + if (!foundBefore) { + foundBefore = { + value: part.value, + list: [] + }; + + stringLiterals.push(foundBefore); } - - if (requiresBlock) { - errors.push({ - range: new vscode.Range( - statementStart, - statementEnd - ), - offset: {position: part.position, length: part.position + part.value.length}, - type: `RequiresParameter`, - }); - } - } - } - } - - if (part.type === `string`) { - if (part.value.substring(1, part.value.length-1).trim() === `` && rules.RequireBlankSpecial) { - errors.push({ - range: new vscode.Range( - statementStart, - statementEnd - ), - offset: {position: part.position, length: part.position + part.value.length}, - type: `RequireBlankSpecial`, - newValue: `*BLANK` - }); - - } else if (rules.StringLiteralDupe) { - let foundBefore = stringLiterals.find(literal => literal.value === part.value); - - // If it does not exist on our list, we can add it - if (!foundBefore) { - foundBefore = { - value: part.value, - list: [] - }; - - stringLiterals.push(foundBefore); + + // Then add our new found literal location to the list + foundBefore.list.push({ + range: new vscode.Range( + statementStart, + statementEnd + ), + offset: {position: part.position, length: part.position + part.value.length} + }); } - - // Then add our new found literal location to the list - foundBefore.list.push({ - range: new vscode.Range( - statementStart, - statementEnd - ), - offset: {position: part.position, length: part.position + part.value.length} - }); + break; } } } diff --git a/src/models/default.js b/src/models/default.js index 0acba2e4..c7a2a786 100644 --- a/src/models/default.js +++ b/src/models/default.js @@ -9,5 +9,6 @@ module.exports = { IncorrectVariableCase: true, RequiresParameter: true, StringLiteralDupe: true, - NoSQLJoins: true + NoSQLJoins: true, + NoCTDATA: true, } \ No newline at end of file diff --git a/src/schemas/rpglint.json b/src/schemas/rpglint.json index 58eefdfb..647d051d 100644 --- a/src/schemas/rpglint.json +++ b/src/schemas/rpglint.json @@ -107,6 +107,11 @@ } }, "description": "Specific casing for operation codes." + }, + "NoCTDATA": { + "$id": "#/properties/NoCTDATA", + "type": "boolean", + "description": "CTDATA is not allowed." } }, "additionalProperties": true diff --git a/src/statement.js b/src/statement.js index ebd805f7..1e328189 100644 --- a/src/statement.js +++ b/src/statement.js @@ -2,14 +2,14 @@ /** @type {{name: string, match: {type: string, match?: function}[], becomes: {type: string}}[]} */ const commonMatchers = [ { - name: `IS_FREE`, + name: `FORMAT_STATEMEMT`, match: [ { type: `asterisk` }, { type: `asterisk` }, { type: `word` }, ], becomes: { - type: `isfree` + type: `format`, } }, { @@ -41,7 +41,7 @@ const commonMatchers = [ match: [ { type: `asterisk` }, { type: `word`, match: (word) => - [ `BLANK`, `BLANKS`, `ZERO`, `ZEROS`, `ON`, `OFF`, `NULL`, `ISO`, `MDY`, `DMY`, `EUR`, `YMD`, `USA`, `SECONDS`, `S`, `MINUTES`, `MN`, `HOURS`, `H`, `DAYS`, `D`, `MONTHS`, `M`, `YEARS`, `Y`, `HIVAL`, `END`, `LOVAL`, `START`, `N`].includes(word.toUpperCase()) + [ `CTDATA`, `BLANK`, `BLANKS`, `ZERO`, `ZEROS`, `ON`, `OFF`, `NULL`, `ISO`, `MDY`, `DMY`, `EUR`, `YMD`, `USA`, `SECONDS`, `S`, `MINUTES`, `MN`, `HOURS`, `H`, `DAYS`, `D`, `MONTHS`, `M`, `YEARS`, `Y`, `HIVAL`, `END`, `LOVAL`, `START`, `N`].includes(word.toUpperCase()) } ], becomes: { diff --git a/src/worker.js b/src/worker.js index 49a9163e..2491957c 100644 --- a/src/worker.js +++ b/src/worker.js @@ -12,7 +12,6 @@ const possibleTags = require(`./models/tags`); const Linter = require(`./linter`); const Parser = require(`./parser`); const Generic = require(`./generic`); -const { workspace } = require(`../tests/models/vscode`); const lintFile = { member: `vscode,rpglint`, diff --git a/tests/suite/index.js b/tests/suite/index.js index dc7e0c20..45b9353c 100644 --- a/tests/suite/index.js +++ b/tests/suite/index.js @@ -838,4 +838,52 @@ module.exports = { newValue: `localVar` }, `Error not as expected`); }, + + linter10: async () => { + const lines = [ + `**FREE`, + `ctl-opt debug option(*nodebugio: *srcstmt);`, + `dcl-ds mything DIM(8) PERRCD(3) CTDATA;`, + `end-ds;`, + ``, + `Dcl-s MyVariable2 Char(20);`, + ``, + `myVariable2 = *blank;`, + ``, + `If myVariable2 = *blank;`, + `MyVariable2 = 'Hello world';`, + `Endif;`, + `Return;`, + ``, + `**CTDATA ARC`, + `Toronto 12:15:00Winnipeg 13:23:00Calgary 15:44:00`, + `Sydney 17:24:30Edmonton 21:33:00Saskatoon 08:40:00`, + `Regina 12:33:00Vancouver 13:20:00` + ].join(`\n`); + + const parser = new Parser(); + const cache = await parser.getDocs(URI, lines); + const { errors } = Linter.getErrors(lines, { + NoCTDATA: true + }, cache); + + assert.strictEqual(errors.length, 2, `Expect length of 2`); + + assert.deepStrictEqual(errors[0], { + range: new vscode.Range( + new vscode.Position(2, 0), + new vscode.Position(2, 38), + ), + type: `NoCTDATA`, + }, `Error not as expected`); + + assert.deepStrictEqual(errors[1], { + range: new vscode.Range( + new vscode.Position(14, 0), + new vscode.Position(14, 12), + ), + offset: { position: 0, length: 8 }, + type: `NoCTDATA`, + }, `Error not as expected`); + }, } \ No newline at end of file