From f793eb3a76e5284e8231747a0849f303f56ba449 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Fri, 21 May 2021 17:29:23 +0500 Subject: [PATCH] fix(strictMacroDefinition): moved from lineRules to fileRules --- src/rules/file/index.ts | 1 + src/rules/file/strictMacroDefinition.spec.ts | 204 +++++++++++++++++++ src/rules/file/strictMacroDefinition.ts | 140 +++++++++++++ src/rules/line/index.ts | 1 - src/rules/line/strictMacroDefinition.spec.ts | 115 ----------- src/rules/line/strictMacroDefinition.ts | 110 ---------- src/types/LintConfig.ts | 8 +- src/utils/getLintConfig.spec.ts | 4 +- 8 files changed, 351 insertions(+), 232 deletions(-) create mode 100644 src/rules/file/strictMacroDefinition.spec.ts create mode 100644 src/rules/file/strictMacroDefinition.ts delete mode 100644 src/rules/line/strictMacroDefinition.spec.ts delete mode 100644 src/rules/line/strictMacroDefinition.ts diff --git a/src/rules/file/index.ts b/src/rules/file/index.ts index 40730af..b3a13b7 100644 --- a/src/rules/file/index.ts +++ b/src/rules/file/index.ts @@ -3,3 +3,4 @@ export { hasMacroNameInMend } from './hasMacroNameInMend' export { hasMacroParentheses } from './hasMacroParentheses' export { lineEndings } from './lineEndings' export { noNestedMacros } from './noNestedMacros' +export { strictMacroDefinition } from './strictMacroDefinition' diff --git a/src/rules/file/strictMacroDefinition.spec.ts b/src/rules/file/strictMacroDefinition.spec.ts new file mode 100644 index 0000000..ac7e982 --- /dev/null +++ b/src/rules/file/strictMacroDefinition.spec.ts @@ -0,0 +1,204 @@ +import { LintConfig, Severity } from '../../types' +import { strictMacroDefinition } from './strictMacroDefinition' + +describe('strictMacroDefinition', () => { + it('should return an empty array when the content has correct macro definition syntax', () => { + const content = '%macro somemacro;' + expect(strictMacroDefinition.test(content)).toEqual([]) + + const content2 = '%macro somemacro();' + expect(strictMacroDefinition.test(content2)).toEqual([]) + + const content3 = '%macro somemacro(var1);' + expect(strictMacroDefinition.test(content3)).toEqual([]) + + const content4 = '%macro somemacro/minoperator;' + expect(strictMacroDefinition.test(content4)).toEqual([]) + + const content5 = '%macro somemacro /minoperator;' + expect(strictMacroDefinition.test(content5)).toEqual([]) + + const content6 = '%macro somemacro(var1, var2)/minoperator;' + expect(strictMacroDefinition.test(content6)).toEqual([]) + + const content7 = + ' /* Some Comment */ %macro somemacro(var1, var2) /minoperator ; /* Some Comment */' + expect(strictMacroDefinition.test(content7)).toEqual([]) + + const content8 = + '%macro macroName( arr, arr/* / store source */3 ) /* / store source */;/* / store source */' + expect(strictMacroDefinition.test(content8)).toEqual([]) + + const content9 = '%macro macroName(var1, var2=with space, var3=);' + expect(strictMacroDefinition.test(content9)).toEqual([]) + + const content10 = '%macro macroName()/ /* some comment */ store source;' + expect(strictMacroDefinition.test(content10)).toEqual([]) + + const content11 = '`%macro macroName() /* / store source */;' + expect(strictMacroDefinition.test(content11)).toEqual([]) + }) + + it('should return an array with a single diagnostic when Macro definition has space in param', () => { + const content = '%macro somemacro(va r1);' + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Param 'va r1' cannot have space`, + lineNumber: 1, + startColumnNumber: 18, + endColumnNumber: 22, + severity: Severity.Warning + } + ]) + }) + + it('should return an array with a two diagnostics when Macro definition has space in params', () => { + const content = '%macro somemacro(var1, var 2, v ar3, var4);' + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Param 'var 2' cannot have space`, + lineNumber: 1, + startColumnNumber: 24, + endColumnNumber: 28, + severity: Severity.Warning + }, + { + message: `Param 'v ar3' cannot have space`, + lineNumber: 1, + startColumnNumber: 31, + endColumnNumber: 35, + severity: Severity.Warning + } + ]) + }) + + it('should return an array with a two diagnostics when Macro definition has space in params - special case', () => { + const content = + '%macro macroName( arr, ar r/* / store source */ 3 ) /* / store source */;/* / store source */' + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Param 'ar r 3' cannot have space`, + lineNumber: 1, + startColumnNumber: 24, + endColumnNumber: 49, + severity: Severity.Warning + } + ]) + }) + + it('should return an array with a single diagnostic when Macro definition has invalid option', () => { + const content = '%macro somemacro(var1, var2)/minXoperator;' + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Option 'minXoperator' is not valid`, + lineNumber: 1, + startColumnNumber: 30, + endColumnNumber: 41, + severity: Severity.Warning + } + ]) + }) + + it('should return an array with a two diagnostics when Macro definition has invalid options', () => { + const content = + '%macro somemacro(var1, var2)/ store invalidoption secure ;' + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Option 'invalidoption' is not valid`, + lineNumber: 1, + startColumnNumber: 39, + endColumnNumber: 51, + severity: Severity.Warning + } + ]) + }) + + describe('multi-content macro declarations', () => { + it('should return an array with a single diagnostic when Macro definition has space in param', () => { + const content = `%macro + somemacro(va r1);` + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Param 'va r1' cannot have space`, + lineNumber: 2, + startColumnNumber: 18, + endColumnNumber: 22, + severity: Severity.Warning + } + ]) + }) + + it('should return an array with a two diagnostics when Macro definition has space in params', () => { + const content = `%macro somemacro( + var1, + var 2, + v ar3, + var4);` + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Param 'var 2' cannot have space`, + lineNumber: 3, + startColumnNumber: 7, + endColumnNumber: 11, + severity: Severity.Warning + }, + { + message: `Param 'v ar3' cannot have space`, + lineNumber: 4, + startColumnNumber: 7, + endColumnNumber: 11, + severity: Severity.Warning + } + ]) + }) + + it('should return an array with a two diagnostics when Macro definition has space in params - special case', () => { + const content = `%macro macroName( + arr, + ar r/* / store source */ 3 + ) /* / store source */;/* / store source */` + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Param 'ar r 3' cannot have space`, + lineNumber: 3, + startColumnNumber: 7, + endColumnNumber: 32, + severity: Severity.Warning + } + ]) + }) + + it('should return an array with a single diagnostic when Macro definition has invalid option', () => { + const content = `%macro somemacro(var1, var2) + /minXoperator;` + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Option 'minXoperator' is not valid`, + lineNumber: 2, + startColumnNumber: 8, + endColumnNumber: 19, + severity: Severity.Warning + } + ]) + }) + + it('should return an array with a two diagnostics when Macro definition has invalid options', () => { + const content = `%macro + somemacro( + var1, var2 + ) + / store + invalidoption + secure ;` + expect(strictMacroDefinition.test(content)).toEqual([ + { + message: `Option 'invalidoption' is not valid`, + lineNumber: 6, + startColumnNumber: 16, + endColumnNumber: 28, + severity: Severity.Warning + } + ]) + }) + }) +}) diff --git a/src/rules/file/strictMacroDefinition.ts b/src/rules/file/strictMacroDefinition.ts new file mode 100644 index 0000000..cc4db97 --- /dev/null +++ b/src/rules/file/strictMacroDefinition.ts @@ -0,0 +1,140 @@ +import { Diagnostic } from '../../types/Diagnostic' +import { LintConfig } from '../../types' +import { FileLintRule } from '../../types/LintRule' +import { LintRuleType } from '../../types/LintRuleType' +import { Severity } from '../../types/Severity' +import { parseMacros } from '../../utils/parseMacros' + +const name = 'strictMacroDefinition' +const description = 'Enforce strictly rules of macro definition syntax.' +const message = 'Incorrent Macro Definition Syntax' + +const validOptions = [ + 'CMD', + 'DES', + 'MINDELIMITER', + 'MINOPERATOR', + 'NOMINOPERATOR', + 'PARMBUFF', + 'SECURE', + 'NOSECURE', + 'STMT', + 'SOURCE', + 'SRC', + 'STORE' +] + +const test = (value: string, config?: LintConfig) => { + const diagnostics: Diagnostic[] = [] + + const macros = parseMacros(value, config) + + macros.forEach((macro) => { + const declaration = macro.declaration + + const regExpParams = new RegExp(/\((.*?)\)/) + const regExpParamsResult = regExpParams.exec(declaration) + + let _declaration = declaration + if (regExpParamsResult) { + const paramsPresent = regExpParamsResult[1] + + const paramsTrimmed = paramsPresent.trim() + const params = paramsTrimmed.split(',') + params.forEach((param) => { + const trimedParam = param.split('=')[0].trim() + + let paramLineNumber: number = 1, + paramStartIndex: number = 1, + paramEndIndex: number = value.length + + if ( + macro.declarationLines.findIndex( + (dl) => dl.indexOf(trimedParam) !== -1 + ) === -1 + ) { + const comment = '/\\*(.*?)\\*/' + for (let i = 1; i < trimedParam.length; i++) { + const paramWithComment = + trimedParam.slice(0, i) + comment + trimedParam.slice(i) + const regEx = new RegExp(paramWithComment) + + const declarationLineIndex = macro.declarationLines.findIndex( + (dl) => !!regEx.exec(dl) + ) + + if (declarationLineIndex !== -1) { + const declarationLine = + macro.declarationLines[declarationLineIndex] + const partFound = regEx.exec(declarationLine)![0] + + paramLineNumber = macro.startLineNumbers[declarationLineIndex] + paramStartIndex = declarationLine.indexOf(partFound) + paramEndIndex = + declarationLine.indexOf(partFound) + partFound.length + break + } + } + } else { + const declarationLineIndex = macro.declarationLines.findIndex( + (dl) => dl.indexOf(trimedParam) !== -1 + ) + const declarationLine = macro.declarationLines[declarationLineIndex] + paramLineNumber = macro.startLineNumbers[declarationLineIndex] + + paramStartIndex = declarationLine.indexOf(trimedParam) + paramEndIndex = + declarationLine.indexOf(trimedParam) + trimedParam.length + } + + if (trimedParam.includes(' ')) { + diagnostics.push({ + message: `Param '${trimedParam}' cannot have space`, + lineNumber: paramLineNumber, + startColumnNumber: paramStartIndex + 1, + endColumnNumber: paramEndIndex, + severity: Severity.Warning + }) + } + }) + + _declaration = declaration.split(`(${paramsPresent})`)[1] + } + + const optionsPresent = _declaration.split('/')?.[1]?.trim().split(' ') + + optionsPresent + ?.filter((o) => !!o) + .forEach((option) => { + const trimmedOption = option.trim() + if (!validOptions.includes(trimmedOption.toUpperCase())) { + const declarationLineIndex = macro.declarationLines.findIndex( + (dl) => dl.indexOf(trimmedOption) !== -1 + ) + const declarationLine = macro.declarationLines[declarationLineIndex] + + diagnostics.push({ + message: `Option '${trimmedOption}' is not valid`, + lineNumber: macro.startLineNumbers[declarationLineIndex], + startColumnNumber: declarationLine.indexOf(trimmedOption) + 1, + endColumnNumber: + declarationLine.indexOf(trimmedOption) + trimmedOption.length, + severity: Severity.Warning + }) + } + }) + }) + + return diagnostics +} + +/** + * Lint rule that checks if a line has followed syntax for macro definition + */ +export const strictMacroDefinition: FileLintRule = { + type: LintRuleType.File, + name, + description, + message, + test +} diff --git a/src/rules/line/index.ts b/src/rules/line/index.ts index 6029956..e8b0f70 100644 --- a/src/rules/line/index.ts +++ b/src/rules/line/index.ts @@ -3,4 +3,3 @@ export { maxLineLength } from './maxLineLength' export { noEncodedPasswords } from './noEncodedPasswords' export { noTabIndentation } from './noTabIndentation' export { noTrailingSpaces } from './noTrailingSpaces' -export { strictMacroDefinition } from './strictMacroDefinition' diff --git a/src/rules/line/strictMacroDefinition.spec.ts b/src/rules/line/strictMacroDefinition.spec.ts deleted file mode 100644 index 750084d..0000000 --- a/src/rules/line/strictMacroDefinition.spec.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { LintConfig, Severity } from '../../types' -import { strictMacroDefinition } from './strictMacroDefinition' - -describe('strictMacroDefinition', () => { - it('should return an empty array when the line has correct macro definition syntax', () => { - const line = '%macro somemacro;' - expect(strictMacroDefinition.test(line, 1)).toEqual([]) - - const line2 = '%macro somemacro();' - expect(strictMacroDefinition.test(line2, 1)).toEqual([]) - - const line3 = '%macro somemacro(var1);' - expect(strictMacroDefinition.test(line3, 1)).toEqual([]) - - const line4 = '%macro somemacro/minoperator;' - expect(strictMacroDefinition.test(line4, 1)).toEqual([]) - - const line5 = '%macro somemacro /minoperator;' - expect(strictMacroDefinition.test(line5, 1)).toEqual([]) - - const line6 = '%macro somemacro(var1, var2)/minoperator;' - expect(strictMacroDefinition.test(line6, 1)).toEqual([]) - - const line7 = - ' /* Some Comment */ %macro somemacro(var1, var2) /minoperator ; /* Some Comment */' - expect(strictMacroDefinition.test(line7, 1)).toEqual([]) - - const line8 = - '%macro macroName( arr, arr/* / store source */3 ) /* / store source */;/* / store source */' - expect(strictMacroDefinition.test(line8, 1)).toEqual([]) - - const line9 = '%macro macroName(var1, var2=with space, var3=);' - expect(strictMacroDefinition.test(line9, 1)).toEqual([]) - - const line10 = '%macro macroName()/ /* some comment */ store source;' - expect(strictMacroDefinition.test(line10, 1)).toEqual([]) - - const line11 = '`%macro macroName() /* / store source */;' - expect(strictMacroDefinition.test(line11, 1)).toEqual([]) - }) - - it('should return an array with a single diagnostic when Macro definition has space in param', () => { - const line = '%macro somemacro(va r1);' - expect(strictMacroDefinition.test(line, 1)).toEqual([ - { - message: `Param 'va r1' cannot have space`, - lineNumber: 1, - startColumnNumber: 18, - endColumnNumber: 22, - severity: Severity.Warning - } - ]) - }) - - it('should return an array with a two diagnostics when Macro definition has space in params', () => { - const line = '%macro somemacro(var1, var 2, v ar3, var4);' - expect(strictMacroDefinition.test(line, 1)).toEqual([ - { - message: `Param 'var 2' cannot have space`, - lineNumber: 1, - startColumnNumber: 24, - endColumnNumber: 28, - severity: Severity.Warning - }, - { - message: `Param 'v ar3' cannot have space`, - lineNumber: 1, - startColumnNumber: 31, - endColumnNumber: 35, - severity: Severity.Warning - } - ]) - }) - - it('should return an array with a two diagnostics when Macro definition has space in params - special case', () => { - const line = - '%macro macroName( arr, ar r/* / store source */ 3 ) /* / store source */;/* / store source */' - expect(strictMacroDefinition.test(line, 1)).toEqual([ - { - message: `Param 'ar r 3' cannot have space`, - lineNumber: 1, - startColumnNumber: 24, - endColumnNumber: 49, - severity: Severity.Warning - } - ]) - }) - - it('should return an array with a single diagnostic when Macro definition has invalid option', () => { - const line = '%macro somemacro(var1, var2)/minXoperator;' - expect(strictMacroDefinition.test(line, 1)).toEqual([ - { - message: `Option 'minXoperator' is not valid`, - lineNumber: 1, - startColumnNumber: 30, - endColumnNumber: 41, - severity: Severity.Warning - } - ]) - }) - - it('should return an array with a two diagnostics when Macro definition has invalid options', () => { - const line = - '%macro somemacro(var1, var2)/ store invalidoption secure ;' - expect(strictMacroDefinition.test(line, 1)).toEqual([ - { - message: `Option 'invalidoption' is not valid`, - lineNumber: 1, - startColumnNumber: 39, - endColumnNumber: 51, - severity: Severity.Warning - } - ]) - }) -}) diff --git a/src/rules/line/strictMacroDefinition.ts b/src/rules/line/strictMacroDefinition.ts deleted file mode 100644 index 246ba7f..0000000 --- a/src/rules/line/strictMacroDefinition.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Diagnostic } from '../../types/Diagnostic' -import { LintConfig } from '../../types' -import { LineLintRule } from '../../types/LintRule' -import { LintRuleType } from '../../types/LintRuleType' -import { Severity } from '../../types/Severity' -import { parseMacros } from '../../utils/parseMacros' - -const name = 'strictMacroDefinition' -const description = 'Enforce strictly rules of macro definition syntax.' -const message = 'Incorrent Macro Definition Syntax' - -const validOptions = [ - 'CMD', - 'DES', - 'MINDELIMITER', - 'MINOPERATOR', - 'NOMINOPERATOR', - 'PARMBUFF', - 'SECURE', - 'NOSECURE', - 'STMT', - 'SOURCE', - 'SRC', - 'STORE' -] - -const test = (value: string, lineNumber: number) => { - const diagnostics: Diagnostic[] = [] - - const macros = parseMacros(value) - const declaration = macros[0]?.declaration - if (!declaration) return [] - - const regExpParams = new RegExp(/\((.*?)\)/) - const regExpParamsResult = regExpParams.exec(declaration) - - let _declaration = declaration - if (regExpParamsResult) { - const paramsPresent = regExpParamsResult[1] - - const paramsTrimmed = paramsPresent.trim() - const params = paramsTrimmed.split(',') - params.forEach((param) => { - const trimedParam = param.split('=')[0].trim() - - let paramStartIndex: number = 1, - paramEndIndex: number = value.length - - if (value.indexOf(trimedParam) === -1) { - const comment = '/\\*(.*?)\\*/' - for (let i = 1; i < trimedParam.length; i++) { - const paramWithComment = - trimedParam.slice(0, i) + comment + trimedParam.slice(i) - const regEx = new RegExp(paramWithComment) - const result = regEx.exec(value) - if (result) { - paramStartIndex = value.indexOf(result[0]) - paramEndIndex = value.indexOf(result[0]) + result[0].length - break - } - } - } else { - paramStartIndex = value.indexOf(trimedParam) - paramEndIndex = value.indexOf(trimedParam) + trimedParam.length - } - - if (trimedParam.includes(' ')) { - diagnostics.push({ - message: `Param '${trimedParam}' cannot have space`, - lineNumber, - startColumnNumber: paramStartIndex + 1, - endColumnNumber: paramEndIndex, - severity: Severity.Warning - }) - } - }) - - _declaration = declaration.split(`(${paramsPresent})`)[1] - } - - const optionsPresent = _declaration.split('/')?.[1]?.trim().split(' ') - - optionsPresent - ?.filter((o) => !!o) - .forEach((option) => { - const trimmedOption = option.trim() - if (!validOptions.includes(trimmedOption.toUpperCase())) { - diagnostics.push({ - message: `Option '${trimmedOption}' is not valid`, - lineNumber, - startColumnNumber: value.indexOf(trimmedOption) + 1, - endColumnNumber: value.indexOf(trimmedOption) + trimmedOption.length, - severity: Severity.Warning - }) - } - }) - - return diagnostics -} - -/** - * Lint rule that checks if a line has followed syntax for macro definition - */ -export const strictMacroDefinition: LineLintRule = { - type: LintRuleType.Line, - name, - description, - message, - test -} diff --git a/src/types/LintConfig.ts b/src/types/LintConfig.ts index 1ecbd06..f8fe2d3 100644 --- a/src/types/LintConfig.ts +++ b/src/types/LintConfig.ts @@ -3,15 +3,15 @@ import { hasMacroNameInMend, noNestedMacros, hasMacroParentheses, - lineEndings + lineEndings, + strictMacroDefinition } from '../rules/file' import { indentationMultiple, maxLineLength, noEncodedPasswords, noTabIndentation, - noTrailingSpaces, - strictMacroDefinition + noTrailingSpaces } from '../rules/line' import { lowerCaseFileNames, noSpacesInFileNames } from '../rules/path' import { LineEndings } from './LineEndings' @@ -93,7 +93,7 @@ export class LintConfig { } if (json?.strictMacroDefinition) { - this.lineLintRules.push(strictMacroDefinition) + this.fileLintRules.push(strictMacroDefinition) } } } diff --git a/src/utils/getLintConfig.spec.ts b/src/utils/getLintConfig.spec.ts index 0374ca1..75be58d 100644 --- a/src/utils/getLintConfig.spec.ts +++ b/src/utils/getLintConfig.spec.ts @@ -2,8 +2,8 @@ import * as fileModule from '@sasjs/utils/file' import { LintConfig } from '../types/LintConfig' import { getLintConfig } from './getLintConfig' -const expectedFileLintRulesCount = 4 -const expectedLineLintRulesCount = 6 +const expectedFileLintRulesCount = 5 +const expectedLineLintRulesCount = 5 const expectedPathLintRulesCount = 2 describe('getLintConfig', () => {