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

feat(*): add line endings rule, add automatic formatting for fixable violations

This commit is contained in:
Krishna Acondy
2021-04-19 21:00:38 +01:00
parent 99813f04c0
commit 519a0164b5
32 changed files with 941 additions and 259 deletions

View File

@@ -2,99 +2,97 @@ import { Diagnostic } from '../../types/Diagnostic'
import { FileLintRule } from '../../types/LintRule'
import { LintRuleType } from '../../types/LintRuleType'
import { Severity } from '../../types/Severity'
import { trimComments } from '../../utils/trimComments'
import { getColumnNumber } from '../../utils/getColumnNumber'
import { LintConfig } from '../../types'
import { LineEndings } from '../../types/LineEndings'
import { parseMacros } from '../../utils/parseMacros'
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) => {
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 diagnostics: Diagnostic[] = []
const lines: string[] = value ? value.split('\n') : []
const declaredMacros: { name: string; lineNumber: number }[] = []
let isCommentStarted = false
lines.forEach((line, lineIndex) => {
const { statement: trimmedLine, commentStarted } = trimComments(
line,
isCommentStarted
)
isCommentStarted = commentStarted
const statements: string[] = trimmedLine ? trimmedLine.split(';') : []
statements.forEach((statement) => {
const { statement: trimmedStatement, commentStarted } = trimComments(
statement,
isCommentStarted
)
isCommentStarted = commentStarted
if (trimmedStatement.startsWith('%macro ')) {
const macroName = trimmedStatement
.slice(7, trimmedStatement.length)
.trim()
.split('(')[0]
if (macroName)
declaredMacros.push({
name: macroName,
lineNumber: lineIndex + 1
})
} else if (trimmedStatement.startsWith('%mend')) {
const declaredMacro = declaredMacros.pop()
const macroName = trimmedStatement
.split(' ')
.filter((s: string) => !!s)[1]
if (!declaredMacro) {
diagnostics.push({
message: `%mend statement is redundant`,
lineNumber: lineIndex + 1,
startColumnNumber: getColumnNumber(line, '%mend'),
endColumnNumber:
getColumnNumber(line, '%mend') + trimmedStatement.length,
severity: Severity.Warning
})
} else if (!macroName) {
diagnostics.push({
message: `%mend statement is missing macro name - ${
declaredMacro!.name
}`,
lineNumber: lineIndex + 1,
startColumnNumber: getColumnNumber(line, '%mend'),
endColumnNumber: getColumnNumber(line, '%mend') + 6,
severity: Severity.Warning
})
} else if (macroName !== declaredMacro!.name) {
diagnostics.push({
message: `%mend statement has mismatched macro name, it should be '${
declaredMacro!.name
}'`,
lineNumber: lineIndex + 1,
startColumnNumber: getColumnNumber(line, macroName),
endColumnNumber:
getColumnNumber(line, macroName) + macroName.length - 1,
severity: Severity.Warning
})
}
}
})
})
declaredMacros.forEach((declaredMacro) => {
diagnostics.push({
message: `Missing %mend statement for macro - ${declaredMacro.name}`,
lineNumber: declaredMacro.lineNumber,
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
})
macros.forEach((macro) => {
if (macro.startLineNumber === null && macro.endLineNumber !== null) {
diagnostics.push({
message: `%mend statement is redundant`,
lineNumber: macro.endLineNumber,
startColumnNumber: getColumnNumber(
lines[macro.endLineNumber - 1],
'%mend'
),
endColumnNumber:
getColumnNumber(lines[macro.endLineNumber - 1], '%mend') +
lines[macro.endLineNumber - 1].trim().length -
1,
severity: Severity.Warning
})
} else if (macro.endLineNumber === null && macro.startLineNumber !== null) {
diagnostics.push({
message: `Missing %mend statement for macro - ${macro.name}`,
lineNumber: macro.startLineNumber,
startColumnNumber: 1,
endColumnNumber: 1,
severity: Severity.Warning
})
} else if (macro.mismatchedMendMacroName) {
diagnostics.push({
message: `%mend statement has mismatched macro name, it should be '${
macro!.name
}'`,
lineNumber: macro.endLineNumber as number,
startColumnNumber: getColumnNumber(
lines[(macro.endLineNumber as number) - 1],
macro.mismatchedMendMacroName
),
endColumnNumber:
getColumnNumber(
lines[(macro.endLineNumber as number) - 1],
macro.mismatchedMendMacroName
) +
macro.mismatchedMendMacroName.length -
1,
severity: Severity.Warning
})
} else if (!macro.hasMacroNameInMend) {
diagnostics.push({
message: `%mend statement is missing macro name - ${macro.name}`,
lineNumber: macro.endLineNumber as number,
startColumnNumber: getColumnNumber(
lines[(macro.endLineNumber as number) - 1],
'%mend'
),
endColumnNumber:
getColumnNumber(lines[(macro.endLineNumber as number) - 1], '%mend') +
6,
severity: Severity.Warning
})
}
})
return diagnostics
}
const fix = (value: string, config?: LintConfig): string => {
const lineEnding = config?.lineEndings === LineEndings.CRLF ? '\r\n' : '\n'
let formattedText = value
const macros = parseMacros(value, config)
macros
.filter((macro) => !macro.hasMacroNameInMend)
.forEach((macro) => {
formattedText = formattedText.replace(
macro.termination,
`%mend ${macro.name};${lineEnding}`
)
})
return formattedText
}
/**
* Lint rule that checks for the presence of macro name in %mend statement.
*/
@@ -103,5 +101,6 @@ export const hasMacroNameInMend: FileLintRule = {
name,
description,
message,
test
test,
fix
}