mirror of
https://github.com/sasjs/lint.git
synced 2025-12-10 17:34:36 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8be59ac591 | ||
|
|
c6a70a1d1a | ||
| 75b103003c | |||
| 0cff87fe12 |
37
README.md
37
README.md
@@ -40,9 +40,19 @@ Configuration is via a `.sasjslint` file with the following structure (these are
|
||||
|
||||
### SAS Lint Settings
|
||||
|
||||
Each setting can have three states:
|
||||
|
||||
* OFF - usually by setting the value to `false` or 0. In this case, the rule won't be executed.
|
||||
* WARN - a warning is written to the log, but the return code will be 0
|
||||
* ERROR - an error is written to the log, and the return code is 1
|
||||
|
||||
For more details, and the default state, see the description of each rule below. It is also possible to change whether a rule returns ERROR or WARN using the `severityLevels` object.
|
||||
|
||||
#### defaultHeader
|
||||
|
||||
This sets the default program header - applies when a SAS program does NOT begin with `/**`. The default header is as follows:
|
||||
This isn't actually a rule - but rather a formatting setting, which applies to SAS program that do NOT begin with `/**`. It can be triggered by running `sasjs lint fix` in the SASjs CLI, or by hitting "save" when using the SASjs VS Code extension (with "formatOnSave" in place)
|
||||
|
||||
The default header is as follows:
|
||||
|
||||
```sas
|
||||
/**
|
||||
@@ -51,8 +61,7 @@ This sets the default program header - applies when a SAS program does NOT begin
|
||||
<h4> SAS Macros </h4>
|
||||
**/
|
||||
```
|
||||
|
||||
The default header is automatically applied when running `sasjs lint fix` in the SASjs CLI, or by hitting "save" when using the SASjs VS Code extension. If creating a new value, use `{lineEnding}` instead of `\n`, eg as follows:
|
||||
If creating a new value, use `{lineEnding}` instead of `\n`, eg as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -62,7 +71,7 @@ The default header is automatically applied when running `sasjs lint fix` in the
|
||||
|
||||
#### noEncodedPasswords
|
||||
|
||||
This will highlight any rows that contain a `{sas00X}` type password, or `{sasenc}`. These passwords (especially 001 and 002) are NOT secure, and should NEVER be pushed to source control or saved to the filesystem without special permissions applied.
|
||||
This rule will highlight any rows that contain a `{sas00X}` type password, or `{sasenc}`. These passwords (especially 001 and 002) are NOT secure, and should NEVER be pushed to source control or saved to the filesystem without special permissions applied.
|
||||
|
||||
* Default: true
|
||||
* Severity: ERROR
|
||||
@@ -139,6 +148,26 @@ This will highlight lines with trailing spaces. Trailing spaces serve no useful
|
||||
* 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:
|
||||
|
||||
```json
|
||||
{
|
||||
"noTrailingSpaces": true,
|
||||
"hasDoxygenHeader": true,
|
||||
"maxLineLength": 100,
|
||||
"severityLevel": {
|
||||
"hasDoxygenHeader": "warn",
|
||||
"maxLineLength": "error",
|
||||
"noTrailingSpaces": "error"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* "warn" - show warning in the log (doesn’t affect exit code)
|
||||
* "error" - show error in the log (exit code is 1 when triggered)
|
||||
|
||||
### Upcoming Linting Rules:
|
||||
|
||||
* `noTabs` -> does what it says on the tin
|
||||
|
||||
@@ -159,6 +159,115 @@
|
||||
"description": "An array of paths or path patterns to ignore when linting. Any files or matching patterns in the .gitignore file will also be ignored.",
|
||||
"default": ["sasjsbuild/", "sasjsresults/"],
|
||||
"examples": ["sasjs/tests", "tmp/scratch.sas"]
|
||||
},
|
||||
"severityLevel": {
|
||||
"$id": "#/properties/severityLevel",
|
||||
"type": "object",
|
||||
"title": "severityLevel",
|
||||
"description": "An object which specifies the severity level of each rule.",
|
||||
"default": {},
|
||||
"examples": [{
|
||||
"hasDoxygenHeader": "warn",
|
||||
"maxLineLength": "warn",
|
||||
"noTrailingSpaces": "error"
|
||||
}, {
|
||||
"hasDoxygenHeader": "warn",
|
||||
"maxLineLength": "error",
|
||||
"noTrailingSpaces": "error"
|
||||
}],
|
||||
"properties": {
|
||||
"noEncodedPasswords": {
|
||||
"$id": "#/properties/severityLevel/noEncodedPasswords",
|
||||
"title": "noEncodedPasswords",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "error"
|
||||
},
|
||||
"hasDoxygenHeader": {
|
||||
"$id": "#/properties/severityLevel/hasDoxygenHeader",
|
||||
"title": "hasDoxygenHeader",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn" },
|
||||
"hasMacroNameInMend": {
|
||||
"$id": "#/properties/severityLevel/hasMacroNameInMend",
|
||||
"title": "hasMacroNameInMend",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"hasMacroParentheses": {
|
||||
"$id": "#/properties/severityLevel/hasMacroParentheses",
|
||||
"title": "hasMacroParentheses",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"indentationMultiple": {
|
||||
"$id": "#/properties/severityLevel/indentationMultiple",
|
||||
"title": "indentationMultiple",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"lowerCaseFileNames": {
|
||||
"$id": "#/properties/severityLevel/lowerCaseFileNames",
|
||||
"title": "lowerCaseFileNames",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"maxLineLength": {
|
||||
"$id": "#/properties/severityLevel/maxLineLength",
|
||||
"title": "maxLineLength",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"noNestedMacros": {
|
||||
"$id": "#/properties/severityLevel/noNestedMacros",
|
||||
"title": "noNestedMacros",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"noSpacesInFileNames": {
|
||||
"$id": "#/properties/severityLevel/noSpacesInFileNames",
|
||||
"title": "noSpacesInFileNames",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"noTabIndentation": {
|
||||
"$id": "#/properties/severityLevel/noTabIndentation",
|
||||
"title": "noTabIndentation",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"noTrailingSpaces": {
|
||||
"$id": "#/properties/severityLevel/noTrailingSpaces",
|
||||
"title": "noTrailingSpaces",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"lineEndings": {
|
||||
"$id": "#/properties/severityLevel/lineEndings",
|
||||
"title": "lineEndings",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
},
|
||||
"strictMacroDefinition": {
|
||||
"$id": "#/properties/severityLevel/strictMacroDefinition",
|
||||
"title": "strictMacroDefinition",
|
||||
"type": "string",
|
||||
"enum": ["error", "warn"],
|
||||
"default": "warn"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -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 [
|
||||
{
|
||||
@@ -13,7 +16,7 @@ const test = (value: string, lineNumber: number) => {
|
||||
lineNumber,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
severity
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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({})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user