mirror of
https://github.com/sasjs/lint.git
synced 2026-01-07 12:40:05 +00:00
Merge pull request #199 from sasjs/issue-45
feat: add a new config maxHeaderLineLength
This commit is contained in:
19
README.md
19
README.md
@@ -26,6 +26,7 @@ Configuration is via a `.sasjslint` file with the following structure (these are
|
|||||||
"ignoreList": ["sajsbuild/", "sasjsresults/"],
|
"ignoreList": ["sajsbuild/", "sasjsresults/"],
|
||||||
"indentationMultiple": 2,
|
"indentationMultiple": 2,
|
||||||
"lowerCaseFileNames": true,
|
"lowerCaseFileNames": true,
|
||||||
|
"maxHeaderLineLength": 80,
|
||||||
"maxLineLength": 80,
|
"maxLineLength": 80,
|
||||||
"noNestedMacros": true,
|
"noNestedMacros": true,
|
||||||
"noGremlins": true,
|
"noGremlins": true,
|
||||||
@@ -127,6 +128,20 @@ On *nix systems, it is imperative that autocall macros are in lowercase. When sh
|
|||||||
- Default: true
|
- Default: true
|
||||||
- Severity: WARNING
|
- Severity: WARNING
|
||||||
|
|
||||||
|
### maxHeaderLineLength
|
||||||
|
|
||||||
|
In a program header it can be necessary to insert items such as URLs or markdown tables, that cannot be split over multiple lines. To avoid the need to increase `maxLineLength` for the entire project, it is possible to raise the line length limit for the header section only.
|
||||||
|
|
||||||
|
The `maxHeaderLineLength` setting is always the _higher_ of `maxHeaderLineLength` and `maxLineLength` (if you set a lower number, it is ignored).
|
||||||
|
|
||||||
|
- Default: 80
|
||||||
|
- Severity: WARNING
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [hasDoxygenHeader](#hasdoxygenheader)
|
||||||
|
* [maxLineLength](#maxlinelength)
|
||||||
|
|
||||||
### maxLineLength
|
### maxLineLength
|
||||||
|
|
||||||
Code becomes far more readable when line lengths are short. The most compelling reason for short line lengths is to avoid the need to scroll when performing a side-by-side 'compare' between two files (eg as part of a GIT feature branch review). A longer discussion on optimal code line length can be found [here](https://stackoverflow.com/questions/578059/studies-on-optimal-code-width)
|
Code becomes far more readable when line lengths are short. The most compelling reason for short line lengths is to avoid the need to scroll when performing a side-by-side 'compare' between two files (eg as part of a GIT feature branch review). A longer discussion on optimal code line length can be found [here](https://stackoverflow.com/questions/578059/studies-on-optimal-code-width)
|
||||||
@@ -138,6 +153,10 @@ We strongly recommend a line length limit, and set the bar at 80. To turn this f
|
|||||||
- Default: 80
|
- Default: 80
|
||||||
- Severity: WARNING
|
- Severity: WARNING
|
||||||
|
|
||||||
|
See also:
|
||||||
|
|
||||||
|
* [maxHeaderLineLength](#maxheaderlinelength)
|
||||||
|
|
||||||
### noGremlins
|
### noGremlins
|
||||||
|
|
||||||
Capture zero-width whitespace and other non-standard characters. The logic is borrowed from the [VSCode Gremlins Extension](https://github.com/nhoizey/vscode-gremlins) - if you are looking for more advanced gremlin zapping capabilities, we highly recommend to use their extension instead.
|
Capture zero-width whitespace and other non-standard characters. The logic is borrowed from the [VSCode Gremlins Extension](https://github.com/nhoizey/vscode-gremlins) - if you are looking for more advanced gremlin zapping capabilities, we highly recommend to use their extension instead.
|
||||||
|
|||||||
10876
package-lock.json
generated
10876
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -39,16 +39,16 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/sasjs/lint#readme",
|
"homepage": "https://github.com/sasjs/lint#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "29.2.5",
|
||||||
"@types/node": "^15.12.2",
|
"@types/node": "18.11.18",
|
||||||
"all-contributors-cli": "^6.20.0",
|
"all-contributors-cli": "6.24.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "29.3.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"ts-jest": "^26.5.6",
|
"ts-jest": "29.0.3",
|
||||||
"typescript": "^4.3.2"
|
"typescript": "^4.3.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/utils": "^2.19.0",
|
"@sasjs/utils": "2.52.0",
|
||||||
"ignore": "^5.2.0"
|
"ignore": "5.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"indentationMultiple": 2,
|
"indentationMultiple": 2,
|
||||||
"lowerCaseFileNames": true,
|
"lowerCaseFileNames": true,
|
||||||
"maxLineLength": 80,
|
"maxLineLength": 80,
|
||||||
|
"maxHeaderLineLength": 80,
|
||||||
"noGremlins": true,
|
"noGremlins": true,
|
||||||
"noNestedMacros": true,
|
"noNestedMacros": true,
|
||||||
"noSpacesInFileNames": true,
|
"noSpacesInFileNames": true,
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
"noSpacesInFileNames": true,
|
"noSpacesInFileNames": true,
|
||||||
"lowerCaseFileNames": true,
|
"lowerCaseFileNames": true,
|
||||||
"maxLineLength": 80,
|
"maxLineLength": 80,
|
||||||
|
"maxHeaderLineLength": 80,
|
||||||
"noGremlins": true,
|
"noGremlins": true,
|
||||||
"allowedGremlins": ["0x0080", "0x3000"],
|
"allowedGremlins": ["0x0080", "0x3000"],
|
||||||
"noTabs": true,
|
"noTabs": true,
|
||||||
@@ -127,6 +129,14 @@
|
|||||||
"default": 80,
|
"default": 80,
|
||||||
"examples": [60, 80, 120]
|
"examples": [60, 80, 120]
|
||||||
},
|
},
|
||||||
|
"maxHeaderLineLength": {
|
||||||
|
"$id": "#/properties/maxHeaderLineLength",
|
||||||
|
"type": "number",
|
||||||
|
"title": "maxHeaderLineLength",
|
||||||
|
"description": "Enforces a configurable maximum line length for header section. Shows a warning for lines exceeding this length.",
|
||||||
|
"default": 80,
|
||||||
|
"examples": [60, 80, 120]
|
||||||
|
},
|
||||||
"noNestedMacros": {
|
"noNestedMacros": {
|
||||||
"$id": "#/properties/noNestedMacros",
|
"$id": "#/properties/noNestedMacros",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { LintConfig, Diagnostic } from '../types'
|
import { LintConfig, Diagnostic } from '../types'
|
||||||
import { splitText } from '../utils'
|
import { getHeaderLinesCount, splitText } from '../utils'
|
||||||
|
|
||||||
export const processText = (text: string, config: LintConfig) => {
|
export const processText = (text: string, config: LintConfig) => {
|
||||||
const lines = splitText(text, config)
|
const lines = splitText(text, config)
|
||||||
|
const headerLinesCount = getHeaderLinesCount(text, config)
|
||||||
const diagnostics: Diagnostic[] = []
|
const diagnostics: Diagnostic[] = []
|
||||||
diagnostics.push(...processContent(config, text))
|
diagnostics.push(...processContent(config, text))
|
||||||
lines.forEach((line, index) => {
|
lines.forEach((line, index) => {
|
||||||
diagnostics.push(...processLine(config, line, index + 1))
|
index += 1
|
||||||
|
diagnostics.push(
|
||||||
|
...processLine(config, line, index, index <= headerLinesCount)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return diagnostics
|
return diagnostics
|
||||||
@@ -36,11 +40,12 @@ const processContent = (config: LintConfig, content: string): Diagnostic[] => {
|
|||||||
export const processLine = (
|
export const processLine = (
|
||||||
config: LintConfig,
|
config: LintConfig,
|
||||||
line: string,
|
line: string,
|
||||||
lineNumber: number
|
lineNumber: number,
|
||||||
|
isHeaderLine: boolean
|
||||||
): Diagnostic[] => {
|
): Diagnostic[] => {
|
||||||
const diagnostics: Diagnostic[] = []
|
const diagnostics: Diagnostic[] = []
|
||||||
config.lineLintRules.forEach((rule) => {
|
config.lineLintRules.forEach((rule) => {
|
||||||
diagnostics.push(...rule.test(line, lineNumber, config))
|
diagnostics.push(...rule.test(line, lineNumber, config, isHeaderLine))
|
||||||
})
|
})
|
||||||
|
|
||||||
return diagnostics
|
return diagnostics
|
||||||
|
|||||||
@@ -2,14 +2,27 @@ import { LintConfig } from '../../types'
|
|||||||
import { LineLintRule } from '../../types/LintRule'
|
import { LineLintRule } from '../../types/LintRule'
|
||||||
import { LintRuleType } from '../../types/LintRuleType'
|
import { LintRuleType } from '../../types/LintRuleType'
|
||||||
import { Severity } from '../../types/Severity'
|
import { Severity } from '../../types/Severity'
|
||||||
|
import { DefaultLintConfiguration } from '../../utils'
|
||||||
|
|
||||||
const name = 'maxLineLength'
|
const name = 'maxLineLength'
|
||||||
const description = 'Restrict lines to the specified length.'
|
const description = 'Restrict lines to the specified length.'
|
||||||
const message = 'Line exceeds maximum length'
|
const message = 'Line exceeds maximum length'
|
||||||
|
|
||||||
const test = (value: string, lineNumber: number, config?: LintConfig) => {
|
const test = (
|
||||||
|
value: string,
|
||||||
|
lineNumber: number,
|
||||||
|
config?: LintConfig,
|
||||||
|
isHeaderLine?: boolean
|
||||||
|
) => {
|
||||||
const severity = config?.severityLevel[name] || Severity.Warning
|
const severity = config?.severityLevel[name] || Severity.Warning
|
||||||
const maxLineLength = config?.maxLineLength || 80
|
let maxLineLength = config
|
||||||
|
? config.maxLineLength
|
||||||
|
: DefaultLintConfiguration.maxLineLength
|
||||||
|
|
||||||
|
if (isHeaderLine && config) {
|
||||||
|
maxLineLength = Math.max(config.maxLineLength, config.maxHeaderLineLength)
|
||||||
|
}
|
||||||
|
|
||||||
if (value.length <= maxLineLength) return []
|
if (value.length <= maxLineLength) return []
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,6 +31,28 @@ describe('LintConfig', () => {
|
|||||||
).toBeUndefined()
|
).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should create an instance with the maxLineLength flag off by setting value to 0', () => {
|
||||||
|
const config = new LintConfig({ maxLineLength: 0 })
|
||||||
|
|
||||||
|
expect(config).toBeTruthy()
|
||||||
|
expect(config.lineLintRules.length).toBeGreaterThan(0)
|
||||||
|
expect(config.fileLintRules.length).toBeGreaterThan(0)
|
||||||
|
expect(
|
||||||
|
config.lineLintRules.find((rule) => rule.name === 'maxLineLength')
|
||||||
|
).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create an instance with the maxLineLength flag off by setting value to a negative number', () => {
|
||||||
|
const config = new LintConfig({ maxLineLength: -1 })
|
||||||
|
|
||||||
|
expect(config).toBeTruthy()
|
||||||
|
expect(config.lineLintRules.length).toBeGreaterThan(0)
|
||||||
|
expect(config.fileLintRules.length).toBeGreaterThan(0)
|
||||||
|
expect(
|
||||||
|
config.lineLintRules.find((rule) => rule.name === 'maxLineLength')
|
||||||
|
).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('should create an instance with the hasDoxygenHeader flag off', () => {
|
it('should create an instance with the hasDoxygenHeader flag off', () => {
|
||||||
const config = new LintConfig({ hasDoxygenHeader: false })
|
const config = new LintConfig({ hasDoxygenHeader: false })
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export class LintConfig {
|
|||||||
readonly fileLintRules: FileLintRule[] = []
|
readonly fileLintRules: FileLintRule[] = []
|
||||||
readonly pathLintRules: PathLintRule[] = []
|
readonly pathLintRules: PathLintRule[] = []
|
||||||
readonly maxLineLength: number = 80
|
readonly maxLineLength: number = 80
|
||||||
|
readonly maxHeaderLineLength: number = 80
|
||||||
readonly indentationMultiple: number = 2
|
readonly indentationMultiple: number = 2
|
||||||
readonly lineEndings: LineEndings = LineEndings.LF
|
readonly lineEndings: LineEndings = LineEndings.LF
|
||||||
readonly defaultHeader: string = getDefaultHeader()
|
readonly defaultHeader: string = getDefaultHeader()
|
||||||
@@ -67,9 +68,13 @@ export class LintConfig {
|
|||||||
this.lineLintRules.pop()
|
this.lineLintRules.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (json?.maxLineLength > 0) {
|
||||||
this.lineLintRules.push(maxLineLength)
|
this.lineLintRules.push(maxLineLength)
|
||||||
if (!isNaN(json?.maxLineLength)) {
|
|
||||||
this.maxLineLength = json.maxLineLength
|
this.maxLineLength = json.maxLineLength
|
||||||
|
|
||||||
|
if (!isNaN(json?.maxHeaderLineLength)) {
|
||||||
|
this.maxHeaderLineLength = json.maxHeaderLineLength
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fileLintRules.push(lineEndings)
|
this.fileLintRules.push(lineEndings)
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ export interface LintRule {
|
|||||||
*/
|
*/
|
||||||
export interface LineLintRule extends LintRule {
|
export interface LineLintRule extends LintRule {
|
||||||
type: LintRuleType.Line
|
type: LintRuleType.Line
|
||||||
test: (value: string, lineNumber: number, config?: LintConfig) => Diagnostic[]
|
test: (
|
||||||
|
value: string,
|
||||||
|
lineNumber: number,
|
||||||
|
config?: LintConfig,
|
||||||
|
isHeaderLine?: boolean
|
||||||
|
) => Diagnostic[]
|
||||||
fix?: (value: string, config?: LintConfig) => string
|
fix?: (value: string, config?: LintConfig) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
src/utils/getHeaderLinesCount.spec.ts
Normal file
21
src/utils/getHeaderLinesCount.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { LintConfig } from '../types'
|
||||||
|
import { getHeaderLinesCount } from './getHeaderLinesCount'
|
||||||
|
import { DefaultLintConfiguration } from './getLintConfig'
|
||||||
|
|
||||||
|
const sasCodeWithHeader = `/**
|
||||||
|
@file
|
||||||
|
@brief <Your brief here>
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
**/
|
||||||
|
%put hello world;
|
||||||
|
`
|
||||||
|
|
||||||
|
const sasCodeWithoutHeader = `%put hello world;`
|
||||||
|
|
||||||
|
describe('getHeaderLinesCount', () => {
|
||||||
|
it('should return the number of line header spans upon', () => {
|
||||||
|
const config = new LintConfig(DefaultLintConfiguration)
|
||||||
|
expect(getHeaderLinesCount(sasCodeWithHeader, config)).toEqual(5)
|
||||||
|
expect(getHeaderLinesCount(sasCodeWithoutHeader, config)).toEqual(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
23
src/utils/getHeaderLinesCount.ts
Normal file
23
src/utils/getHeaderLinesCount.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { LintConfig } from '../types'
|
||||||
|
import { splitText } from './splitText'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function returns the number of lines the header spans upon.
|
||||||
|
* The file must start with "/*" and the header will finish with ⇙
|
||||||
|
*/
|
||||||
|
export const getHeaderLinesCount = (text: string, config: LintConfig) => {
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
if (text.trimStart().startsWith('/*')) {
|
||||||
|
const lines = splitText(text, config)
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
count++
|
||||||
|
if (line.match(/\*\//)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ export const DefaultLintConfiguration = {
|
|||||||
noSpacesInFileNames: true,
|
noSpacesInFileNames: true,
|
||||||
lowerCaseFileNames: true,
|
lowerCaseFileNames: true,
|
||||||
maxLineLength: 80,
|
maxLineLength: 80,
|
||||||
|
maxHeaderLineLength: 80,
|
||||||
noTabIndentation: true,
|
noTabIndentation: true,
|
||||||
indentationMultiple: 2,
|
indentationMultiple: 2,
|
||||||
hasMacroNameInMend: true,
|
hasMacroNameInMend: true,
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ export * from './isIgnored'
|
|||||||
export * from './listSasFiles'
|
export * from './listSasFiles'
|
||||||
export * from './splitText'
|
export * from './splitText'
|
||||||
export * from './getIndicesOf'
|
export * from './getIndicesOf'
|
||||||
|
export * from './getHeaderLinesCount'
|
||||||
|
|||||||
Reference in New Issue
Block a user