Skip to content

Commit

Permalink
NoCTDATA configuration (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
worksofliam committed Dec 23, 2021
1 parent 7ec5ab9 commit 8a4b836
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 87 deletions.
3 changes: 0 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
"skipFiles": [
"<node_internals>/**"
],
"args": [
"linter9"
],
"type": "pwa-node",
},
{
Expand Down
189 changes: 110 additions & 79 deletions src/linter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -55,6 +56,7 @@ module.exports = class Linter {
* NoSQLJoins?: boolean,
* NoGlobalsInProcedures?: boolean,
* SpecificCasing?: {operation: string, expected: string}[],
* NoCTDATA?: boolean
* }} rules
* @param {Cache|null} [globalScope]
*/
Expand Down Expand Up @@ -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 = [];
Expand Down Expand Up @@ -181,7 +184,7 @@ module.exports = class Linter {
}

// Ignore free directive.
if (upperLine === `**FREE`) {
if (upperLine.startsWith(`**`)) {
continuedStatement = false;
}

Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/models/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ module.exports = {
IncorrectVariableCase: true,
RequiresParameter: true,
StringLiteralDupe: true,
NoSQLJoins: true
NoSQLJoins: true,
NoCTDATA: true,
}
5 changes: 5 additions & 0 deletions src/schemas/rpglint.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@
}
},
"description": "Specific casing for operation codes."
},
"NoCTDATA": {
"$id": "#/properties/NoCTDATA",
"type": "boolean",
"description": "CTDATA is not allowed."
}
},
"additionalProperties": true
Expand Down
6 changes: 3 additions & 3 deletions src/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
}
},
{
Expand Down Expand Up @@ -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: {
Expand Down
1 change: 0 additions & 1 deletion src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
48 changes: 48 additions & 0 deletions tests/suite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
},
}

0 comments on commit 8a4b836

Please sign in to comment.