diff --git a/sasjslint-schema.json b/sasjslint-schema.json index 11734f2..debceb6 100644 --- a/sasjslint-schema.json +++ b/sasjslint-schema.json @@ -13,6 +13,7 @@ "indentationMultiple": 2, "lowerCaseFileNames": true, "maxLineLength": 80, + "maxHeaderLineLength": 80, "noGremlins": true, "noNestedMacros": true, "noSpacesInFileNames": true, @@ -30,6 +31,7 @@ "noSpacesInFileNames": true, "lowerCaseFileNames": true, "maxLineLength": 80, + "maxHeaderLineLength": 80, "noGremlins": true, "allowedGremlins": ["0x0080", "0x3000"], "noTabs": true, @@ -127,6 +129,14 @@ "default": 80, "examples": [60, 80, 120] }, + "maxHeaderLineLength": { + "$id": "#/properties/maxHeaderLineLength", + "type": "number", + "title": "maxLineLength", + "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": { "$id": "#/properties/noNestedMacros", "type": "boolean", diff --git a/src/lint/shared.ts b/src/lint/shared.ts index 556ba54..db82940 100644 --- a/src/lint/shared.ts +++ b/src/lint/shared.ts @@ -1,12 +1,16 @@ import { LintConfig, Diagnostic } from '../types' -import { splitText } from '../utils' +import { getHeaderLinesCount, splitText } from '../utils' export const processText = (text: string, config: LintConfig) => { const lines = splitText(text, config) + const headerLinesCount = getHeaderLinesCount(text, config) const diagnostics: Diagnostic[] = [] diagnostics.push(...processContent(config, text)) lines.forEach((line, index) => { - diagnostics.push(...processLine(config, line, index + 1)) + index += 1 + diagnostics.push( + ...processLine(config, line, index, index <= headerLinesCount) + ) }) return diagnostics @@ -36,11 +40,12 @@ const processContent = (config: LintConfig, content: string): Diagnostic[] => { export const processLine = ( config: LintConfig, line: string, - lineNumber: number + lineNumber: number, + isHeaderLine: boolean ): Diagnostic[] => { const diagnostics: Diagnostic[] = [] config.lineLintRules.forEach((rule) => { - diagnostics.push(...rule.test(line, lineNumber, config)) + diagnostics.push(...rule.test(line, lineNumber, config, isHeaderLine)) }) return diagnostics diff --git a/src/rules/line/maxLineLength.ts b/src/rules/line/maxLineLength.ts index a3d9d7d..0b5e97a 100644 --- a/src/rules/line/maxLineLength.ts +++ b/src/rules/line/maxLineLength.ts @@ -7,9 +7,19 @@ 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 test = ( + value: string, + lineNumber: number, + config?: LintConfig, + isHeaderLine?: boolean +) => { const severity = config?.severityLevel[name] || Severity.Warning - const maxLineLength = config?.maxLineLength || 80 + let maxLineLength = config?.maxLineLength || 80 + + if (isHeaderLine && config) { + maxLineLength = Math.max(config.maxLineLength, config.maxHeaderLineLength) + } + if (value.length <= maxLineLength) return [] return [ { diff --git a/src/types/LintConfig.spec.ts b/src/types/LintConfig.spec.ts index c30754f..a5d97d9 100644 --- a/src/types/LintConfig.spec.ts +++ b/src/types/LintConfig.spec.ts @@ -31,6 +31,17 @@ describe('LintConfig', () => { ).toBeUndefined() }) + it('should create an instance with the maxLineLength flag off', () => { + 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 hasDoxygenHeader flag off', () => { const config = new LintConfig({ hasDoxygenHeader: false }) diff --git a/src/types/LintConfig.ts b/src/types/LintConfig.ts index 850f37e..7a4e028 100644 --- a/src/types/LintConfig.ts +++ b/src/types/LintConfig.ts @@ -34,6 +34,7 @@ export class LintConfig { readonly fileLintRules: FileLintRule[] = [] readonly pathLintRules: PathLintRule[] = [] readonly maxLineLength: number = 80 + readonly maxHeaderLineLength: number = 80 readonly indentationMultiple: number = 2 readonly lineEndings: LineEndings = LineEndings.LF readonly defaultHeader: string = getDefaultHeader() @@ -67,9 +68,16 @@ export class LintConfig { this.lineLintRules.pop() } - this.lineLintRules.push(maxLineLength) - if (!isNaN(json?.maxLineLength)) { - this.maxLineLength = json.maxLineLength + if (json?.maxLineLength) { + this.lineLintRules.push(maxLineLength) + + if (!isNaN(json?.maxLineLength)) { + this.maxLineLength = json.maxLineLength + } + + if (!isNaN(json?.maxHeaderLineLength)) { + this.maxHeaderLineLength = json.maxHeaderLineLength + } } this.fileLintRules.push(lineEndings) diff --git a/src/types/LintRule.ts b/src/types/LintRule.ts index e2f7b90..27cd0a7 100644 --- a/src/types/LintRule.ts +++ b/src/types/LintRule.ts @@ -18,7 +18,12 @@ export interface LintRule { */ export interface LineLintRule extends LintRule { 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 } diff --git a/src/utils/getHeaderLinesCount.spec.ts b/src/utils/getHeaderLinesCount.spec.ts new file mode 100644 index 0000000..54a3fb6 --- /dev/null +++ b/src/utils/getHeaderLinesCount.spec.ts @@ -0,0 +1,21 @@ +import { LintConfig } from '../types' +import { getHeaderLinesCount } from './getHeaderLinesCount' +import { DefaultLintConfiguration } from './getLintConfig' + +const sasCodeWithHeader = `/** +@file +@brief +

SAS Macros

+**/ +%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) + }) +}) diff --git a/src/utils/getHeaderLinesCount.ts b/src/utils/getHeaderLinesCount.ts new file mode 100644 index 0000000..fcad8c3 --- /dev/null +++ b/src/utils/getHeaderLinesCount.ts @@ -0,0 +1,25 @@ +import { LintConfig } from '../types' +import { splitText } from './splitText' + +/** + * This funtion returns the number of lines header spans upon. + */ +export const getHeaderLinesCount = (text: string, config: LintConfig) => { + text = text.replace('/*', '/**') + text = text.replace('*/', '**/') + + let count = 0 + + if (text.trimStart().startsWith('/**')) { + const lines = splitText(text, config) + + for (const line of lines) { + count++ + if (line.match(/\*\*\//)) { + break + } + } + } + + return count +} diff --git a/src/utils/getLintConfig.ts b/src/utils/getLintConfig.ts index 8914dc3..f937d0b 100644 --- a/src/utils/getLintConfig.ts +++ b/src/utils/getLintConfig.ts @@ -16,6 +16,7 @@ export const DefaultLintConfiguration = { noSpacesInFileNames: true, lowerCaseFileNames: true, maxLineLength: 80, + maxHeaderLineLength: 80, noTabIndentation: true, indentationMultiple: 2, hasMacroNameInMend: true, diff --git a/src/utils/index.ts b/src/utils/index.ts index 958b2c7..3fcf5af 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,3 +6,4 @@ export * from './isIgnored' export * from './listSasFiles' export * from './splitText' export * from './getIndicesOf' +export * from './getHeaderLinesCount'