mirror of
https://github.com/sasjs/lint.git
synced 2026-01-07 12:40:05 +00:00
feat(lint): add rule for indentation multiple
This commit is contained in:
@@ -5,5 +5,6 @@
|
|||||||
"noSpacesInFileNames": true,
|
"noSpacesInFileNames": true,
|
||||||
"maxLineLength": 80,
|
"maxLineLength": 80,
|
||||||
"lowerCaseFileNames": true,
|
"lowerCaseFileNames": true,
|
||||||
"noTabIndentation": true
|
"noTabIndentation": true,
|
||||||
|
"indentationMultiple": 2
|
||||||
}
|
}
|
||||||
18
src/Example File.sas
Normal file
18
src/Example File.sas
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
|
||||||
|
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||||
|
%local x libref;
|
||||||
|
%let x={SAS002};
|
||||||
|
%do x=0 %to &maxtries;
|
||||||
|
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
|
||||||
|
%let libref=&prefix&x;
|
||||||
|
%let rc=%sysfunc(libname(&libref,%sysfunc(pathname(work))));
|
||||||
|
%if &rc %then %put %sysfunc(sysmsg());
|
||||||
|
&prefix&x
|
||||||
|
%*put &sysmacroname: Libref &libref assigned as WORK and returned;
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
|
%put unable to find available libref in range &prefix.0-&maxtries;
|
||||||
|
%mend;
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
|
||||||
%local x libref;
|
|
||||||
%let x={SAS002};
|
|
||||||
%do x=0 %to &maxtries;
|
|
||||||
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
|
|
||||||
%let libref=&prefix&x;
|
|
||||||
%let rc=%sysfunc(libname(&libref,%sysfunc(pathname(work))));
|
|
||||||
%if &rc %then %put %sysfunc(sysmsg());
|
|
||||||
&prefix&x
|
|
||||||
%*put &sysmacroname: Libref &libref assigned as WORK and returned;
|
|
||||||
%return;
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
%put unable to find available libref in range &prefix.0-&maxtries;
|
|
||||||
%mend;
|
|
||||||
@@ -45,7 +45,6 @@ const text = `/**
|
|||||||
%end;
|
%end;
|
||||||
%put unable to find available libref in range &prefix.0-&maxtries;
|
%put unable to find available libref in range &prefix.0-&maxtries;
|
||||||
%mend;
|
%mend;
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
lintText(text).then((diagnostics) => {
|
lintText(text).then((diagnostics) => {
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ describe('lintText', () => {
|
|||||||
|
|
||||||
describe('lintFile', () => {
|
describe('lintFile', () => {
|
||||||
it('should identify lint issues in a given file', async () => {
|
it('should identify lint issues in a given file', async () => {
|
||||||
const results = await lintFile(path.join(__dirname, 'example file.sas'))
|
const results = await lintFile(path.join(__dirname, 'Example File.sas'))
|
||||||
|
|
||||||
expect(results.length).toEqual(5)
|
expect(results.length).toEqual(8)
|
||||||
expect(results).toContainEqual({
|
expect(results).toContainEqual({
|
||||||
message: 'Line contains trailing spaces',
|
message: 'Line contains trailing spaces',
|
||||||
lineNumber: 1,
|
lineNumber: 1,
|
||||||
@@ -95,6 +95,13 @@ describe('lintFile', () => {
|
|||||||
endColumnNumber: 1,
|
endColumnNumber: 1,
|
||||||
severity: Severity.Warning
|
severity: Severity.Warning
|
||||||
})
|
})
|
||||||
|
expect(results).toContainEqual({
|
||||||
|
message: 'File name contains uppercase characters',
|
||||||
|
lineNumber: 1,
|
||||||
|
startColumnNumber: 1,
|
||||||
|
endColumnNumber: 1,
|
||||||
|
severity: Severity.Warning
|
||||||
|
})
|
||||||
expect(results).toContainEqual({
|
expect(results).toContainEqual({
|
||||||
message: 'File missing Doxygen header',
|
message: 'File missing Doxygen header',
|
||||||
lineNumber: 1,
|
lineNumber: 1,
|
||||||
@@ -105,10 +112,24 @@ describe('lintFile', () => {
|
|||||||
expect(results).toContainEqual({
|
expect(results).toContainEqual({
|
||||||
message: 'Line contains encoded password',
|
message: 'Line contains encoded password',
|
||||||
lineNumber: 5,
|
lineNumber: 5,
|
||||||
startColumnNumber: 11,
|
startColumnNumber: 10,
|
||||||
endColumnNumber: 19,
|
endColumnNumber: 18,
|
||||||
severity: Severity.Error
|
severity: Severity.Error
|
||||||
})
|
})
|
||||||
|
expect(results).toContainEqual({
|
||||||
|
message: 'Line is indented with a tab',
|
||||||
|
lineNumber: 7,
|
||||||
|
startColumnNumber: 1,
|
||||||
|
endColumnNumber: 1,
|
||||||
|
severity: Severity.Warning
|
||||||
|
})
|
||||||
|
expect(results).toContainEqual({
|
||||||
|
message: 'Line has incorrect indentation - 3 spaces',
|
||||||
|
lineNumber: 6,
|
||||||
|
startColumnNumber: 1,
|
||||||
|
endColumnNumber: 1,
|
||||||
|
severity: Severity.Warning
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
68
src/rules/indentationMultiple.spec.ts
Normal file
68
src/rules/indentationMultiple.spec.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { LintConfig, Severity } from '../types'
|
||||||
|
import { indentationMultiple } from './indentationMultiple'
|
||||||
|
|
||||||
|
describe('indentationMultiple', () => {
|
||||||
|
it('should return an empty array when the line is indented by two spaces', () => {
|
||||||
|
const line = " %put 'hello';"
|
||||||
|
const config = new LintConfig({ indentationMultiple: 2 })
|
||||||
|
expect(indentationMultiple.test(line, 1, config)).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an empty array when the line is indented by a multiple of 2 spaces', () => {
|
||||||
|
const line = " %put 'hello';"
|
||||||
|
const config = new LintConfig({ indentationMultiple: 2 })
|
||||||
|
expect(indentationMultiple.test(line, 1, config)).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an empty array when the line is not indented', () => {
|
||||||
|
const line = "%put 'hello';"
|
||||||
|
const config = new LintConfig({ indentationMultiple: 2 })
|
||||||
|
expect(indentationMultiple.test(line, 1, config)).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single diagnostic when the line is indented incorrectly', () => {
|
||||||
|
const line = " %put 'hello';"
|
||||||
|
const config = new LintConfig({ indentationMultiple: 2 })
|
||||||
|
expect(indentationMultiple.test(line, 1, config)).toEqual([
|
||||||
|
{
|
||||||
|
message: `Line has incorrect indentation - 3 spaces`,
|
||||||
|
lineNumber: 1,
|
||||||
|
startColumnNumber: 1,
|
||||||
|
endColumnNumber: 1,
|
||||||
|
severity: Severity.Warning
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an array with a single diagnostic when the line is indented incorrectly', () => {
|
||||||
|
const line = " %put 'hello';"
|
||||||
|
const config = new LintConfig({ indentationMultiple: 3 })
|
||||||
|
expect(indentationMultiple.test(line, 1, config)).toEqual([
|
||||||
|
{
|
||||||
|
message: `Line has incorrect indentation - 2 spaces`,
|
||||||
|
lineNumber: 1,
|
||||||
|
startColumnNumber: 1,
|
||||||
|
endColumnNumber: 1,
|
||||||
|
severity: Severity.Warning
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fall back to a default of 2 spaces', () => {
|
||||||
|
const line = " %put 'hello';"
|
||||||
|
expect(indentationMultiple.test(line, 1)).toEqual([
|
||||||
|
{
|
||||||
|
message: `Line has incorrect indentation - 1 space`,
|
||||||
|
lineNumber: 1,
|
||||||
|
startColumnNumber: 1,
|
||||||
|
endColumnNumber: 1,
|
||||||
|
severity: Severity.Warning
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return an empty array for lines within the default indentation', () => {
|
||||||
|
const line = " %put 'hello';"
|
||||||
|
expect(indentationMultiple.test(line, 1)).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
37
src/rules/indentationMultiple.ts
Normal file
37
src/rules/indentationMultiple.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { LintConfig } from '../types'
|
||||||
|
import { LineLintRule } from '../types/LintRule'
|
||||||
|
import { LintRuleType } from '../types/LintRuleType'
|
||||||
|
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 indentationMultiple = config?.indentationMultiple || 2
|
||||||
|
const numberOfSpaces = value.search(/\S|$/)
|
||||||
|
if (numberOfSpaces % indentationMultiple === 0) return []
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
message: `${message} - ${numberOfSpaces} ${
|
||||||
|
numberOfSpaces === 1 ? 'space' : 'spaces'
|
||||||
|
}`,
|
||||||
|
lineNumber,
|
||||||
|
startColumnNumber: 1,
|
||||||
|
endColumnNumber: 1,
|
||||||
|
severity: Severity.Warning
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lint rule that checks if a line is indented by a multiple of the configured indentation multiple.
|
||||||
|
*/
|
||||||
|
export const indentationMultiple: LineLintRule = {
|
||||||
|
type: LintRuleType.Line,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
message,
|
||||||
|
test
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { hasDoxygenHeader } from '../rules/hasDoxygenHeader'
|
import { hasDoxygenHeader } from '../rules/hasDoxygenHeader'
|
||||||
|
import { indentationMultiple } from '../rules/indentationMultiple'
|
||||||
import { lowerCaseFileNames } from '../rules/lowerCaseFileNames'
|
import { lowerCaseFileNames } from '../rules/lowerCaseFileNames'
|
||||||
import { maxLineLength } from '../rules/maxLineLength'
|
import { maxLineLength } from '../rules/maxLineLength'
|
||||||
import { noEncodedPasswords } from '../rules/noEncodedPasswords'
|
import { noEncodedPasswords } from '../rules/noEncodedPasswords'
|
||||||
@@ -19,6 +20,7 @@ export class LintConfig {
|
|||||||
readonly fileLintRules: FileLintRule[] = []
|
readonly fileLintRules: FileLintRule[] = []
|
||||||
readonly pathLintRules: PathLintRule[] = []
|
readonly pathLintRules: PathLintRule[] = []
|
||||||
readonly maxLineLength = 80
|
readonly maxLineLength = 80
|
||||||
|
readonly indentationMultiple = 2
|
||||||
|
|
||||||
constructor(json?: any) {
|
constructor(json?: any) {
|
||||||
if (json?.noTrailingSpaces) {
|
if (json?.noTrailingSpaces) {
|
||||||
@@ -38,6 +40,11 @@ export class LintConfig {
|
|||||||
this.lineLintRules.push(maxLineLength)
|
this.lineLintRules.push(maxLineLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (json?.indentationMultiple) {
|
||||||
|
this.indentationMultiple = json.indentationMultiple
|
||||||
|
this.lineLintRules.push(indentationMultiple)
|
||||||
|
}
|
||||||
|
|
||||||
if (json?.hasDoxygenHeader) {
|
if (json?.hasDoxygenHeader) {
|
||||||
this.fileLintRules.push(hasDoxygenHeader)
|
this.fileLintRules.push(hasDoxygenHeader)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ describe('getLintConfig', () => {
|
|||||||
|
|
||||||
expect(config).toBeInstanceOf(LintConfig)
|
expect(config).toBeInstanceOf(LintConfig)
|
||||||
expect(config.fileLintRules.length).toEqual(1)
|
expect(config.fileLintRules.length).toEqual(1)
|
||||||
expect(config.lineLintRules.length).toEqual(4)
|
expect(config.lineLintRules.length).toEqual(5)
|
||||||
expect(config.pathLintRules.length).toEqual(2)
|
expect(config.pathLintRules.length).toEqual(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export const DefaultLintConfiguration = {
|
|||||||
noSpacesInFileNames: true,
|
noSpacesInFileNames: true,
|
||||||
lowerCaseFileNames: true,
|
lowerCaseFileNames: true,
|
||||||
maxLineLength: 80,
|
maxLineLength: 80,
|
||||||
noTabIndentation: true
|
noTabIndentation: true,
|
||||||
|
indentationMultiple: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user