diff --git a/README.md b/README.md index 25c5e0aa..9cf4d9d3 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,4 @@ Thanks so much to everyone [who has contributed](https://github.com/codefori/vsc - [@p-behr](https://github.com/p-behr) - [@chrjorgensen](https://github.com/chrjorgensen) - [@sebjulliand](https://github.com/sebjulliand) +- [@richardm90](https://github.com/richardm90) \ No newline at end of file diff --git a/extension/server/src/providers/linter/documentFormatting.ts b/extension/server/src/providers/linter/documentFormatting.ts index 3a73d03b..e9e01a30 100644 --- a/extension/server/src/providers/linter/documentFormatting.ts +++ b/extension/server/src/providers/linter/documentFormatting.ts @@ -23,7 +23,8 @@ export default async function documentFormattingProvider(params: DocumentFormatt // Need to fetch the docs again incase comments were added // as part of RequiresProcedureDescription docs = await parser.getDocs(document.uri, document.getText(), { - ignoreCache: true + ignoreCache: true, + withIncludes: true }); // Next up, let's fix all the other things! diff --git a/extension/server/src/providers/linter/index.ts b/extension/server/src/providers/linter/index.ts index 8642de5c..943dc90d 100644 --- a/extension/server/src/providers/linter/index.ts +++ b/extension/server/src/providers/linter/index.ts @@ -314,6 +314,7 @@ export function getActions(document: TextDocument, errors: IssueRange[]) { case `SpecificCasing`: case `IncorrectVariableCase`: + case `DirectiveCasing`: case `UppercaseDirectives`: if (error.newValue) { action = CodeAction.create(`Correct casing to '${error.newValue}'`, CodeActionKind.QuickFix); @@ -342,7 +343,7 @@ export function getActions(document: TextDocument, errors: IssueRange[]) { case `RequireBlankSpecial`: if (error.newValue) { - action = CodeAction.create(`Convert constant name to uppercase`, CodeActionKind.QuickFix); + action = CodeAction.create(`Convert empty string literal to *BLANK`, CodeActionKind.QuickFix); action.edit = { changes: { [document.uri]: [ diff --git a/language/linter.ts b/language/linter.ts index 2e02eca0..6dc9acf4 100644 --- a/language/linter.ts +++ b/language/linter.ts @@ -26,7 +26,8 @@ const errorText = { 'StringLiteralDupe': `Same string literal used more than once. Consider using a constant instead.`, 'RequireBlankSpecial': `\`*BLANK\` should be used over empty string literals.`, 'CopybookDirective': `Directive does not match requirement.`, - 'UppercaseDirectives': `Directives must be in uppercase.`, + 'DirectiveCasing': `Does not match required case.`, + 'UppercaseDirectives': `Directives must be in uppercase. UppercaseDirectives option is deprecated, consider using DirectiveCasing.`, '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.`, @@ -203,15 +204,6 @@ export default class Linter { case `directive`: value = statement[0].value; - if (rules.UppercaseDirectives) { - if (value !== value.toUpperCase()) { - errors.push({ - offset: { position: statement[0].range.start, end: statement[0].range.end }, - type: `UppercaseDirectives`, - newValue: value.toUpperCase() - }); - } - } if (rules.CopybookDirective || rules.IncludeMustBeRelative) { if ([`/COPY`, `/INCLUDE`].includes(value.toUpperCase())) { @@ -291,8 +283,13 @@ export default class Linter { } if (rules.CopybookDirective) { - const correctDirective = `/${rules.CopybookDirective.toUpperCase()}`; - if (value.toUpperCase() !== correctDirective) { + let correctDirective = `/${rules.CopybookDirective.toUpperCase()}`; + let correctValue = value.toUpperCase(); + if (rules.DirectiveCasing === `lower`) { + correctDirective = correctDirective.toLowerCase(); + correctValue = value.toLowerCase(); + } + if (correctValue !== correctDirective) { errors.push({ offset: { position: statement[0].range.start, end: statement[0].range.end }, type: `CopybookDirective`, @@ -302,6 +299,36 @@ export default class Linter { } } } + + if (rules.DirectiveCasing === `lower`) { + if (value !== value.toLowerCase()) { + errors.push({ + offset: { position: statement[0].range.start, end: statement[0].range.end }, + type: `DirectiveCasing`, + newValue: value.toLowerCase() + }); + } + } + + if (rules.DirectiveCasing === `upper`) { + if (value !== value.toUpperCase()) { + errors.push({ + offset: { position: statement[0].range.start, end: statement[0].range.end }, + type: `DirectiveCasing`, + newValue: value.toUpperCase() + }); + } + } + + if (rules.UppercaseDirectives) { + if (value !== value.toUpperCase()) { + errors.push({ + offset: { position: statement[0].range.start, end: statement[0].range.end }, + type: `UppercaseDirectives`, + newValue: value.toUpperCase() + }); + } + } break; case `declare`: diff --git a/language/parserTypes.ts b/language/parserTypes.ts index c4d4355a..29a69d03 100644 --- a/language/parserTypes.ts +++ b/language/parserTypes.ts @@ -41,6 +41,7 @@ export interface Rules { literalMinimum?: number; RequireBlankSpecial?: boolean; CopybookDirective?: "copy"|"include"; + DirectiveCasing?: "lower"|"upper"; UppercaseDirectives?: boolean; NoSQLJoins?: boolean; NoGlobalsInProcedures?: boolean; diff --git a/schemas/rpglint.json b/schemas/rpglint.json index 9a452c0b..79dccbfe 100644 --- a/schemas/rpglint.json +++ b/schemas/rpglint.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://github.com/halcyon-tech/vscode-ibmi", "type": "object", "description": "Available lint configuration for RPGLE", @@ -85,10 +85,20 @@ ], "description": "Force which directive which must be used to include other source. (Copy or Include)" }, + "DirectiveCase": { + "$id": "#/properties/DirectiveCase", + "type": "string", + "enum": [ + "lower", + "upper" + ], + "description": "The expected casing of directives (lower or upper)." + }, "UppercaseDirectives": { "$id": "#/properties/UppercaseDirectives", "type": "boolean", - "description": "Directives must be in uppercase." + "description": "Directives must be in uppercase.", + "deprecated": true }, "NoSQLJoins": { "$id": "#/properties/NoSQLJoins", diff --git a/tests/rpgle/copy3.rpgle b/tests/rpgle/copy3.rpgle new file mode 100644 index 00000000..b07ca6c1 --- /dev/null +++ b/tests/rpgle/copy3.rpgle @@ -0,0 +1,3 @@ +**FREE + +Dcl-S CustomerName_t varchar(40) template; diff --git a/tests/suite/directives.js b/tests/suite/directives.js index ff5f91f3..6ece64e3 100644 --- a/tests/suite/directives.js +++ b/tests/suite/directives.js @@ -463,5 +463,144 @@ module.exports = { const someDs = cache.find(`someDs`); assert.strictEqual(someDs.keyword[`BASED`], undefined); + }, + + variable_case1: async () => { + const lines = [ + `**FREE`, + `Ctl-Opt DftActGrp(*No);`, + `/copy './tests/rpgle/copy3.rpgle'`, + `Dcl-S MyCustomerName1 like(customername_t);`, + `Dcl-S MyCustomerName2 like(CustomerName_t);`, + `Dcl-S MyCustomerName3 like(CUSTOMERNAME_t);`, + `Dcl-S MyCustomerName4 like(CUSTOMERNAME_T);`, + `MyCustomerName1 = 'John Smith';`, + `dsply MyCustomerName1;`, + `Return;` + ].join(`\n`); + + const cache = await parser.getDocs(uri, lines, {withIncludes: true, ignoreCache: true}); + const { errors } = Linter.getErrors({ uri, content: lines }, { + IncorrectVariableCase: true + }, cache); + + assert.strictEqual(errors.length, 3, `Expect length of 3`); + + assert.deepStrictEqual(errors[0], { + offset: { position: 92, end: 106 }, + type: `IncorrectVariableCase`, + newValue: `CustomerName_t` + }); + + assert.deepStrictEqual(errors[1], { + offset: { position: 180, end: 194 }, + type: `IncorrectVariableCase`, + newValue: `CustomerName_t` + }); + + assert.deepStrictEqual(errors[2], { + offset: { position: 224, end: 238 }, + type: `IncorrectVariableCase`, + newValue: `CustomerName_t` + }); + }, + + uppercase1: async () => { + const lines = [ + `**FREE`, + `Ctl-Opt DftActGrp(*No);`, + `/copy './tests/rpgle/copy1.rpgle'`, + `/Copy './tests/rpgle/copy2.rpgle'`, + `/COPY './tests/rpgle/copy3.rpgle'`, + `Dcl-S MyCustomerName1 like(CustomerName_t);`, + `MyCustomerName1 = 'John Smith';`, + `dsply MyCustomerName1;`, + `Return;` + ].join(`\n`); + + const cache = await parser.getDocs(uri, lines, {withIncludes: true, ignoreCache: true}); + const { errors } = Linter.getErrors({ uri, content: lines }, { + DirectiveCasing: `upper` + }, cache); + + assert.strictEqual(errors.length, 2, `Expect length of 2`); + + assert.deepStrictEqual(errors[0], { + offset: { position: 31, end: 36 }, + type: `DirectiveCasing`, + newValue: `/COPY` + }); + + assert.deepStrictEqual(errors[1], { + offset: { position: 65, end: 70 }, + type: `DirectiveCasing`, + newValue: `/COPY` + }); + }, + + uppercase2: async () => { + const lines = [ + `**FREE`, + `Ctl-Opt DftActGrp(*No);`, + `/copy './tests/rpgle/copy1.rpgle'`, + `/Copy './tests/rpgle/copy2.rpgle'`, + `/COPY './tests/rpgle/copy3.rpgle'`, + `Dcl-S MyCustomerName1 like(CustomerName_t);`, + `MyCustomerName1 = 'John Smith';`, + `dsply MyCustomerName1;`, + `Return;` + ].join(`\n`); + + const cache = await parser.getDocs(uri, lines, {withIncludes: true, ignoreCache: true}); + const { errors } = Linter.getErrors({ uri, content: lines }, { + UppercaseDirectives: true + }, cache); + + assert.strictEqual(errors.length, 2, `Expect length of 2`); + + assert.deepStrictEqual(errors[0], { + offset: { position: 31, end: 36 }, + type: `UppercaseDirectives`, + newValue: `/COPY` + }); + + assert.deepStrictEqual(errors[1], { + offset: { position: 65, end: 70 }, + type: `UppercaseDirectives`, + newValue: `/COPY` + }); + }, + + lowercase1: async () => { + const lines = [ + `**FREE`, + `Ctl-Opt DftActGrp(*No);`, + `/copy './tests/rpgle/copy1.rpgle'`, + `/Copy './tests/rpgle/copy2.rpgle'`, + `/COPY './tests/rpgle/copy3.rpgle'`, + `Dcl-S MyCustomerName1 like(CustomerName_t);`, + `MyCustomerName1 = 'John Smith';`, + `dsply MyCustomerName1;`, + `Return;` + ].join(`\n`); + + const cache = await parser.getDocs(uri, lines, {withIncludes: true, ignoreCache: true}); + const { errors } = Linter.getErrors({ uri, content: lines }, { + DirectiveCasing: `lower` + }, cache); + + assert.strictEqual(errors.length, 2, `Expect length of 2`); + + assert.deepStrictEqual(errors[0], { + offset: { position: 65, end: 70 }, + type: `DirectiveCasing`, + newValue: `/copy` + }); + + assert.deepStrictEqual(errors[1], { + offset: { position: 99, end: 104 }, + type: `DirectiveCasing`, + newValue: `/copy` + }); } } \ No newline at end of file diff --git a/tests/suite/linter.js b/tests/suite/linter.js index 894b3b66..aca984de 100644 --- a/tests/suite/linter.js +++ b/tests/suite/linter.js @@ -1269,14 +1269,14 @@ exports.linter15 = async () => { assert.deepStrictEqual(errors[0], { offset: { position: 36, end: 38 }, - type: 'PrettyComments', - newValue: '// ' + type: `PrettyComments`, + newValue: `// ` }); assert.deepStrictEqual(errors[1], { offset: { position: 207, end: 209 }, - type: 'PrettyComments', - newValue: '// ' + type: `PrettyComments`, + newValue: `// ` }); };