From a0e2c2d843d1fb7e2a3cf957df1a577993288256 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 5 Apr 2021 21:30:09 +0500 Subject: [PATCH 1/5] feat: new rule hasMacroNameInMend --- .sasjslint | 3 +- src/rules/hasMacroNameInMend.spec.ts | 219 +++++++++++++++++++++++++++ src/rules/hasMacroNameInMend.ts | 80 ++++++++++ src/types/LintConfig.ts | 5 + src/utils/getLintConfig.spec.ts | 2 +- src/utils/getLintConfig.ts | 3 +- 6 files changed, 309 insertions(+), 3 deletions(-) create mode 100644 src/rules/hasMacroNameInMend.spec.ts create mode 100644 src/rules/hasMacroNameInMend.ts diff --git a/.sasjslint b/.sasjslint index 44e07e4..2fef118 100644 --- a/.sasjslint +++ b/.sasjslint @@ -6,5 +6,6 @@ "maxLineLength": 80, "lowerCaseFileNames": true, "noTabIndentation": true, - "indentationMultiple": 2 + "indentationMultiple": 2, + "hasMacroNameInMend": false } \ No newline at end of file diff --git a/src/rules/hasMacroNameInMend.spec.ts b/src/rules/hasMacroNameInMend.spec.ts new file mode 100644 index 0000000..b2e5a5d --- /dev/null +++ b/src/rules/hasMacroNameInMend.spec.ts @@ -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([]) + }) + }) + }) +}) diff --git a/src/rules/hasMacroNameInMend.ts b/src/rules/hasMacroNameInMend.ts new file mode 100644 index 0000000..a12dd59 --- /dev/null +++ b/src/rules/hasMacroNameInMend.ts @@ -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 +} diff --git a/src/types/LintConfig.ts b/src/types/LintConfig.ts index 184202e..60b49df 100644 --- a/src/types/LintConfig.ts +++ b/src/types/LintConfig.ts @@ -6,6 +6,7 @@ import { noEncodedPasswords } from '../rules/noEncodedPasswords' import { noSpacesInFileNames } from '../rules/noSpacesInFileNames' import { noTabIndentation } from '../rules/noTabIndentation' import { noTrailingSpaces } from '../rules/noTrailingSpaces' +import { hasMacroNameInMend } from '../rules/hasMacroNameInMend' import { FileLintRule, LineLintRule, PathLintRule } from './LintRule' /** @@ -56,5 +57,9 @@ export class LintConfig { if (json?.lowerCaseFileNames) { this.pathLintRules.push(lowerCaseFileNames) } + + if (json?.hasMacroNameInMend !== undefined) { + this.fileLintRules.push(hasMacroNameInMend) + } } } diff --git a/src/utils/getLintConfig.spec.ts b/src/utils/getLintConfig.spec.ts index e7e4870..65d64bd 100644 --- a/src/utils/getLintConfig.spec.ts +++ b/src/utils/getLintConfig.spec.ts @@ -17,7 +17,7 @@ describe('getLintConfig', () => { const config = await getLintConfig() expect(config).toBeInstanceOf(LintConfig) - expect(config.fileLintRules.length).toEqual(1) + expect(config.fileLintRules.length).toEqual(2) expect(config.lineLintRules.length).toEqual(5) expect(config.pathLintRules.length).toEqual(2) }) diff --git a/src/utils/getLintConfig.ts b/src/utils/getLintConfig.ts index 032fbf0..9cd359d 100644 --- a/src/utils/getLintConfig.ts +++ b/src/utils/getLintConfig.ts @@ -14,7 +14,8 @@ export const DefaultLintConfiguration = { lowerCaseFileNames: true, maxLineLength: 80, noTabIndentation: true, - indentationMultiple: 2 + indentationMultiple: 2, + hasMacroNameInMend: false } /** From 5782886bdcdbcc69bf411907eb60456754f83777 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 5 Apr 2021 21:56:28 +0500 Subject: [PATCH 2/5] fix(hasMacroNameInMend): linting through comments --- src/rules/hasMacroNameInMend.spec.ts | 15 ++++++++++----- src/rules/hasMacroNameInMend.ts | 13 +++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/rules/hasMacroNameInMend.spec.ts b/src/rules/hasMacroNameInMend.spec.ts index b2e5a5d..28a15d9 100644 --- a/src/rules/hasMacroNameInMend.spec.ts +++ b/src/rules/hasMacroNameInMend.spec.ts @@ -149,12 +149,17 @@ describe('hasMacroNameInMend', () => { }) }) - describe('with extra spaces ', () => { + 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; - %mend somemacro ;` + + /* 2nd + comment */ + /* 3rd comment */ %mend somemacro ;` expect(hasMacroNameInMend.test(content)).toEqual([]) }) @@ -162,9 +167,9 @@ describe('hasMacroNameInMend', () => { 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([ @@ -181,7 +186,7 @@ describe('hasMacroNameInMend', () => { it('should return an array with a single diagnostic when %mend has no macro name', () => { const content = ` %macro somemacro ; - %put &sysmacroname; + /* some comments */%put &sysmacroname; %mend ;` expect(hasMacroNameInMend.test(content)).toEqual([ diff --git a/src/rules/hasMacroNameInMend.ts b/src/rules/hasMacroNameInMend.ts index a12dd59..fb0dcbe 100644 --- a/src/rules/hasMacroNameInMend.ts +++ b/src/rules/hasMacroNameInMend.ts @@ -13,7 +13,7 @@ const test = (value: string) => { const stack: string[] = [] statements.forEach((statement, index) => { - const trimmedStatement = statement.trim() + const trimmedStatement = trimComments(statement).trim() if (trimmedStatement.startsWith('%macro ')) { const macroName = trimmedStatement .split(' ') @@ -58,6 +58,15 @@ const test = (value: string) => { return diagnostics } +const trimComments = (statement: string): string => { + let trimmed = statement.trim() + + if (trimmed.startsWith('/*')) + trimmed = (trimmed.split('*/').pop() as string).trim() + + return trimmed +} + const getLineNumber = (statements: string[], index: number): number => { const combinedCode = statements.slice(0, index).join(';') const lines = (combinedCode.match(/\n/g) || []).length + 1 @@ -65,7 +74,7 @@ const getLineNumber = (statements: string[], index: number): number => { } const getColNumber = (statement: string, text: string): number => { - return statement.replace(/[\r\n]+/, '').indexOf(text) + 1 + return (statement.split('\n').pop() as string).indexOf(text) + 1 } /** From 86554a074c2c38e3f917a243332f67627c5a173f Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 5 Apr 2021 22:00:45 +0500 Subject: [PATCH 3/5] chore: tests fix --- src/lint/lintFile.spec.ts | 2 +- src/lint/lintFolder.spec.ts | 2 +- src/lint/lintProject.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lint/lintFile.spec.ts b/src/lint/lintFile.spec.ts index a7de9ef..1843393 100644 --- a/src/lint/lintFile.spec.ts +++ b/src/lint/lintFile.spec.ts @@ -8,7 +8,7 @@ describe('lintFile', () => { path.join(__dirname, '..', 'Example File.sas') ) - expect(results.length).toEqual(8) + expect(results.length).toEqual(9) expect(results).toContainEqual({ message: 'Line contains trailing spaces', lineNumber: 1, diff --git a/src/lint/lintFolder.spec.ts b/src/lint/lintFolder.spec.ts index 7124d3e..49ed8fa 100644 --- a/src/lint/lintFolder.spec.ts +++ b/src/lint/lintFolder.spec.ts @@ -10,7 +10,7 @@ describe('lintFolder', () => { const diagnostics = results.get( path.join(__dirname, '..', 'Example File.sas') )! - expect(diagnostics.length).toEqual(8) + expect(diagnostics.length).toEqual(9) expect(diagnostics).toContainEqual({ message: 'Line contains trailing spaces', lineNumber: 1, diff --git a/src/lint/lintProject.spec.ts b/src/lint/lintProject.spec.ts index 3a0b200..4bc4963 100644 --- a/src/lint/lintProject.spec.ts +++ b/src/lint/lintProject.spec.ts @@ -15,7 +15,7 @@ describe('lintProject', () => { const diagnostics = results.get( path.join(__dirname, '..', 'Example File.sas') )! - expect(diagnostics.length).toEqual(8) + expect(diagnostics.length).toEqual(9) expect(diagnostics).toContainEqual({ message: 'Line contains trailing spaces', lineNumber: 1, From 2f07bfa0a15bad4abd051ea287ff4786b744d3b3 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Mon, 5 Apr 2021 22:58:59 +0500 Subject: [PATCH 4/5] chore: updated tests --- src/lint/lintFile.spec.ts | 2 +- src/lint/lintFolder.spec.ts | 2 +- src/lint/lintProject.spec.ts | 2 +- src/types/LintConfig.spec.ts | 44 ++++++++++++++++++++++++++++++--- src/types/LintConfig.ts | 2 +- src/utils/getLintConfig.spec.ts | 2 +- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/lint/lintFile.spec.ts b/src/lint/lintFile.spec.ts index 1843393..a7de9ef 100644 --- a/src/lint/lintFile.spec.ts +++ b/src/lint/lintFile.spec.ts @@ -8,7 +8,7 @@ describe('lintFile', () => { path.join(__dirname, '..', 'Example File.sas') ) - expect(results.length).toEqual(9) + expect(results.length).toEqual(8) expect(results).toContainEqual({ message: 'Line contains trailing spaces', lineNumber: 1, diff --git a/src/lint/lintFolder.spec.ts b/src/lint/lintFolder.spec.ts index 49ed8fa..7124d3e 100644 --- a/src/lint/lintFolder.spec.ts +++ b/src/lint/lintFolder.spec.ts @@ -10,7 +10,7 @@ describe('lintFolder', () => { const diagnostics = results.get( path.join(__dirname, '..', 'Example File.sas') )! - expect(diagnostics.length).toEqual(9) + expect(diagnostics.length).toEqual(8) expect(diagnostics).toContainEqual({ message: 'Line contains trailing spaces', lineNumber: 1, diff --git a/src/lint/lintProject.spec.ts b/src/lint/lintProject.spec.ts index 4bc4963..3a0b200 100644 --- a/src/lint/lintProject.spec.ts +++ b/src/lint/lintProject.spec.ts @@ -15,7 +15,7 @@ describe('lintProject', () => { const diagnostics = results.get( path.join(__dirname, '..', 'Example File.sas') )! - expect(diagnostics.length).toEqual(9) + expect(diagnostics.length).toEqual(8) expect(diagnostics).toContainEqual({ message: 'Line contains trailing spaces', lineNumber: 1, diff --git a/src/types/LintConfig.spec.ts b/src/types/LintConfig.spec.ts index e11cd83..bc88fc3 100644 --- a/src/types/LintConfig.spec.ts +++ b/src/types/LintConfig.spec.ts @@ -40,6 +40,24 @@ describe('LintConfig', () => { expect(config.fileLintRules[0].type).toEqual(LintRuleType.File) }) + it('should create an instance with the hasMacroNameInMend flag set', () => { + const config = new LintConfig({ hasMacroNameInMend: true }) + + expect(config).toBeTruthy() + expect(config.lineLintRules.length).toEqual(0) + expect(config.fileLintRules.length).toEqual(1) + expect(config.fileLintRules[0].name).toEqual('hasMacroNameInMend') + expect(config.fileLintRules[0].type).toEqual(LintRuleType.File) + }) + + it('should create an instance with the hasMacroNameInMend flag off', () => { + const config = new LintConfig({ hasMacroNameInMend: false }) + + expect(config).toBeTruthy() + expect(config.lineLintRules.length).toEqual(0) + expect(config.fileLintRules.length).toEqual(0) + }) + it('should create an instance with the indentation multiple set', () => { const config = new LintConfig({ indentationMultiple: 5 }) @@ -58,18 +76,38 @@ describe('LintConfig', () => { const config = new LintConfig({ noTrailingSpaces: true, noEncodedPasswords: true, - hasDoxygenHeader: true + hasDoxygenHeader: true, + noSpacesInFileNames: true, + lowerCaseFileNames: true, + maxLineLength: 80, + noTabIndentation: true, + indentationMultiple: 2, + hasMacroNameInMend: true }) expect(config).toBeTruthy() - expect(config.lineLintRules.length).toEqual(2) + expect(config.lineLintRules.length).toEqual(5) expect(config.lineLintRules[0].name).toEqual('noTrailingSpaces') expect(config.lineLintRules[0].type).toEqual(LintRuleType.Line) expect(config.lineLintRules[1].name).toEqual('noEncodedPasswords') expect(config.lineLintRules[1].type).toEqual(LintRuleType.Line) + expect(config.lineLintRules[2].name).toEqual('noTabs') + expect(config.lineLintRules[2].type).toEqual(LintRuleType.Line) + expect(config.lineLintRules[3].name).toEqual('maxLineLength') + expect(config.lineLintRules[3].type).toEqual(LintRuleType.Line) + expect(config.lineLintRules[4].name).toEqual('indentationMultiple') + expect(config.lineLintRules[4].type).toEqual(LintRuleType.Line) - expect(config.fileLintRules.length).toEqual(1) + expect(config.fileLintRules.length).toEqual(2) expect(config.fileLintRules[0].name).toEqual('hasDoxygenHeader') expect(config.fileLintRules[0].type).toEqual(LintRuleType.File) + expect(config.fileLintRules[1].name).toEqual('hasMacroNameInMend') + expect(config.fileLintRules[1].type).toEqual(LintRuleType.File) + + expect(config.pathLintRules.length).toEqual(2) + expect(config.pathLintRules[0].name).toEqual('noSpacesInFileNames') + expect(config.pathLintRules[0].type).toEqual(LintRuleType.Path) + expect(config.pathLintRules[1].name).toEqual('lowerCaseFileNames') + expect(config.pathLintRules[1].type).toEqual(LintRuleType.Path) }) }) diff --git a/src/types/LintConfig.ts b/src/types/LintConfig.ts index 60b49df..f8e57c2 100644 --- a/src/types/LintConfig.ts +++ b/src/types/LintConfig.ts @@ -58,7 +58,7 @@ export class LintConfig { this.pathLintRules.push(lowerCaseFileNames) } - if (json?.hasMacroNameInMend !== undefined) { + if (json?.hasMacroNameInMend) { this.fileLintRules.push(hasMacroNameInMend) } } diff --git a/src/utils/getLintConfig.spec.ts b/src/utils/getLintConfig.spec.ts index 65d64bd..e7e4870 100644 --- a/src/utils/getLintConfig.spec.ts +++ b/src/utils/getLintConfig.spec.ts @@ -17,7 +17,7 @@ describe('getLintConfig', () => { const config = await getLintConfig() expect(config).toBeInstanceOf(LintConfig) - expect(config.fileLintRules.length).toEqual(2) + expect(config.fileLintRules.length).toEqual(1) expect(config.lineLintRules.length).toEqual(5) expect(config.pathLintRules.length).toEqual(2) }) From 443bdc0a506b6cbab4c90e003c8d6a1dffc46a98 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Tue, 6 Apr 2021 14:34:51 +0500 Subject: [PATCH 5/5] fix(hasMacroNameInMend): added support for comments having code in it --- src/rules/hasMacroNameInMend.spec.ts | 42 ++++++++++++++++++++++++++++ src/rules/hasMacroNameInMend.ts | 29 +++++++++++++++---- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/src/rules/hasMacroNameInMend.spec.ts b/src/rules/hasMacroNameInMend.spec.ts index 28a15d9..6ea35ca 100644 --- a/src/rules/hasMacroNameInMend.spec.ts +++ b/src/rules/hasMacroNameInMend.spec.ts @@ -164,6 +164,48 @@ describe('hasMacroNameInMend', () => { 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() + +

SAS Macros

+ @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 missing macro name', + 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; diff --git a/src/rules/hasMacroNameInMend.ts b/src/rules/hasMacroNameInMend.ts index fb0dcbe..338bf5a 100644 --- a/src/rules/hasMacroNameInMend.ts +++ b/src/rules/hasMacroNameInMend.ts @@ -12,8 +12,14 @@ const test = (value: string) => { const statements: string[] = value ? value.split(';') : [] const stack: string[] = [] + let trimmedStatement = '', + commentStarted = false statements.forEach((statement, index) => { - const trimmedStatement = trimComments(statement).trim() + ;({ statement: trimmedStatement, commentStarted } = trimComments( + statement, + commentStarted + )) + if (trimmedStatement.startsWith('%macro ')) { const macroName = trimmedStatement .split(' ') @@ -58,13 +64,24 @@ const test = (value: string) => { return diagnostics } -const trimComments = (statement: string): string => { +const trimComments = ( + statement: string, + commentStarted: boolean = false +): { statement: string; commentStarted: boolean } => { let trimmed = statement.trim() - if (trimmed.startsWith('/*')) - trimmed = (trimmed.split('*/').pop() as string).trim() - - return trimmed + 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 => {