mirror of
https://github.com/sasjs/lint.git
synced 2025-12-10 17:34:36 +00:00
feat: added hasRequiredMacroOptions
This commit is contained in:
@@ -254,6 +254,14 @@ This will highlight lines with trailing spaces. Trailing spaces serve no useful
|
||||
- Default: true
|
||||
- severity: WARNING
|
||||
|
||||
### hasRequiredMacroOptions
|
||||
|
||||
This will require macros to have the options listed as "requiredMacroOptions." This is helpful if you want to ensure all macros are SECURE.
|
||||
|
||||
- Default: true
|
||||
- severity: WARNING
|
||||
|
||||
|
||||
## severityLevel
|
||||
|
||||
This setting allows the default severity to be adjusted. This is helpful when running the lint in a pipeline or git hook. Simply list the rules you would like to adjust along with the desired setting ("warn" or "error"), eg as follows:
|
||||
|
||||
110
src/rules/file/hasRequiredMacroOptions.spec.ts
Normal file
110
src/rules/file/hasRequiredMacroOptions.spec.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { LintConfig, Severity } from '../../types'
|
||||
import { hasRequiredMacroOptions } from './hasRequiredMacroOptions'
|
||||
|
||||
describe('hasRequiredMacroOptions - test', () => {
|
||||
it('should return an empty array when the content has the required macro option(s)', () => {
|
||||
const content = '%macro somemacro/ SECURE;'
|
||||
const config = new LintConfig({
|
||||
hasRequiredMacroOptions: true,
|
||||
requiredMacroOptions: ['SECURE']
|
||||
})
|
||||
expect(hasRequiredMacroOptions.test(content, config)).toEqual([])
|
||||
|
||||
const content2 = '%macro somemacro/ SECURE SRC;'
|
||||
const config2 = new LintConfig({
|
||||
hasRequiredMacroOptions: true,
|
||||
requiredMacroOptions: ['SECURE', 'SRC']
|
||||
})
|
||||
expect(hasRequiredMacroOptions.test(content, config)).toEqual([])
|
||||
|
||||
const content3 = '%macro somemacro/ SECURE SRC;'
|
||||
const config3 = new LintConfig({
|
||||
hasRequiredMacroOptions: true,
|
||||
requiredMacroOptions: ['']
|
||||
})
|
||||
expect(hasRequiredMacroOptions.test(content, config)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when Macro does not contain the required option', () => {
|
||||
const config = new LintConfig({
|
||||
hasRequiredMacroOptions: true,
|
||||
requiredMacroOptions: ['SECURE']
|
||||
})
|
||||
|
||||
const content = '%macro somemacro(var1, var2)/minXoperator;'
|
||||
expect(hasRequiredMacroOptions.test(content, config)).toEqual([
|
||||
{
|
||||
message: `Macro 'somemacro' does not contain the required option 'SECURE'`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 0,
|
||||
endColumnNumber: 0,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
|
||||
const content2 = '%macro somemacro(var1, var2)/ SE CURE;'
|
||||
expect(hasRequiredMacroOptions.test(content2, config)).toEqual([
|
||||
{
|
||||
message: `Macro 'somemacro' does not contain the required option 'SECURE'`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 0,
|
||||
endColumnNumber: 0,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
|
||||
const content3 = '%macro somemacro(var1, var2);'
|
||||
expect(hasRequiredMacroOptions.test(content3, config)).toEqual([
|
||||
{
|
||||
message: `Macro 'somemacro' does not contain the required option 'SECURE'`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 0,
|
||||
endColumnNumber: 0,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a two diagnostics when Macro does not contain the required options', () => {
|
||||
const config = new LintConfig({
|
||||
hasRequiredMacroOptions: true,
|
||||
requiredMacroOptions: ['SRC', 'STMT'],
|
||||
severityLevel: { hasRequiredMacroOptions: 'warn' }
|
||||
})
|
||||
const content = '%macro somemacro(var1, var2)/minXoperator;'
|
||||
expect(hasRequiredMacroOptions.test(content, config)).toEqual([
|
||||
{
|
||||
message: `Macro 'somemacro' does not contain the required option 'SRC'`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 0,
|
||||
endColumnNumber: 0,
|
||||
severity: Severity.Warning
|
||||
},
|
||||
{
|
||||
message: `Macro 'somemacro' does not contain the required option 'STMT'`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 0,
|
||||
endColumnNumber: 0,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a one diagnostic when Macro contains 1 of 2 required options', () => {
|
||||
const config = new LintConfig({
|
||||
hasRequiredMacroOptions: true,
|
||||
requiredMacroOptions: ['SRC', 'STMT'],
|
||||
severityLevel: { hasRequiredMacroOptions: 'error' }
|
||||
})
|
||||
const content = '%macro somemacro(var1, var2)/ SRC;'
|
||||
expect(hasRequiredMacroOptions.test(content, config)).toEqual([
|
||||
{
|
||||
message: `Macro 'somemacro' does not contain the required option 'STMT'`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 0,
|
||||
endColumnNumber: 0,
|
||||
severity: Severity.Error
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
52
src/rules/file/hasRequiredMacroOptions.ts
Normal file
52
src/rules/file/hasRequiredMacroOptions.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Diagnostic, LintConfig, Macro, Severity } from '../../types'
|
||||
import { FileLintRule } from '../../types/LintRule'
|
||||
import { LintRuleType } from '../../types/LintRuleType'
|
||||
import { parseMacros } from '../../utils/parseMacros'
|
||||
|
||||
const name = 'hasRequiredMacroOptions'
|
||||
const description = 'Enforce required macro options'
|
||||
const message = 'Macro defined without required options'
|
||||
|
||||
const processOptions = (
|
||||
macro: Macro,
|
||||
diagnostics: Diagnostic[],
|
||||
config?: LintConfig
|
||||
): void => {
|
||||
let optionsPresent = macro.declaration.split('/')?.[1]?.trim() ?? ''
|
||||
const severity = config?.severityLevel[name] || Severity.Warning
|
||||
|
||||
config?.requiredMacroOptions.forEach((option) => {
|
||||
if (!optionsPresent.includes(option)) {
|
||||
diagnostics.push({
|
||||
message: `Macro '${macro.name}' does not contain the required option '${option}'`,
|
||||
lineNumber: macro.startLineNumbers[0],
|
||||
startColumnNumber: 0,
|
||||
endColumnNumber: 0,
|
||||
severity
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const test = (value: string, config?: LintConfig) => {
|
||||
const diagnostics: Diagnostic[] = []
|
||||
|
||||
const macros = parseMacros(value, config)
|
||||
|
||||
macros.forEach((macro) => {
|
||||
processOptions(macro, diagnostics, config)
|
||||
})
|
||||
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks if a macro has the required options
|
||||
*/
|
||||
export const hasRequiredMacroOptions: FileLintRule = {
|
||||
type: LintRuleType.File,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
@@ -4,3 +4,4 @@ export { hasMacroParentheses } from './hasMacroParentheses'
|
||||
export { lineEndings } from './lineEndings'
|
||||
export { noNestedMacros } from './noNestedMacros'
|
||||
export { strictMacroDefinition } from './strictMacroDefinition'
|
||||
export { hasRequiredMacroOptions } from './hasRequiredMacroOptions'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { hasRequiredMacroOptions } from '../rules/file'
|
||||
import { LineEndings } from './LineEndings'
|
||||
import { LintConfig } from './LintConfig'
|
||||
import { LintRuleType } from './LintRuleType'
|
||||
@@ -168,6 +169,7 @@ describe('LintConfig', () => {
|
||||
hasMacroNameInMend: true,
|
||||
noNestedMacros: true,
|
||||
hasMacroParentheses: true,
|
||||
hasRequiredMacroOptions: true,
|
||||
noGremlins: true,
|
||||
lineEndings: 'lf'
|
||||
})
|
||||
@@ -187,7 +189,7 @@ describe('LintConfig', () => {
|
||||
expect(config.lineLintRules[5].name).toEqual('noGremlins')
|
||||
expect(config.lineLintRules[5].type).toEqual(LintRuleType.Line)
|
||||
|
||||
expect(config.fileLintRules.length).toEqual(6)
|
||||
expect(config.fileLintRules.length).toEqual(7)
|
||||
expect(config.fileLintRules[0].name).toEqual('lineEndings')
|
||||
expect(config.fileLintRules[0].type).toEqual(LintRuleType.File)
|
||||
expect(config.fileLintRules[1].name).toEqual('hasDoxygenHeader')
|
||||
@@ -200,6 +202,8 @@ describe('LintConfig', () => {
|
||||
expect(config.fileLintRules[4].type).toEqual(LintRuleType.File)
|
||||
expect(config.fileLintRules[5].name).toEqual('strictMacroDefinition')
|
||||
expect(config.fileLintRules[5].type).toEqual(LintRuleType.File)
|
||||
expect(config.fileLintRules[6].name).toEqual('hasRequiredMacroOptions')
|
||||
expect(config.fileLintRules[6].type).toEqual(LintRuleType.File)
|
||||
|
||||
expect(config.pathLintRules.length).toEqual(2)
|
||||
expect(config.pathLintRules[0].name).toEqual('noSpacesInFileNames')
|
||||
@@ -207,4 +211,25 @@ describe('LintConfig', () => {
|
||||
expect(config.pathLintRules[1].name).toEqual('lowerCaseFileNames')
|
||||
expect(config.pathLintRules[1].type).toEqual(LintRuleType.Path)
|
||||
})
|
||||
|
||||
it('should throw an error with an invalid value for requiredMacroOptions', () => {
|
||||
expect(
|
||||
() =>
|
||||
new LintConfig({
|
||||
hasRequiredMacroOptions: true,
|
||||
requiredMacroOptions: 'test'
|
||||
})
|
||||
).toThrowError(
|
||||
`Property "requiredMacroOptions" can only be an array of strings.`
|
||||
)
|
||||
expect(
|
||||
() =>
|
||||
new LintConfig({
|
||||
hasRequiredMacroOptions: true,
|
||||
requiredMacroOptions: ['test', 2]
|
||||
})
|
||||
).toThrowError(
|
||||
`Property "requiredMacroOptions" has invalid type of values. It can only contain strings.`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
noNestedMacros,
|
||||
hasMacroParentheses,
|
||||
lineEndings,
|
||||
strictMacroDefinition
|
||||
strictMacroDefinition,
|
||||
hasRequiredMacroOptions
|
||||
} from '../rules/file'
|
||||
import {
|
||||
indentationMultiple,
|
||||
@@ -40,6 +41,7 @@ export class LintConfig {
|
||||
readonly lineEndings: LineEndings = LineEndings.LF
|
||||
readonly defaultHeader: string = getDefaultHeader()
|
||||
readonly severityLevel: { [key: string]: Severity } = {}
|
||||
readonly requiredMacroOptions: string[] = []
|
||||
|
||||
constructor(json?: any) {
|
||||
if (json?.ignoreList) {
|
||||
@@ -132,6 +134,31 @@ export class LintConfig {
|
||||
this.fileLintRules.push(strictMacroDefinition)
|
||||
}
|
||||
|
||||
if (json?.hasRequiredMacroOptions) {
|
||||
this.fileLintRules.push(hasRequiredMacroOptions)
|
||||
|
||||
if (json?.requiredMacroOptions) {
|
||||
if (
|
||||
Array.isArray(json.requiredMacroOptions) &&
|
||||
json.requiredMacroOptions.length > 0
|
||||
) {
|
||||
json.requiredMacroOptions.forEach((item: any) => {
|
||||
if (typeof item === 'string') {
|
||||
this.requiredMacroOptions.push(item)
|
||||
} else {
|
||||
throw new Error(
|
||||
`Property "requiredMacroOptions" has invalid type of values. It can only contain strings.`
|
||||
)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
throw new Error(
|
||||
`Property "requiredMacroOptions" can only be an array of strings.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (json?.noGremlins !== false) {
|
||||
this.lineLintRules.push(noGremlins)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user