1
0
mirror of https://github.com/sasjs/lint.git synced 2026-01-05 03:30:06 +00:00

feat: add new property severityLevel

This commit is contained in:
2022-11-16 22:40:17 +05:00
parent 8031468926
commit 0cff87fe12
18 changed files with 213 additions and 33 deletions

View File

@@ -18,7 +18,7 @@ export const processFile = (
): Diagnostic[] => {
const diagnostics: Diagnostic[] = []
config.pathLintRules.forEach((rule) => {
diagnostics.push(...rule.test(filePath))
diagnostics.push(...rule.test(filePath, config))
})
return diagnostics
@@ -27,7 +27,7 @@ export const processFile = (
const processContent = (config: LintConfig, content: string): Diagnostic[] => {
const diagnostics: Diagnostic[] = []
config.fileLintRules.forEach((rule) => {
diagnostics.push(...rule.test(content))
diagnostics.push(...rule.test(content, config))
})
return diagnostics

View File

@@ -11,8 +11,11 @@ const description =
const message = 'File missing Doxygen header'
const messageForSingleAsterisk =
'File not following Doxygen header style, use double asterisks'
const test = (value: string, config?: LintConfig) => {
const lineEnding = config?.lineEndings === LineEndings.CRLF ? '\r\n' : '\n'
const severity = config?.severityLevel[name] || Severity.Warning
try {
const hasFileHeader = value.trimStart().startsWith('/**')
if (hasFileHeader) return []
@@ -27,7 +30,7 @@ const test = (value: string, config?: LintConfig) => {
.length + 1,
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
severity
}
]
@@ -37,7 +40,7 @@ const test = (value: string, config?: LintConfig) => {
lineNumber: 1,
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
severity
}
]
} catch (e) {
@@ -47,7 +50,7 @@ const test = (value: string, config?: LintConfig) => {
lineNumber: 1,
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
severity
}
]
}

View File

@@ -11,11 +11,14 @@ const name = 'hasMacroNameInMend'
const description =
'Enforces the presence of the macro name in each %mend statement.'
const message = '%mend statement has missing or incorrect macro name'
const test = (value: string, config?: LintConfig) => {
const lineEnding = config?.lineEndings === LineEndings.CRLF ? '\r\n' : '\n'
const lines: string[] = value ? value.split(lineEnding) : []
const macros = parseMacros(value, config)
const severity = config?.severityLevel[name] || Severity.Warning
const diagnostics: Diagnostic[] = []
macros.forEach((macro) => {
if (macro.startLineNumbers.length === 0 && macro.endLineNumber !== null) {
const endLine = lines[macro.endLineNumber - 1]
@@ -25,7 +28,7 @@ const test = (value: string, config?: LintConfig) => {
startColumnNumber: getColumnNumber(endLine, '%mend'),
endColumnNumber:
getColumnNumber(endLine, '%mend') + macro.termination.length,
severity: Severity.Warning
severity
})
} else if (
macro.endLineNumber === null &&
@@ -36,7 +39,7 @@ const test = (value: string, config?: LintConfig) => {
lineNumber: macro.startLineNumbers![0],
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
severity
})
} else if (macro.mismatchedMendMacroName) {
const endLine = lines[(macro.endLineNumber as number) - 1]
@@ -53,7 +56,7 @@ const test = (value: string, config?: LintConfig) => {
getColumnNumber(endLine, macro.mismatchedMendMacroName) +
macro.mismatchedMendMacroName.length -
1,
severity: Severity.Warning
severity
})
} else if (!macro.hasMacroNameInMend) {
const endLine = lines[(macro.endLineNumber as number) - 1]
@@ -62,7 +65,7 @@ const test = (value: string, config?: LintConfig) => {
lineNumber: macro.endLineNumber as number,
startColumnNumber: getColumnNumber(endLine, '%mend'),
endColumnNumber: getColumnNumber(endLine, '%mend') + 6,
severity: Severity.Warning
severity
})
}
})

View File

@@ -9,9 +9,12 @@ import { LintConfig } from '../../types'
const name = 'hasMacroParentheses'
const description = 'Enforces the presence of parentheses in macro definitions.'
const message = 'Macro definition missing parentheses'
const test = (value: string, config?: LintConfig) => {
const diagnostics: Diagnostic[] = []
const macros = parseMacros(value, config)
const severity = config?.severityLevel[name] || Severity.Warning
macros.forEach((macro) => {
if (!macro.name) {
diagnostics.push({
@@ -24,7 +27,7 @@ const test = (value: string, config?: LintConfig) => {
endColumnNumber:
getColumnNumber(macro.declarationLines![0], '%macro') +
macro.declaration.length,
severity: Severity.Warning
severity
})
} else if (!macro.declarationLines.find((dl) => dl.includes('('))) {
const macroNameLineIndex = macro.declarationLines.findIndex((dl) =>
@@ -44,7 +47,7 @@ const test = (value: string, config?: LintConfig) => {
) +
macro.name.length -
1,
severity: Severity.Warning
severity
})
}
})

View File

@@ -7,6 +7,7 @@ import { Severity } from '../../types/Severity'
const name = 'lineEndings'
const description = 'Ensures line endings conform to the configured type.'
const message = 'Incorrect line ending - {actual} instead of {expected}'
const test = (value: string, config?: LintConfig) => {
const lineEndingConfig = config?.lineEndings || LineEndings.LF
const expectedLineEnding =
@@ -18,8 +19,10 @@ const test = (value: string, config?: LintConfig) => {
.replace(/\n/g, '{lf}')
.split(new RegExp(`(?<=${expectedLineEnding})`))
const diagnostics: Diagnostic[] = []
const severity = config?.severityLevel[name] || Severity.Warning
let indexOffset = 0
lines.forEach((line, index) => {
if (line.endsWith(incorrectLineEnding)) {
diagnostics.push({
@@ -29,7 +32,7 @@ const test = (value: string, config?: LintConfig) => {
lineNumber: index + 1 + indexOffset,
startColumnNumber: line.indexOf(incorrectLineEnding),
endColumnNumber: line.indexOf(incorrectLineEnding) + 1,
severity: Severity.Warning
severity
})
} else {
const splitLine = line.split(new RegExp(`(?<=${incorrectLineEnding})`))
@@ -51,7 +54,7 @@ const test = (value: string, config?: LintConfig) => {
lineNumber: index + i + 1,
startColumnNumber: l.indexOf(incorrectLineEnding),
endColumnNumber: l.indexOf(incorrectLineEnding) + 1,
severity: Severity.Warning
severity
})
}
})

View File

@@ -10,11 +10,14 @@ import { LineEndings } from '../../types/LineEndings'
const name = 'noNestedMacros'
const description = 'Enfoces the absence of nested macro definitions.'
const message = `Macro definition for '{macro}' present in macro '{parent}'`
const test = (value: string, config?: LintConfig) => {
const lineEnding = config?.lineEndings === LineEndings.CRLF ? '\r\n' : '\n'
const lines: string[] = value ? value.split(lineEnding) : []
const diagnostics: Diagnostic[] = []
const macros = parseMacros(value, config)
const severity = config?.severityLevel[name] || Severity.Warning
macros
.filter((m) => !!m.parentMacro)
.forEach((macro) => {
@@ -34,7 +37,7 @@ const test = (value: string, config?: LintConfig) => {
) +
lines[(macro.startLineNumbers![0] as number) - 1].trim().length -
1,
severity: Severity.Warning
severity
})
})
return diagnostics

View File

@@ -25,9 +25,11 @@ const validOptions = [
const processParams = (
content: string,
macro: Macro,
diagnostics: Diagnostic[]
diagnostics: Diagnostic[],
config?: LintConfig
): string => {
const declaration = macro.declaration
const severity = config?.severityLevel[name] || Severity.Warning
const regExpParams = new RegExp(/(?<=\().*(?=\))/)
const regExpParamsResult = regExpParams.exec(declaration)
@@ -88,7 +90,7 @@ const processParams = (
lineNumber: paramLineNumber,
startColumnNumber: paramStartIndex + 1,
endColumnNumber: paramEndIndex,
severity: Severity.Warning
severity
})
}
})
@@ -101,9 +103,11 @@ const processParams = (
const processOptions = (
_declaration: string,
macro: Macro,
diagnostics: Diagnostic[]
diagnostics: Diagnostic[],
config?: LintConfig
): void => {
let optionsPresent = _declaration.split('/')?.[1]?.trim()
const severity = config?.severityLevel[name] || Severity.Warning
if (optionsPresent) {
const regex = new RegExp(/="(.*?)"/, 'g')
@@ -136,7 +140,7 @@ const processOptions = (
startColumnNumber: declarationLine.indexOf(trimmedOption) + 1,
endColumnNumber:
declarationLine.indexOf(trimmedOption) + trimmedOption.length,
severity: Severity.Warning
severity
})
}
})
@@ -149,9 +153,9 @@ const test = (value: string, config?: LintConfig) => {
const macros = parseMacros(value, config)
macros.forEach((macro) => {
const _declaration = processParams(value, macro, diagnostics)
const _declaration = processParams(value, macro, diagnostics, config)
processOptions(_declaration, macro, diagnostics)
processOptions(_declaration, macro, diagnostics, config)
})
return diagnostics

View File

@@ -6,9 +6,11 @@ import { Severity } from '../../types/Severity'
const name = 'indentationMultiple'
const description = 'Ensure indentation by a multiple of the configured number.'
const message = 'Line has incorrect indentation'
const test = (value: string, lineNumber: number, config?: LintConfig) => {
if (!value.startsWith(' ')) return []
const severity = config?.severityLevel[name] || Severity.Warning
const indentationMultiple = isNaN(config?.indentationMultiple as number)
? 2
: config!.indentationMultiple
@@ -24,7 +26,7 @@ const test = (value: string, lineNumber: number, config?: LintConfig) => {
lineNumber,
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
severity
}
]
}

View File

@@ -6,7 +6,9 @@ import { Severity } from '../../types/Severity'
const name = 'maxLineLength'
const description = 'Restrict lines to the specified length.'
const message = 'Line exceeds maximum length'
const test = (value: string, lineNumber: number, config?: LintConfig) => {
const severity = config?.severityLevel[name] || Severity.Warning
const maxLineLength = config?.maxLineLength || 80
if (value.length <= maxLineLength) return []
return [
@@ -15,7 +17,7 @@ const test = (value: string, lineNumber: number, config?: LintConfig) => {
lineNumber,
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
severity
}
]
}

View File

@@ -1,3 +1,4 @@
import { LintConfig } from '../../types'
import { LineLintRule } from '../../types/LintRule'
import { LintRuleType } from '../../types/LintRuleType'
import { Severity } from '../../types/Severity'
@@ -5,7 +6,9 @@ import { Severity } from '../../types/Severity'
const name = 'noEncodedPasswords'
const description = 'Disallow encoded passwords in SAS code.'
const message = 'Line contains encoded password'
const test = (value: string, lineNumber: number) => {
const test = (value: string, lineNumber: number, config?: LintConfig) => {
const severity = config?.severityLevel[name] || Severity.Error
const regex = new RegExp(/{sas(\d{2,4}|enc)}[^;"'\s]*/, 'gi')
const matches = value.match(regex)
if (!matches || !matches.length) return []
@@ -14,7 +17,7 @@ const test = (value: string, lineNumber: number) => {
lineNumber,
startColumnNumber: value.indexOf(match) + 1,
endColumnNumber: value.indexOf(match) + match.length + 1,
severity: Severity.Error
severity
}))
}

View File

@@ -1,3 +1,4 @@
import { LintConfig } from '../../types'
import { LineLintRule } from '../../types/LintRule'
import { LintRuleType } from '../../types/LintRuleType'
import { Severity } from '../../types/Severity'
@@ -5,7 +6,9 @@ import { Severity } from '../../types/Severity'
const name = 'noTabs'
const description = 'Disallow indenting with tabs.'
const message = 'Line is indented with a tab'
const test = (value: string, lineNumber: number) => {
const test = (value: string, lineNumber: number, config?: LintConfig) => {
const severity = config?.severityLevel[name] || Severity.Warning
if (!value.startsWith('\t')) return []
return [
{

View File

@@ -1,3 +1,4 @@
import { LintConfig } from '../../types'
import { LineLintRule } from '../../types/LintRule'
import { LintRuleType } from '../../types/LintRuleType'
import { Severity } from '../../types/Severity'
@@ -5,8 +6,11 @@ import { Severity } from '../../types/Severity'
const name = 'noTrailingSpaces'
const description = 'Disallow trailing spaces on lines.'
const message = 'Line contains trailing spaces'
const test = (value: string, lineNumber: number) =>
value.trimEnd() === value
const test = (value: string, lineNumber: number, config?: LintConfig) => {
const severity = config?.severityLevel[name] || Severity.Warning
return value.trimEnd() === value
? []
: [
{
@@ -14,9 +18,11 @@ const test = (value: string, lineNumber: number) =>
lineNumber,
startColumnNumber: value.trimEnd().length + 1,
endColumnNumber: value.length,
severity: Severity.Warning
severity
}
]
}
const fix = (value: string) => value.trimEnd()
/**

View File

@@ -2,20 +2,25 @@ import { PathLintRule } from '../../types/LintRule'
import { LintRuleType } from '../../types/LintRuleType'
import { Severity } from '../../types/Severity'
import path from 'path'
import { LintConfig } from '../../types'
const name = 'lowerCaseFileNames'
const description = 'Enforce the use of lower case file names.'
const message = 'File name contains uppercase characters'
const test = (value: string) => {
const test = (value: string, config?: LintConfig) => {
const severity = config?.severityLevel[name] || Severity.Warning
const fileName = path.basename(value)
if (fileName.toLocaleLowerCase() === fileName) return []
return [
{
message,
lineNumber: 1,
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
severity
}
]
}

View File

@@ -2,12 +2,16 @@ import { PathLintRule } from '../../types/LintRule'
import { LintRuleType } from '../../types/LintRuleType'
import { Severity } from '../../types/Severity'
import path from 'path'
import { LintConfig } from '../../types'
const name = 'noSpacesInFileNames'
const description = 'Enforce the absence of spaces within file names.'
const message = 'File name contains spaces'
const test = (value: string) => {
const test = (value: string, config?: LintConfig) => {
const severity = config?.severityLevel[name] || Severity.Warning
const fileName = path.basename(value)
if (fileName.includes(' ')) {
return [
{
@@ -15,7 +19,7 @@ const test = (value: string) => {
lineNumber: 1,
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
severity
}
]
}

View File

@@ -1,6 +1,7 @@
import { LineEndings } from './LineEndings'
import { LintConfig } from './LintConfig'
import { LintRuleType } from './LintRuleType'
import { Severity } from './Severity'
describe('LintConfig', () => {
it('should create an empty instance', () => {
@@ -123,6 +124,23 @@ describe('LintConfig', () => {
expect(config.lineEndings).toEqual(LineEndings.CRLF)
})
it('should create an instance with the severityLevel config', () => {
const config = new LintConfig({
severityLevel: {
hasDoxygenHeader: 'warn',
maxLineLength: 'error',
noTrailingSpaces: 'error'
}
})
expect(config).toBeTruthy()
expect(config.severityLevel).toEqual({
hasDoxygenHeader: Severity.Warning,
maxLineLength: Severity.Error,
noTrailingSpaces: Severity.Error
})
})
it('should create an instance with the line endings set to LF by default', () => {
const config = new LintConfig({})

View File

@@ -17,6 +17,7 @@ import { lowerCaseFileNames, noSpacesInFileNames } from '../rules/path'
import { LineEndings } from './LineEndings'
import { FileLintRule, LineLintRule, PathLintRule } from './LintRule'
import { getDefaultHeader } from '../utils'
import { Severity } from './Severity'
/**
* LintConfig is the logical representation of the .sasjslint file.
@@ -34,6 +35,7 @@ export class LintConfig {
readonly indentationMultiple: number = 2
readonly lineEndings: LineEndings = LineEndings.LF
readonly defaultHeader: string = getDefaultHeader()
readonly severityLevel: { [key: string]: Severity } = {}
constructor(json?: any) {
if (json?.ignoreList) {
@@ -116,5 +118,12 @@ export class LintConfig {
if (json?.strictMacroDefinition) {
this.fileLintRules.push(strictMacroDefinition)
}
if (json?.severityLevel) {
for (const [rule, severity] of Object.entries(json.severityLevel)) {
if (severity === 'warn') this.severityLevel[rule] = Severity.Warning
if (severity === 'error') this.severityLevel[rule] = Severity.Error
}
}
}
}

View File

@@ -36,5 +36,5 @@ export interface FileLintRule extends LintRule {
*/
export interface PathLintRule extends LintRule {
type: LintRuleType.Path
test: (value: string) => Diagnostic[]
test: (value: string, config?: LintConfig) => Diagnostic[]
}