mirror of
https://github.com/sasjs/lint.git
synced 2026-01-08 04:50:07 +00:00
feat: new rule hasMacroNameInMend
This commit is contained in:
219
src/rules/hasMacroNameInMend.spec.ts
Normal file
219
src/rules/hasMacroNameInMend.spec.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
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 missing macro name',
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 3,
|
||||
endColumnNumber: 9,
|
||||
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: 'mismatch macro name in %mend statement',
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 9,
|
||||
endColumnNumber: 25,
|
||||
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 missing macro name',
|
||||
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 missing macro name',
|
||||
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 missing macro name',
|
||||
lineNumber: 6,
|
||||
startColumnNumber: 5,
|
||||
endColumnNumber: 11,
|
||||
severity: Severity.Warning
|
||||
},
|
||||
{
|
||||
message: '%mend missing macro name',
|
||||
lineNumber: 9,
|
||||
startColumnNumber: 3,
|
||||
endColumnNumber: 9,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('with extra spaces ', () => {
|
||||
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 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: 'mismatch macro name in %mend statement',
|
||||
lineNumber: 6,
|
||||
startColumnNumber: 14,
|
||||
endColumnNumber: 30,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
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 missing macro name',
|
||||
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([])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
80
src/rules/hasMacroNameInMend.ts
Normal file
80
src/rules/hasMacroNameInMend.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Diagnostic } from '../types/Diagnostic'
|
||||
import { FileLintRule } from '../types/LintRule'
|
||||
import { LintRuleType } from '../types/LintRuleType'
|
||||
import { Severity } from '../types/Severity'
|
||||
|
||||
const name = 'hasMacroNameInMend'
|
||||
const description = 'The %mend statement should contain the macro name'
|
||||
const message = '$mend statement missing or incorrect'
|
||||
const test = (value: string) => {
|
||||
const diagnostics: Diagnostic[] = []
|
||||
|
||||
const statements: string[] = value ? value.split(';') : []
|
||||
|
||||
const stack: string[] = []
|
||||
statements.forEach((statement, index) => {
|
||||
const trimmedStatement = statement.trim()
|
||||
if (trimmedStatement.startsWith('%macro ')) {
|
||||
const macroName = trimmedStatement
|
||||
.split(' ')
|
||||
.filter((s: string) => !!s)[1]
|
||||
.split('(')[0]
|
||||
stack.push(macroName)
|
||||
} else if (trimmedStatement.startsWith('%mend')) {
|
||||
const macroStarted = stack.pop()
|
||||
const macroName = trimmedStatement
|
||||
.split(' ')
|
||||
.filter((s: string) => !!s)[1]
|
||||
|
||||
if (!macroName) {
|
||||
diagnostics.push({
|
||||
message: '%mend missing macro name',
|
||||
lineNumber: getLineNumber(statements, index + 1),
|
||||
startColumnNumber: getColNumber(statement, '%mend'),
|
||||
endColumnNumber: getColNumber(statement, '%mend') + 6,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
} else if (macroName !== macroStarted) {
|
||||
diagnostics.push({
|
||||
message: 'mismatch macro name in %mend statement',
|
||||
lineNumber: getLineNumber(statements, index + 1),
|
||||
startColumnNumber: getColNumber(statement, macroName),
|
||||
endColumnNumber:
|
||||
getColNumber(statement, macroName) + macroName.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
if (stack.length) {
|
||||
diagnostics.push({
|
||||
message: 'missing %mend statement for macro(s)',
|
||||
lineNumber: statements.length + 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
const getLineNumber = (statements: string[], index: number): number => {
|
||||
const combinedCode = statements.slice(0, index).join(';')
|
||||
const lines = (combinedCode.match(/\n/g) || []).length + 1
|
||||
return lines
|
||||
}
|
||||
|
||||
const getColNumber = (statement: string, text: string): number => {
|
||||
return statement.replace(/[\r\n]+/, '').indexOf(text) + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks for the presence of macro name in %mend statement.
|
||||
*/
|
||||
export const hasMacroNameInMend: FileLintRule = {
|
||||
type: LintRuleType.File,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
Reference in New Issue
Block a user