From de1fabc394b9fc9c81b3a3ccad9f60903f88d3a5 Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Wed, 24 Mar 2021 09:11:09 +0000 Subject: [PATCH] fix(*): Add severity, start and end column numbers for diagnostics, change warning to message --- src/lint.spec.ts | 25 +++++++++++++++++-------- src/rules/hasDoxygenHeader.spec.ts | 17 +++++++++++++++-- src/rules/hasDoxygenHeader.ts | 25 +++++++++++++++++++++---- src/rules/noEncodedPasswords.spec.ts | 25 +++++++++++++++++-------- src/rules/noEncodedPasswords.ts | 11 +++++++---- src/rules/noTrailingSpaces.spec.ts | 7 +++++-- src/rules/noTrailingSpaces.ts | 15 ++++++++++++--- src/types/Diagnostic.ts | 8 ++++++-- src/types/LintRule.ts | 4 ++-- src/types/Severity.ts | 8 ++++++++ src/utils/getLintConfig.spec.ts | 13 +++++++++++++ src/utils/getLintConfig.ts | 5 +++-- 12 files changed, 126 insertions(+), 37 deletions(-) create mode 100644 src/types/Severity.ts diff --git a/src/lint.spec.ts b/src/lint.spec.ts index 0c1e5b9..239184c 100644 --- a/src/lint.spec.ts +++ b/src/lint.spec.ts @@ -1,4 +1,5 @@ import { lint, splitText } from './lint' +import { Severity } from './types/Severity' describe('lint', () => { it('should identify trailing spaces', async () => { @@ -11,14 +12,18 @@ describe('lint', () => { expect(results.length).toEqual(2) expect(results[0]).toEqual({ - warning: 'Line contains trailing spaces', + message: 'Line contains trailing spaces', lineNumber: 4, - columnNumber: 18 + startColumnNumber: 18, + endColumnNumber: 18, + severity: Severity.Warning }) expect(results[1]).toEqual({ - warning: 'Line contains trailing spaces', + message: 'Line contains trailing spaces', lineNumber: 5, - columnNumber: 22 + startColumnNumber: 22, + endColumnNumber: 23, + severity: Severity.Warning }) }) @@ -31,9 +36,11 @@ describe('lint', () => { expect(results.length).toEqual(1) expect(results[0]).toEqual({ - warning: 'Line contains encoded password', + message: 'Line contains encoded password', lineNumber: 4, - columnNumber: 11 + startColumnNumber: 11, + endColumnNumber: 19, + severity: Severity.Error }) }) @@ -43,9 +50,11 @@ describe('lint', () => { expect(results.length).toEqual(1) expect(results[0]).toEqual({ - warning: 'File missing Doxygen header', + message: 'File missing Doxygen header', lineNumber: 1, - columnNumber: 1 + startColumnNumber: 1, + endColumnNumber: 1, + severity: Severity.Warning }) }) diff --git a/src/rules/hasDoxygenHeader.spec.ts b/src/rules/hasDoxygenHeader.spec.ts index 1dc017d..477969d 100644 --- a/src/rules/hasDoxygenHeader.spec.ts +++ b/src/rules/hasDoxygenHeader.spec.ts @@ -1,3 +1,4 @@ +import { Severity } from '../types/Severity' import { hasDoxygenHeader } from './hasDoxygenHeader' describe('hasDoxygenHeader', () => { @@ -23,7 +24,13 @@ describe('hasDoxygenHeader', () => { %do x=0 %to &maxtries;` expect(hasDoxygenHeader.test(content)).toEqual([ - { warning: 'File missing Doxygen header', lineNumber: 1, columnNumber: 1 } + { + message: 'File missing Doxygen header', + lineNumber: 1, + startColumnNumber: 1, + endColumnNumber: 1, + severity: Severity.Warning + } ]) }) @@ -31,7 +38,13 @@ describe('hasDoxygenHeader', () => { const content = undefined expect(hasDoxygenHeader.test((content as unknown) as string)).toEqual([ - { warning: 'File missing Doxygen header', lineNumber: 1, columnNumber: 1 } + { + message: 'File missing Doxygen header', + lineNumber: 1, + startColumnNumber: 1, + endColumnNumber: 1, + severity: Severity.Warning + } ]) }) }) diff --git a/src/rules/hasDoxygenHeader.ts b/src/rules/hasDoxygenHeader.ts index 18feb5d..3563154 100644 --- a/src/rules/hasDoxygenHeader.ts +++ b/src/rules/hasDoxygenHeader.ts @@ -1,17 +1,34 @@ import { FileLintRule } from '../types/LintRule' import { LintRuleType } from '../types/LintRuleType' +import { Severity } from '../types/Severity' const name = 'hasDoxygenHeader' const description = 'Enforce the presence of a Doxygen header at the start of each file.' -const warning = 'File missing Doxygen header' +const message = 'File missing Doxygen header' const test = (value: string) => { try { const hasFileHeader = value.split('/**')[0] !== value if (hasFileHeader) return [] - return [{ warning, lineNumber: 1, columnNumber: 1 }] + return [ + { + message, + lineNumber: 1, + startColumnNumber: 1, + endColumnNumber: 1, + severity: Severity.Warning + } + ] } catch (e) { - return [{ warning, lineNumber: 1, columnNumber: 1 }] + return [ + { + message, + lineNumber: 1, + startColumnNumber: 1, + endColumnNumber: 1, + severity: Severity.Warning + } + ] } } @@ -22,6 +39,6 @@ export const hasDoxygenHeader: FileLintRule = { type: LintRuleType.File, name, description, - warning, + message, test } diff --git a/src/rules/noEncodedPasswords.spec.ts b/src/rules/noEncodedPasswords.spec.ts index f68c9f1..bd7e3d9 100644 --- a/src/rules/noEncodedPasswords.spec.ts +++ b/src/rules/noEncodedPasswords.spec.ts @@ -1,3 +1,4 @@ +import { Severity } from '../types/Severity' import { noEncodedPasswords } from './noEncodedPasswords' describe('noEncodedPasswords', () => { @@ -10,9 +11,11 @@ describe('noEncodedPasswords', () => { const line = "%put '{SASENC}'; " expect(noEncodedPasswords.test(line, 1)).toEqual([ { - warning: 'Line contains encoded password', + message: 'Line contains encoded password', lineNumber: 1, - columnNumber: 7 + startColumnNumber: 7, + endColumnNumber: 15, + severity: Severity.Error } ]) }) @@ -21,9 +24,11 @@ describe('noEncodedPasswords', () => { const line = "%put '{SAS001}'; " expect(noEncodedPasswords.test(line, 1)).toEqual([ { - warning: 'Line contains encoded password', + message: 'Line contains encoded password', lineNumber: 1, - columnNumber: 7 + startColumnNumber: 7, + endColumnNumber: 15, + severity: Severity.Error } ]) }) @@ -32,14 +37,18 @@ describe('noEncodedPasswords', () => { const line = "%put '{SAS001} {SAS002}'; " expect(noEncodedPasswords.test(line, 1)).toEqual([ { - warning: 'Line contains encoded password', + message: 'Line contains encoded password', lineNumber: 1, - columnNumber: 7 + startColumnNumber: 7, + endColumnNumber: 15, + severity: Severity.Error }, { - warning: 'Line contains encoded password', + message: 'Line contains encoded password', lineNumber: 1, - columnNumber: 16 + startColumnNumber: 16, + endColumnNumber: 24, + severity: Severity.Error } ]) }) diff --git a/src/rules/noEncodedPasswords.ts b/src/rules/noEncodedPasswords.ts index 2d1a4f8..2cbf600 100644 --- a/src/rules/noEncodedPasswords.ts +++ b/src/rules/noEncodedPasswords.ts @@ -1,17 +1,20 @@ import { LineLintRule } from '../types/LintRule' import { LintRuleType } from '../types/LintRuleType' +import { Severity } from '../types/Severity' const name = 'noEncodedPasswords' const description = 'Disallow encoded passwords in SAS code.' -const warning = 'Line contains encoded password' +const message = 'Line contains encoded password' const test = (value: string, lineNumber: number) => { const regex = new RegExp(/{sas(\d{2,4}|enc)}[^;"'\s]*/, 'gi') const matches = value.match(regex) if (!matches || !matches.length) return [] return matches.map((match) => ({ - warning, + message, lineNumber, - columnNumber: value.indexOf(match) + 1 + startColumnNumber: value.indexOf(match) + 1, + endColumnNumber: value.indexOf(match) + match.length + 1, + severity: Severity.Error })) } @@ -22,6 +25,6 @@ export const noEncodedPasswords: LineLintRule = { type: LintRuleType.Line, name, description, - warning, + message, test } diff --git a/src/rules/noTrailingSpaces.spec.ts b/src/rules/noTrailingSpaces.spec.ts index f0c69e6..76504f3 100644 --- a/src/rules/noTrailingSpaces.spec.ts +++ b/src/rules/noTrailingSpaces.spec.ts @@ -1,3 +1,4 @@ +import { Severity } from '../types/Severity' import { noTrailingSpaces } from './noTrailingSpaces' describe('noTrailingSpaces', () => { @@ -10,9 +11,11 @@ describe('noTrailingSpaces', () => { const line = "%put 'hello'; " expect(noTrailingSpaces.test(line, 1)).toEqual([ { - warning: 'Line contains trailing spaces', + message: 'Line contains trailing spaces', lineNumber: 1, - columnNumber: 14 + startColumnNumber: 14, + endColumnNumber: 15, + severity: Severity.Warning } ]) }) diff --git a/src/rules/noTrailingSpaces.ts b/src/rules/noTrailingSpaces.ts index 5fae710..f5ef0d7 100644 --- a/src/rules/noTrailingSpaces.ts +++ b/src/rules/noTrailingSpaces.ts @@ -1,13 +1,22 @@ import { LineLintRule } from '../types/LintRule' import { LintRuleType } from '../types/LintRuleType' +import { Severity } from '../types/Severity' const name = 'noTrailingSpaces' const description = 'Disallow trailing spaces on lines.' -const warning = 'Line contains trailing spaces' +const message = 'Line contains trailing spaces' const test = (value: string, lineNumber: number) => value.trimEnd() === value ? [] - : [{ warning, lineNumber, columnNumber: value.trimEnd().length + 1 }] + : [ + { + message, + lineNumber, + startColumnNumber: value.trimEnd().length + 1, + endColumnNumber: value.length, + severity: Severity.Warning + } + ] /** * Lint rule that checks for the presence of trailing space(s) in a given line of text. @@ -16,6 +25,6 @@ export const noTrailingSpaces: LineLintRule = { type: LintRuleType.Line, name, description, - warning, + message, test } diff --git a/src/types/Diagnostic.ts b/src/types/Diagnostic.ts index c034a9d..e7fb5db 100644 --- a/src/types/Diagnostic.ts +++ b/src/types/Diagnostic.ts @@ -1,8 +1,12 @@ +import { Severity } from './Severity' + /** * A diagnostic is produced by the execution of a lint rule against a file or line of text. */ export interface Diagnostic { lineNumber: number - columnNumber: number - warning: string + startColumnNumber: number + endColumnNumber: number + message: string + severity: Severity } diff --git a/src/types/LintRule.ts b/src/types/LintRule.ts index 96fd498..488fd0f 100644 --- a/src/types/LintRule.ts +++ b/src/types/LintRule.ts @@ -2,14 +2,14 @@ import { Diagnostic } from './Diagnostic' import { LintRuleType } from './LintRuleType' /** - * A lint rule is defined by a type, name, description, warning text and a test function. + * A lint rule is defined by a type, name, description, message text and a test function. * The test function produces a set of diagnostics when executed. */ export interface LintRule { type: LintRuleType name: string description: string - warning: string + message: string test: (value: string, lineNumber: number) => Diagnostic[] } diff --git a/src/types/Severity.ts b/src/types/Severity.ts new file mode 100644 index 0000000..fd72bd1 --- /dev/null +++ b/src/types/Severity.ts @@ -0,0 +1,8 @@ +/** + * Severity indicates the seriousness of a given violation. + */ +export enum Severity { + Info, + Warning, + Error +} diff --git a/src/utils/getLintConfig.spec.ts b/src/utils/getLintConfig.spec.ts index 88d8c9a..d5136d4 100644 --- a/src/utils/getLintConfig.spec.ts +++ b/src/utils/getLintConfig.spec.ts @@ -1,3 +1,4 @@ +import * as fileModule from '@sasjs/utils/file' import { LintConfig } from '../types/LintConfig' import { getLintConfig } from './getLintConfig' @@ -7,4 +8,16 @@ describe('getLintConfig', () => { expect(config).toBeInstanceOf(LintConfig) }) + + it('should get the default config when a .sasjslint file is unavailable', async () => { + jest + .spyOn(fileModule, 'readFile') + .mockImplementationOnce(() => Promise.reject()) + + const config = await getLintConfig() + + expect(config).toBeInstanceOf(LintConfig) + expect(config.fileLintRules.length).toEqual(1) + expect(config.lineLintRules.length).toEqual(2) + }) }) diff --git a/src/utils/getLintConfig.ts b/src/utils/getLintConfig.ts index 06cf114..97178cf 100644 --- a/src/utils/getLintConfig.ts +++ b/src/utils/getLintConfig.ts @@ -10,14 +10,15 @@ const defaultConfiguration = { } /** * Fetches the config from the .sasjslint file and creates a LintConfig object. + * Returns the default configuration when a .sasjslint file is unavailable. * @returns {Promise} resolves with an object representing the current lint configuration. */ export async function getLintConfig(): Promise { const projectRoot = await getProjectRoot() const configuration = await readFile( path.join(projectRoot, '.sasjslint') - ).catch((e) => { - console.error('Error reading .sasjslint file', e) + ).catch((_) => { + console.warn('Unable to load .sasjslint file. Using default configuration.') return JSON.stringify(defaultConfiguration) }) return new LintConfig(JSON.parse(configuration))