mirror of
https://github.com/sasjs/lint.git
synced 2026-01-13 07:10:05 +00:00
feat: new rules noNestedMacros & hasMacroParentheses
This commit is contained in:
@@ -48,7 +48,7 @@ describe('hasMacroNameInMend', () => {
|
||||
message: 'mismatch macro name in %mend statement',
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 9,
|
||||
endColumnNumber: 25,
|
||||
endColumnNumber: 24,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
@@ -219,7 +219,7 @@ describe('hasMacroNameInMend', () => {
|
||||
message: 'mismatch macro name in %mend statement',
|
||||
lineNumber: 6,
|
||||
startColumnNumber: 14,
|
||||
endColumnNumber: 30,
|
||||
endColumnNumber: 29,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
|
||||
@@ -2,6 +2,9 @@ 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 { getLineNumber } from '../utils/getLineNumber'
|
||||
import { getColNumber } from '../utils/getColNumber'
|
||||
|
||||
const name = 'hasMacroNameInMend'
|
||||
const description = 'The %mend statement should contain the macro name'
|
||||
@@ -46,7 +49,7 @@ const test = (value: string) => {
|
||||
lineNumber: getLineNumber(statements, index + 1),
|
||||
startColumnNumber: getColNumber(statement, macroName),
|
||||
endColumnNumber:
|
||||
getColNumber(statement, macroName) + macroName.length,
|
||||
getColNumber(statement, macroName) + macroName.length - 1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
@@ -64,36 +67,6 @@ const test = (value: string) => {
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
const trimComments = (
|
||||
statement: string,
|
||||
commentStarted: boolean = false
|
||||
): { statement: string; commentStarted: boolean } => {
|
||||
let trimmed = statement.trim()
|
||||
|
||||
if (commentStarted || trimmed.startsWith('/*')) {
|
||||
const parts = trimmed.split('*/')
|
||||
if (parts.length > 1) {
|
||||
return {
|
||||
statement: (parts.pop() as string).trim(),
|
||||
commentStarted: false
|
||||
}
|
||||
} else {
|
||||
return { statement: '', commentStarted: true }
|
||||
}
|
||||
}
|
||||
return { statement: trimmed, commentStarted: false }
|
||||
}
|
||||
|
||||
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.split('\n').pop() as string).indexOf(text) + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks for the presence of macro name in %mend statement.
|
||||
*/
|
||||
|
||||
68
src/rules/hasMacroParentheses.ts
Normal file
68
src/rules/hasMacroParentheses.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 { getLineNumber } from '../utils/getLineNumber'
|
||||
import { getColNumber } from '../utils/getColNumber'
|
||||
|
||||
const name = 'hasMacroParentheses'
|
||||
const description = 'Macros are always defined with parentheses'
|
||||
const message = 'Macro definition missing parentheses'
|
||||
const test = (value: string) => {
|
||||
const diagnostics: Diagnostic[] = []
|
||||
|
||||
const statements: string[] = value ? value.split(';') : []
|
||||
|
||||
let trimmedStatement = '',
|
||||
commentStarted = false
|
||||
statements.forEach((statement, index) => {
|
||||
;({ statement: trimmedStatement, commentStarted } = trimComments(
|
||||
statement,
|
||||
commentStarted
|
||||
))
|
||||
|
||||
if (trimmedStatement.startsWith('%macro ')) {
|
||||
const macroNameDefinition = trimmedStatement
|
||||
.slice(7, trimmedStatement.length)
|
||||
.trim()
|
||||
|
||||
const macroNameDefinitionParts = macroNameDefinition.split('(')
|
||||
const macroName = macroNameDefinitionParts[0]
|
||||
if (macroNameDefinitionParts.length === 1)
|
||||
diagnostics.push({
|
||||
message,
|
||||
lineNumber: getLineNumber(statements, index + 1),
|
||||
startColumnNumber: getColNumber(statement, macroNameDefinition),
|
||||
endColumnNumber:
|
||||
getColNumber(statement, macroNameDefinition) +
|
||||
macroNameDefinition.length -
|
||||
1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
else if (macroName !== macroName.trim())
|
||||
diagnostics.push({
|
||||
message: 'Macro definition cannot have space',
|
||||
lineNumber: getLineNumber(statements, index + 1),
|
||||
startColumnNumber: getColNumber(statement, macroNameDefinition),
|
||||
endColumnNumber:
|
||||
getColNumber(statement, macroNameDefinition) +
|
||||
macroNameDefinition.length -
|
||||
1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
})
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks for the presence of macro name in %mend statement.
|
||||
*/
|
||||
export const hasMacroParentheses: FileLintRule = {
|
||||
type: LintRuleType.File,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
59
src/rules/noNestedMacros.ts
Normal file
59
src/rules/noNestedMacros.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
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 { getLineNumber } from '../utils/getLineNumber'
|
||||
import { getColNumber } from '../utils/getColNumber'
|
||||
|
||||
const name = 'noNestedMacros'
|
||||
const description = 'Defining nested macro is not good practice'
|
||||
const message = 'Macro definition present inside another macro'
|
||||
const test = (value: string) => {
|
||||
const diagnostics: Diagnostic[] = []
|
||||
|
||||
const statements: string[] = value ? value.split(';') : []
|
||||
|
||||
const stack: string[] = []
|
||||
let trimmedStatement = '',
|
||||
commentStarted = false
|
||||
statements.forEach((statement, index) => {
|
||||
;({ statement: trimmedStatement, commentStarted } = trimComments(
|
||||
statement,
|
||||
commentStarted
|
||||
))
|
||||
|
||||
if (trimmedStatement.startsWith('%macro ')) {
|
||||
const macroName = trimmedStatement
|
||||
.split(' ')
|
||||
.filter((s: string) => !!s)[1]
|
||||
.split('(')[0]
|
||||
if (stack.length) {
|
||||
const parentMacro = stack.slice(-1).pop()
|
||||
diagnostics.push({
|
||||
message: `${message} '${parentMacro}'`,
|
||||
lineNumber: getLineNumber(statements, index + 1),
|
||||
startColumnNumber: getColNumber(statement, '%macro'),
|
||||
endColumnNumber:
|
||||
getColNumber(statement, '%macro') + trimmedStatement.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
stack.push(macroName)
|
||||
} else if (trimmedStatement.startsWith('%mend')) {
|
||||
stack.pop()
|
||||
}
|
||||
})
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks for the presence of macro name in %mend statement.
|
||||
*/
|
||||
export const noNestedMacros: FileLintRule = {
|
||||
type: LintRuleType.File,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
Reference in New Issue
Block a user