mirror of
https://github.com/sasjs/lint.git
synced 2026-01-11 06:20:06 +00:00
chore(*): organise rules into folders by type
This commit is contained in:
71
src/rules/file/hasDoxygenHeader.spec.ts
Normal file
71
src/rules/file/hasDoxygenHeader.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { Severity } from '../../types/Severity'
|
||||
import { hasDoxygenHeader } from './hasDoxygenHeader'
|
||||
|
||||
describe('hasDoxygenHeader', () => {
|
||||
it('should return an empty array when the file starts with a doxygen header', () => {
|
||||
const content = `/**
|
||||
@file
|
||||
@brief Returns an unused libref
|
||||
**/
|
||||
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x={SAS002};
|
||||
%do x=0 %to &maxtries;`
|
||||
|
||||
expect(hasDoxygenHeader.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when the file has no header', () => {
|
||||
const content = `
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x={SAS002};
|
||||
%do x=0 %to &maxtries;`
|
||||
|
||||
expect(hasDoxygenHeader.test(content)).toEqual([
|
||||
{
|
||||
message: 'File missing Doxygen header',
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when the file has comment blocks but no header', () => {
|
||||
const content = `
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x={SAS002};
|
||||
/** Comment Line 1
|
||||
* Comment Line 2
|
||||
*/
|
||||
%do x=0 %to &maxtries;`
|
||||
|
||||
expect(hasDoxygenHeader.test(content)).toEqual([
|
||||
{
|
||||
message: 'File missing Doxygen header',
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when the file is undefined', () => {
|
||||
const content = undefined
|
||||
|
||||
expect(hasDoxygenHeader.test((content as unknown) as string)).toEqual([
|
||||
{
|
||||
message: 'File missing Doxygen header',
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
44
src/rules/file/hasDoxygenHeader.ts
Normal file
44
src/rules/file/hasDoxygenHeader.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
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 message = 'File missing Doxygen header'
|
||||
const test = (value: string) => {
|
||||
try {
|
||||
const hasFileHeader = value.trimStart().startsWith('/*')
|
||||
if (hasFileHeader) return []
|
||||
return [
|
||||
{
|
||||
message,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
]
|
||||
} catch (e) {
|
||||
return [
|
||||
{
|
||||
message,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks for the presence of a Doxygen header in a given file.
|
||||
*/
|
||||
export const hasDoxygenHeader: FileLintRule = {
|
||||
type: LintRuleType.File,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
322
src/rules/file/hasMacroNameInMend.spec.ts
Normal file
322
src/rules/file/hasMacroNameInMend.spec.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
import { Severity } from '../../types/Severity'
|
||||
import { hasMacroNameInMend } from './hasMacroNameInMend'
|
||||
|
||||
describe('hasMacroNameInMend', () => {
|
||||
it('should return an empty array when %mend has correct macro name', () => {
|
||||
const content = `
|
||||
%macro somemacro();
|
||||
%put &sysmacroname;
|
||||
%mend somemacro;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an empty array when %mend has correct macro name without parentheses', () => {
|
||||
const content = `
|
||||
%macro somemacro;
|
||||
%put &sysmacroname;
|
||||
%mend somemacro;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when %mend has no macro name', () => {
|
||||
const content = `
|
||||
%macro somemacro;
|
||||
%put &sysmacroname;
|
||||
%mend;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: '%mend statement is missing macro name - somemacro',
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 3,
|
||||
endColumnNumber: 9,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when a macro is missing an %mend statement', () => {
|
||||
const content = `%macro somemacro;
|
||||
%put &sysmacroname;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: 'Missing %mend statement for macro - somemacro',
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a diagnostic for each macro missing an %mend statement', () => {
|
||||
const content = `%macro somemacro;
|
||||
%put &sysmacroname;
|
||||
%macro othermacro`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: 'Missing %mend statement for macro - somemacro',
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
},
|
||||
{
|
||||
message: 'Missing %mend statement for macro - othermacro',
|
||||
lineNumber: 3,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when %mend has incorrect macro name', () => {
|
||||
const content = `
|
||||
%macro somemacro;
|
||||
%put &sysmacroname;
|
||||
%mend someanothermacro;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: `%mend statement has mismatched macro name, it should be 'somemacro'`,
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 9,
|
||||
endColumnNumber: 24,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when extra %mend statement is present', () => {
|
||||
const content = `
|
||||
%macro somemacro;
|
||||
%put &sysmacroname;
|
||||
%mend somemacro;
|
||||
%mend something;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: '%mend statement is redundant',
|
||||
lineNumber: 5,
|
||||
startColumnNumber: 3,
|
||||
endColumnNumber: 18,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an empty array when the file is undefined', () => {
|
||||
const content = undefined
|
||||
|
||||
expect(hasMacroNameInMend.test((content as unknown) as string)).toEqual([])
|
||||
})
|
||||
|
||||
describe('nestedMacros', () => {
|
||||
it('should return an empty array when %mend has correct macro name', () => {
|
||||
const content = `
|
||||
%macro outer();
|
||||
|
||||
%macro inner();
|
||||
%put inner;
|
||||
%mend inner;
|
||||
%inner()
|
||||
%put outer;
|
||||
%mend outer;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when %mend has no macro name(inner)', () => {
|
||||
const content = `
|
||||
%macro outer();
|
||||
|
||||
%macro inner();
|
||||
%put inner;
|
||||
%mend;
|
||||
%inner()
|
||||
%put outer;
|
||||
%mend outer;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: '%mend statement is missing macro name - inner',
|
||||
lineNumber: 6,
|
||||
startColumnNumber: 5,
|
||||
endColumnNumber: 11,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when %mend has no macro name(outer)', () => {
|
||||
const content = `
|
||||
%macro outer();
|
||||
|
||||
%macro inner();
|
||||
%put inner;
|
||||
%mend inner;
|
||||
%inner()
|
||||
%put outer;
|
||||
%mend;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: '%mend statement is missing macro name - outer',
|
||||
lineNumber: 9,
|
||||
startColumnNumber: 3,
|
||||
endColumnNumber: 9,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with two diagnostics when %mend has no macro name(none)', () => {
|
||||
const content = `
|
||||
%macro outer();
|
||||
|
||||
%macro inner();
|
||||
%put inner;
|
||||
%mend;
|
||||
%inner()
|
||||
%put outer;
|
||||
%mend;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: '%mend statement is missing macro name - inner',
|
||||
lineNumber: 6,
|
||||
startColumnNumber: 5,
|
||||
endColumnNumber: 11,
|
||||
severity: Severity.Warning
|
||||
},
|
||||
{
|
||||
message: '%mend statement is missing macro name - outer',
|
||||
lineNumber: 9,
|
||||
startColumnNumber: 3,
|
||||
endColumnNumber: 9,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('with extra spaces and comments', () => {
|
||||
it('should return an empty array when %mend has correct macro name', () => {
|
||||
const content = `
|
||||
/* 1st comment */
|
||||
%macro somemacro ;
|
||||
|
||||
%put &sysmacroname;
|
||||
|
||||
/* 2nd
|
||||
comment */
|
||||
/* 3rd comment */ %mend somemacro ;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when %mend has correct macro name having code in comments', () => {
|
||||
const content = `/**
|
||||
@file examplemacro.sas
|
||||
@brief an example of a macro to be used in a service
|
||||
@details This macro is great. Yadda yadda yadda. Usage:
|
||||
|
||||
* code formatting applies when indented by 4 spaces; code formatting applies when indented by 4 spaces; code formatting applies when indented by 4 spaces; code formatting applies when indented by 4 spaces; code formatting applies when indented by 4 spaces;
|
||||
|
||||
some code
|
||||
%macro examplemacro123();
|
||||
|
||||
%examplemacro()
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li doesnothing.sas
|
||||
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro examplemacro();
|
||||
|
||||
proc sql;
|
||||
create table areas
|
||||
as select area
|
||||
|
||||
from sashelp.springs;
|
||||
|
||||
%doesnothing();
|
||||
|
||||
%mend;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: '%mend statement is missing macro name - examplemacro',
|
||||
lineNumber: 29,
|
||||
startColumnNumber: 5,
|
||||
endColumnNumber: 11,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when %mend has incorrect macro name', () => {
|
||||
const content = `
|
||||
%macro somemacro;
|
||||
/* some comments */
|
||||
%put &sysmacroname;
|
||||
/* some comments */
|
||||
%mend someanothermacro ;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: `%mend statement has mismatched macro name, it should be 'somemacro'`,
|
||||
lineNumber: 6,
|
||||
startColumnNumber: 14,
|
||||
endColumnNumber: 29,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when %mend has no macro name', () => {
|
||||
const content = `
|
||||
%macro somemacro ;
|
||||
/* some comments */%put &sysmacroname;
|
||||
%mend ;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
message: '%mend statement is missing macro name - somemacro',
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 5,
|
||||
endColumnNumber: 11,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
describe('nestedMacros', () => {
|
||||
it('should return an empty array when %mend has correct macro name', () => {
|
||||
const content = `
|
||||
%macro outer( ) ;
|
||||
|
||||
|
||||
%macro inner();
|
||||
|
||||
%put inner;
|
||||
|
||||
%mend inner;
|
||||
|
||||
%inner()
|
||||
|
||||
%put outer;
|
||||
%mend outer;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
107
src/rules/file/hasMacroNameInMend.ts
Normal file
107
src/rules/file/hasMacroNameInMend.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Diagnostic } from '../../types/Diagnostic'
|
||||
import { FileLintRule } from '../../types/LintRule'
|
||||
import { LintRuleType } from '../../types/LintRuleType'
|
||||
import { Severity } from '../../types/Severity'
|
||||
import { trimComments } from '../../utils/trimComments'
|
||||
import { getColumnNumber } from '../../utils/getColumnNumber'
|
||||
|
||||
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) => {
|
||||
const diagnostics: Diagnostic[] = []
|
||||
|
||||
const lines: string[] = value ? value.split('\n') : []
|
||||
|
||||
const declaredMacros: { name: string; lineNumber: number }[] = []
|
||||
let isCommentStarted = false
|
||||
lines.forEach((line, lineIndex) => {
|
||||
const { statement: trimmedLine, commentStarted } = trimComments(
|
||||
line,
|
||||
isCommentStarted
|
||||
)
|
||||
isCommentStarted = commentStarted
|
||||
const statements: string[] = trimmedLine ? trimmedLine.split(';') : []
|
||||
|
||||
statements.forEach((statement) => {
|
||||
const { statement: trimmedStatement, commentStarted } = trimComments(
|
||||
statement,
|
||||
isCommentStarted
|
||||
)
|
||||
isCommentStarted = commentStarted
|
||||
|
||||
if (trimmedStatement.startsWith('%macro ')) {
|
||||
const macroName = trimmedStatement
|
||||
.slice(7, trimmedStatement.length)
|
||||
.trim()
|
||||
.split('(')[0]
|
||||
if (macroName)
|
||||
declaredMacros.push({
|
||||
name: macroName,
|
||||
lineNumber: lineIndex + 1
|
||||
})
|
||||
} else if (trimmedStatement.startsWith('%mend')) {
|
||||
const declaredMacro = declaredMacros.pop()
|
||||
const macroName = trimmedStatement
|
||||
.split(' ')
|
||||
.filter((s: string) => !!s)[1]
|
||||
|
||||
if (!declaredMacro) {
|
||||
diagnostics.push({
|
||||
message: `%mend statement is redundant`,
|
||||
lineNumber: lineIndex + 1,
|
||||
startColumnNumber: getColumnNumber(line, '%mend'),
|
||||
endColumnNumber:
|
||||
getColumnNumber(line, '%mend') + trimmedStatement.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
} else if (!macroName) {
|
||||
diagnostics.push({
|
||||
message: `%mend statement is missing macro name - ${
|
||||
declaredMacro!.name
|
||||
}`,
|
||||
lineNumber: lineIndex + 1,
|
||||
startColumnNumber: getColumnNumber(line, '%mend'),
|
||||
endColumnNumber: getColumnNumber(line, '%mend') + 6,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
} else if (macroName !== declaredMacro!.name) {
|
||||
diagnostics.push({
|
||||
message: `%mend statement has mismatched macro name, it should be '${
|
||||
declaredMacro!.name
|
||||
}'`,
|
||||
lineNumber: lineIndex + 1,
|
||||
startColumnNumber: getColumnNumber(line, macroName),
|
||||
endColumnNumber:
|
||||
getColumnNumber(line, macroName) + macroName.length - 1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
declaredMacros.forEach((declaredMacro) => {
|
||||
diagnostics.push({
|
||||
message: `Missing %mend statement for macro - ${declaredMacro.name}`,
|
||||
lineNumber: declaredMacro.lineNumber,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
})
|
||||
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks for the presence of macro name in %mend statement.
|
||||
*/
|
||||
export const hasMacroNameInMend: FileLintRule = {
|
||||
type: LintRuleType.File,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
142
src/rules/file/hasMacroParentheses.spec.ts
Normal file
142
src/rules/file/hasMacroParentheses.spec.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { Severity } from '../../types/Severity'
|
||||
import { hasMacroParentheses } from './hasMacroParentheses'
|
||||
|
||||
describe('hasMacroParentheses', () => {
|
||||
it('should return an empty array when macro defined correctly', () => {
|
||||
const content = `
|
||||
%macro somemacro();
|
||||
%put &sysmacroname;
|
||||
%mend somemacro;`
|
||||
|
||||
expect(hasMacroParentheses.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostics when macro defined without parentheses', () => {
|
||||
const content = `
|
||||
%macro somemacro;
|
||||
%put &sysmacroname;
|
||||
%mend somemacro;`
|
||||
|
||||
expect(hasMacroParentheses.test(content)).toEqual([
|
||||
{
|
||||
message: 'Macro definition missing parentheses',
|
||||
lineNumber: 2,
|
||||
startColumnNumber: 10,
|
||||
endColumnNumber: 18,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostics when macro defined without name', () => {
|
||||
const content = `
|
||||
%macro ();
|
||||
%put &sysmacroname;
|
||||
%mend;`
|
||||
|
||||
expect(hasMacroParentheses.test(content)).toEqual([
|
||||
{
|
||||
message: 'Macro definition missing name',
|
||||
lineNumber: 2,
|
||||
startColumnNumber: 3,
|
||||
endColumnNumber: 12,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostics when macro defined without name and parentheses', () => {
|
||||
const content = `
|
||||
%macro ;
|
||||
%put &sysmacroname;
|
||||
%mend;`
|
||||
|
||||
expect(hasMacroParentheses.test(content)).toEqual([
|
||||
{
|
||||
message: 'Macro definition missing name',
|
||||
lineNumber: 2,
|
||||
startColumnNumber: 3,
|
||||
endColumnNumber: 9,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an empty array when the file is undefined', () => {
|
||||
const content = undefined
|
||||
|
||||
expect(hasMacroParentheses.test((content as unknown) as string)).toEqual([])
|
||||
})
|
||||
|
||||
describe('with extra spaces and comments', () => {
|
||||
it('should return an empty array when %mend has correct macro name', () => {
|
||||
const content = `
|
||||
/* 1st comment */
|
||||
%macro somemacro();
|
||||
|
||||
%put &sysmacroname;
|
||||
|
||||
/* 2nd
|
||||
comment */
|
||||
/* 3rd comment */ %mend somemacro ;`
|
||||
|
||||
expect(hasMacroParentheses.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when macro defined without parentheses having code in comments', () => {
|
||||
const content = `/**
|
||||
@file examplemacro.sas
|
||||
@brief an example of a macro to be used in a service
|
||||
@details This macro is great. Yadda yadda yadda. Usage:
|
||||
|
||||
* code formatting applies when indented by 4 spaces; code formatting applies when indented by 4 spaces; code formatting applies when indented by 4 spaces; code formatting applies when indented by 4 spaces; code formatting applies when indented by 4 spaces;
|
||||
|
||||
some code
|
||||
%macro examplemacro123();
|
||||
|
||||
%examplemacro()
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li doesnothing.sas
|
||||
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro examplemacro;
|
||||
|
||||
proc sql;
|
||||
create table areas
|
||||
as select area
|
||||
|
||||
from sashelp.springs;
|
||||
|
||||
%doesnothing();
|
||||
|
||||
%mend;`
|
||||
|
||||
expect(hasMacroParentheses.test(content)).toEqual([
|
||||
{
|
||||
message: 'Macro definition missing parentheses',
|
||||
lineNumber: 19,
|
||||
startColumnNumber: 12,
|
||||
endColumnNumber: 23,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when a macro definition contains a space', () => {
|
||||
const content = `%macro test ()`
|
||||
|
||||
expect(hasMacroParentheses.test(content)).toEqual([
|
||||
{
|
||||
message: 'Macro definition contains space(s)',
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 8,
|
||||
endColumnNumber: 14,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
85
src/rules/file/hasMacroParentheses.ts
Normal file
85
src/rules/file/hasMacroParentheses.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Diagnostic } from '../../types/Diagnostic'
|
||||
import { FileLintRule } from '../../types/LintRule'
|
||||
import { LintRuleType } from '../../types/LintRuleType'
|
||||
import { Severity } from '../../types/Severity'
|
||||
import { trimComments } from '../../utils/trimComments'
|
||||
import { getColumnNumber } from '../../utils/getColumnNumber'
|
||||
|
||||
const name = 'hasMacroParentheses'
|
||||
const description = 'Enforces the presence of parentheses in macro definitions.'
|
||||
const message = 'Macro definition missing parentheses'
|
||||
const test = (value: string) => {
|
||||
const diagnostics: Diagnostic[] = []
|
||||
|
||||
const lines: string[] = value ? value.split('\n') : []
|
||||
let isCommentStarted = false
|
||||
lines.forEach((line, lineIndex) => {
|
||||
const { statement: trimmedLine, commentStarted } = trimComments(
|
||||
line,
|
||||
isCommentStarted
|
||||
)
|
||||
isCommentStarted = commentStarted
|
||||
const statements: string[] = trimmedLine ? trimmedLine.split(';') : []
|
||||
|
||||
statements.forEach((statement) => {
|
||||
const { statement: trimmedStatement, commentStarted } = trimComments(
|
||||
statement,
|
||||
isCommentStarted
|
||||
)
|
||||
isCommentStarted = commentStarted
|
||||
|
||||
if (trimmedStatement.startsWith('%macro')) {
|
||||
const macroNameDefinition = trimmedStatement
|
||||
.slice(7, trimmedStatement.length)
|
||||
.trim()
|
||||
|
||||
const macroNameDefinitionParts = macroNameDefinition.split('(')
|
||||
const macroName = macroNameDefinitionParts[0]
|
||||
|
||||
if (!macroName)
|
||||
diagnostics.push({
|
||||
message: 'Macro definition missing name',
|
||||
lineNumber: lineIndex + 1,
|
||||
startColumnNumber: getColumnNumber(line, '%macro'),
|
||||
endColumnNumber:
|
||||
getColumnNumber(line, '%macro') + trimmedStatement.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
else if (macroNameDefinitionParts.length === 1)
|
||||
diagnostics.push({
|
||||
message,
|
||||
lineNumber: lineIndex + 1,
|
||||
startColumnNumber: getColumnNumber(line, macroNameDefinition),
|
||||
endColumnNumber:
|
||||
getColumnNumber(line, macroNameDefinition) +
|
||||
macroNameDefinition.length -
|
||||
1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
else if (macroName !== macroName.trim())
|
||||
diagnostics.push({
|
||||
message: 'Macro definition contains space(s)',
|
||||
lineNumber: lineIndex + 1,
|
||||
startColumnNumber: getColumnNumber(line, macroNameDefinition),
|
||||
endColumnNumber:
|
||||
getColumnNumber(line, macroNameDefinition) +
|
||||
macroNameDefinition.length -
|
||||
1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that enforces the presence of parentheses in macro definitions..
|
||||
*/
|
||||
export const hasMacroParentheses: FileLintRule = {
|
||||
type: LintRuleType.File,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
4
src/rules/file/index.ts
Normal file
4
src/rules/file/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { hasDoxygenHeader } from './hasDoxygenHeader'
|
||||
export { hasMacroNameInMend } from './hasMacroNameInMend'
|
||||
export { hasMacroParentheses } from './hasMacroParentheses'
|
||||
export { noNestedMacros } from './noNestedMacros'
|
||||
78
src/rules/file/noNestedMacros.spec.ts
Normal file
78
src/rules/file/noNestedMacros.spec.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Severity } from '../../types/Severity'
|
||||
import { noNestedMacros } from './noNestedMacros'
|
||||
|
||||
describe('noNestedMacros', () => {
|
||||
it('should return an empty array when no nested macro', () => {
|
||||
const content = `
|
||||
%macro somemacro();
|
||||
%put &sysmacroname;
|
||||
%mend somemacro;`
|
||||
|
||||
expect(noNestedMacros.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when a macro contains a nested macro definition', () => {
|
||||
const content = `
|
||||
%macro outer();
|
||||
/* any amount of arbitrary code */
|
||||
%macro inner();
|
||||
%put inner;
|
||||
%mend;
|
||||
%inner()
|
||||
%put outer;
|
||||
%mend;
|
||||
|
||||
%outer()`
|
||||
|
||||
expect(noNestedMacros.test(content)).toEqual([
|
||||
{
|
||||
message: "Macro definition for 'inner' present in macro 'outer'",
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 7,
|
||||
endColumnNumber: 20,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when nested macros are defined at 2 levels', () => {
|
||||
const content = `
|
||||
%macro outer();
|
||||
/* any amount of arbitrary code */
|
||||
%macro inner();
|
||||
%put inner;
|
||||
|
||||
%macro inner2();
|
||||
%put inner2;
|
||||
%mend;
|
||||
%mend;
|
||||
%inner()
|
||||
%put outer;
|
||||
%mend;
|
||||
|
||||
%outer()`
|
||||
|
||||
expect(noNestedMacros.test(content)).toEqual([
|
||||
{
|
||||
message: "Macro definition for 'inner' present in macro 'outer'",
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 7,
|
||||
endColumnNumber: 20,
|
||||
severity: Severity.Warning
|
||||
},
|
||||
{
|
||||
message: "Macro definition for 'inner2' present in macro 'inner'",
|
||||
lineNumber: 7,
|
||||
startColumnNumber: 17,
|
||||
endColumnNumber: 31,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an empty array when the file is undefined', () => {
|
||||
const content = undefined
|
||||
|
||||
expect(noNestedMacros.test((content as unknown) as string)).toEqual([])
|
||||
})
|
||||
})
|
||||
68
src/rules/file/noNestedMacros.ts
Normal file
68
src/rules/file/noNestedMacros.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Diagnostic } from '../../types/Diagnostic'
|
||||
import { FileLintRule } from '../../types/LintRule'
|
||||
import { LintRuleType } from '../../types/LintRuleType'
|
||||
import { Severity } from '../../types/Severity'
|
||||
import { trimComments } from '../../utils/trimComments'
|
||||
import { getColumnNumber } from '../../utils/getColumnNumber'
|
||||
|
||||
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) => {
|
||||
const diagnostics: Diagnostic[] = []
|
||||
const declaredMacros: string[] = []
|
||||
|
||||
const lines: string[] = value ? value.split('\n') : []
|
||||
let isCommentStarted = false
|
||||
lines.forEach((line, lineIndex) => {
|
||||
const { statement: trimmedLine, commentStarted } = trimComments(
|
||||
line,
|
||||
isCommentStarted
|
||||
)
|
||||
isCommentStarted = commentStarted
|
||||
const statements: string[] = trimmedLine ? trimmedLine.split(';') : []
|
||||
|
||||
statements.forEach((statement) => {
|
||||
const { statement: trimmedStatement, commentStarted } = trimComments(
|
||||
statement,
|
||||
isCommentStarted
|
||||
)
|
||||
isCommentStarted = commentStarted
|
||||
|
||||
if (trimmedStatement.startsWith('%macro ')) {
|
||||
const macroName = trimmedStatement
|
||||
.slice(7, trimmedStatement.length)
|
||||
.trim()
|
||||
.split('(')[0]
|
||||
if (declaredMacros.length) {
|
||||
const parentMacro = declaredMacros.slice(-1).pop()
|
||||
diagnostics.push({
|
||||
message: message
|
||||
.replace('{macro}', macroName)
|
||||
.replace('{parent}', parentMacro!),
|
||||
lineNumber: lineIndex + 1,
|
||||
startColumnNumber: getColumnNumber(line, '%macro'),
|
||||
endColumnNumber:
|
||||
getColumnNumber(line, '%macro') + trimmedStatement.length - 1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
declaredMacros.push(macroName)
|
||||
} else if (trimmedStatement.startsWith('%mend')) {
|
||||
declaredMacros.pop()
|
||||
}
|
||||
})
|
||||
})
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks for the absence of nested macro definitions.
|
||||
*/
|
||||
export const noNestedMacros: FileLintRule = {
|
||||
type: LintRuleType.File,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
Reference in New Issue
Block a user