mirror of
https://github.com/sasjs/lint.git
synced 2025-12-10 17:34:36 +00:00
Merge branch 'main' into dependabot/npm_and_yarn/ts-jest-26.5.6
This commit is contained in:
@@ -16,7 +16,8 @@
|
|||||||
"noSpacesInFileNames": true,
|
"noSpacesInFileNames": true,
|
||||||
"noTabIndentation": true,
|
"noTabIndentation": true,
|
||||||
"noTrailingSpaces": true,
|
"noTrailingSpaces": true,
|
||||||
"lineEndings": "lf"
|
"lineEndings": "lf",
|
||||||
|
"strictMacroDefinition": true
|
||||||
},
|
},
|
||||||
"examples": [
|
"examples": [
|
||||||
{
|
{
|
||||||
@@ -31,7 +32,8 @@
|
|||||||
"hasMacroNameInMend": true,
|
"hasMacroNameInMend": true,
|
||||||
"noNestedMacros": true,
|
"noNestedMacros": true,
|
||||||
"hasMacroParentheses": true,
|
"hasMacroParentheses": true,
|
||||||
"lineEndings": "crlf"
|
"lineEndings": "crlf",
|
||||||
|
"strictMacroDefinition": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -130,6 +132,14 @@
|
|||||||
"description": "Enforces the configured terminating character for each line. Shows a warning when incorrect line endings are present.",
|
"description": "Enforces the configured terminating character for each line. Shows a warning when incorrect line endings are present.",
|
||||||
"default": "lf",
|
"default": "lf",
|
||||||
"examples": ["lf", "crlf"]
|
"examples": ["lf", "crlf"]
|
||||||
|
},
|
||||||
|
"strictMacroDefinition": {
|
||||||
|
"$id": "#/properties/strictMacroDefinition",
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "strictMacroDefinition",
|
||||||
|
"description": "Enforces Macro Definition syntax. Shows a warning when incorrect syntax is used.",
|
||||||
|
"default": true,
|
||||||
|
"examples": [true, false]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export { maxLineLength } from './maxLineLength'
|
|||||||
export { noEncodedPasswords } from './noEncodedPasswords'
|
export { noEncodedPasswords } from './noEncodedPasswords'
|
||||||
export { noTabIndentation } from './noTabIndentation'
|
export { noTabIndentation } from './noTabIndentation'
|
||||||
export { noTrailingSpaces } from './noTrailingSpaces'
|
export { noTrailingSpaces } from './noTrailingSpaces'
|
||||||
|
export { strictMacroDefinition } from './strictMacroDefinition'
|
||||||
|
|||||||
88
src/rules/line/strictMacroDefinition.spec.ts
Normal file
88
src/rules/line/strictMacroDefinition.spec.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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([])
|
||||||
|
})
|
||||||
|
|
||||||
|
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 param', () => {
|
||||||
|
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 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
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
88
src/rules/line/strictMacroDefinition.ts
Normal file
88
src/rules/line/strictMacroDefinition.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
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()
|
||||||
|
if (trimedParam.includes(' ')) {
|
||||||
|
diagnostics.push({
|
||||||
|
message: `Param '${trimedParam}' cannot have space`,
|
||||||
|
lineNumber,
|
||||||
|
startColumnNumber: value.indexOf(trimedParam) + 1,
|
||||||
|
endColumnNumber: value.indexOf(trimedParam) + trimedParam.length,
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
maxLineLength,
|
maxLineLength,
|
||||||
noEncodedPasswords,
|
noEncodedPasswords,
|
||||||
noTabIndentation,
|
noTabIndentation,
|
||||||
noTrailingSpaces
|
noTrailingSpaces,
|
||||||
|
strictMacroDefinition
|
||||||
} from '../rules/line'
|
} from '../rules/line'
|
||||||
import { lowerCaseFileNames, noSpacesInFileNames } from '../rules/path'
|
import { lowerCaseFileNames, noSpacesInFileNames } from '../rules/path'
|
||||||
import { LineEndings } from './LineEndings'
|
import { LineEndings } from './LineEndings'
|
||||||
@@ -90,5 +91,9 @@ export class LintConfig {
|
|||||||
if (json?.hasMacroParentheses) {
|
if (json?.hasMacroParentheses) {
|
||||||
this.fileLintRules.push(hasMacroParentheses)
|
this.fileLintRules.push(hasMacroParentheses)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (json?.strictMacroDefinition) {
|
||||||
|
this.lineLintRules.push(strictMacroDefinition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { LintConfig } from '../types/LintConfig'
|
|||||||
import { getLintConfig } from './getLintConfig'
|
import { getLintConfig } from './getLintConfig'
|
||||||
|
|
||||||
const expectedFileLintRulesCount = 4
|
const expectedFileLintRulesCount = 4
|
||||||
const expectedLineLintRulesCount = 5
|
const expectedLineLintRulesCount = 6
|
||||||
const expectedPathLintRulesCount = 2
|
const expectedPathLintRulesCount = 2
|
||||||
|
|
||||||
describe('getLintConfig', () => {
|
describe('getLintConfig', () => {
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ export const DefaultLintConfiguration = {
|
|||||||
indentationMultiple: 2,
|
indentationMultiple: 2,
|
||||||
hasMacroNameInMend: true,
|
hasMacroNameInMend: true,
|
||||||
noNestedMacros: true,
|
noNestedMacros: true,
|
||||||
hasMacroParentheses: true
|
hasMacroParentheses: true,
|
||||||
|
strictMacroDefinition: true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,6 +25,76 @@ describe('parseMacros', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single macro having parameters', () => {
|
||||||
|
const text = `%macro test(var,sum);
|
||||||
|
%put 'hello';
|
||||||
|
%mend`
|
||||||
|
|
||||||
|
const macros = parseMacros(text, new LintConfig())
|
||||||
|
|
||||||
|
expect(macros.length).toEqual(1)
|
||||||
|
expect(macros).toContainEqual({
|
||||||
|
name: 'test',
|
||||||
|
declarationLine: '%macro test(var,sum);',
|
||||||
|
terminationLine: '%mend',
|
||||||
|
declaration: '%macro test(var,sum)',
|
||||||
|
termination: '%mend',
|
||||||
|
startLineNumber: 1,
|
||||||
|
endLineNumber: 3,
|
||||||
|
parentMacro: '',
|
||||||
|
hasMacroNameInMend: false,
|
||||||
|
hasParentheses: false,
|
||||||
|
mismatchedMendMacroName: ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single macro having PARMBUFF option', () => {
|
||||||
|
const text = `%macro test/parmbuff;
|
||||||
|
%put 'hello';
|
||||||
|
%mend`
|
||||||
|
|
||||||
|
const macros = parseMacros(text, new LintConfig())
|
||||||
|
|
||||||
|
expect(macros.length).toEqual(1)
|
||||||
|
expect(macros).toContainEqual({
|
||||||
|
name: 'test',
|
||||||
|
declarationLine: '%macro test/parmbuff;',
|
||||||
|
terminationLine: '%mend',
|
||||||
|
declaration: '%macro test/parmbuff',
|
||||||
|
termination: '%mend',
|
||||||
|
startLineNumber: 1,
|
||||||
|
endLineNumber: 3,
|
||||||
|
parentMacro: '',
|
||||||
|
hasMacroNameInMend: false,
|
||||||
|
hasParentheses: false,
|
||||||
|
mismatchedMendMacroName: ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single macro having paramerter & SOURCE option', () => {
|
||||||
|
const text = `/* commentary */ %macro foobar(arg) /store source
|
||||||
|
des="This macro does not do much";
|
||||||
|
%put 'hello';
|
||||||
|
%mend`
|
||||||
|
|
||||||
|
const macros = parseMacros(text, new LintConfig())
|
||||||
|
|
||||||
|
expect(macros.length).toEqual(1)
|
||||||
|
expect(macros).toContainEqual({
|
||||||
|
name: 'foobar',
|
||||||
|
declarationLine: '/* commentary */ %macro foobar(arg) /store source',
|
||||||
|
terminationLine: '%mend',
|
||||||
|
declaration: '%macro foobar(arg) /store source',
|
||||||
|
termination: '%mend',
|
||||||
|
startLineNumber: 1,
|
||||||
|
endLineNumber: 4,
|
||||||
|
parentMacro: '',
|
||||||
|
hasMacroNameInMend: false,
|
||||||
|
hasParentheses: false,
|
||||||
|
mismatchedMendMacroName: ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should return an array with multiple macros', () => {
|
it('should return an array with multiple macros', () => {
|
||||||
const text = `%macro foo;
|
const text = `%macro foo;
|
||||||
%put 'foo';
|
%put 'foo';
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const parseMacros = (text: string, config?: LintConfig): Macro[] => {
|
|||||||
const name = trimmedStatement
|
const name = trimmedStatement
|
||||||
.slice(7, trimmedStatement.length)
|
.slice(7, trimmedStatement.length)
|
||||||
.trim()
|
.trim()
|
||||||
|
.split('/')[0]
|
||||||
.split('(')[0]
|
.split('(')[0]
|
||||||
macroStack.push({
|
macroStack.push({
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ export const trimComments = (
|
|||||||
|
|
||||||
if (commentStarted || trimmed.startsWith('/*')) {
|
if (commentStarted || trimmed.startsWith('/*')) {
|
||||||
const parts = trimmed.split('*/')
|
const parts = trimmed.split('*/')
|
||||||
if (parts.length > 1) {
|
if (parts.length === 2) {
|
||||||
return {
|
return {
|
||||||
statement: (parts.pop() as string).trim(),
|
statement: (parts.pop() as string).trim(),
|
||||||
commentStarted: false
|
commentStarted: false
|
||||||
}
|
}
|
||||||
|
} else if (parts.length > 2) {
|
||||||
|
parts.shift()
|
||||||
|
return trimComments(parts.join('*/'), false)
|
||||||
} else {
|
} else {
|
||||||
return { statement: '', commentStarted: true }
|
return { statement: '', commentStarted: true }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user