mirror of
https://github.com/sasjs/lint.git
synced 2025-12-10 17:34:36 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94d9d246eb | ||
|
|
95502647e8 | ||
|
|
be9d5b8e68 | ||
|
|
c2d368327b | ||
|
|
94a693e57d | ||
|
|
fec3372f92 | ||
|
|
d5b38373d4 | ||
|
|
21114e0a6f | ||
|
|
b52b3ac42f | ||
|
|
7f4c389468 | ||
|
|
1fd4cd7ddc | ||
|
|
b13302a315 | ||
|
|
0790a447f3 | ||
|
|
11182aaaa7 | ||
|
|
7144d0cfe3 | ||
|
|
0caf31b7ff | ||
|
|
020a1e08d0 | ||
|
|
a762dadf37 | ||
|
|
c9fa366130 | ||
|
|
5701064c07 | ||
|
|
cbfa1f40d1 | ||
|
|
d391a4e8fc | ||
|
|
f793eb3a76 | ||
|
|
af2d2c12c1 | ||
|
|
8bfb547427 | ||
|
|
d7721f8e5e | ||
|
|
021f36663a |
71
package-lock.json
generated
71
package-lock.json
generated
@@ -648,13 +648,15 @@
|
||||
}
|
||||
},
|
||||
"@sasjs/utils": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.12.0.tgz",
|
||||
"integrity": "sha512-OnC/7R+nGI8tlSPCcI7fPyD7T97B+McnkXT0IuAYDNGbfwRPuseWq0I1h+kbAWThGT67H4hnp61N0qr8LkpHZQ==",
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.19.0.tgz",
|
||||
"integrity": "sha512-b/NlIvTaISIFsllucetBwJjFUiM13I+bS06WtK3WN0G1EXzVrjJt+eXqgkQZbIZfoaeKo5oRigOtZUXth65duQ==",
|
||||
"requires": {
|
||||
"@types/prompts": "^2.0.13",
|
||||
"chalk": "^4.1.1",
|
||||
"cli-table": "^0.3.6",
|
||||
"consola": "^2.15.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"prompts": "^2.4.1",
|
||||
"valid-url": "^1.0.9"
|
||||
},
|
||||
@@ -782,10 +784,9 @@
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz",
|
||||
"integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==",
|
||||
"dev": true
|
||||
"version": "15.12.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
|
||||
"integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww=="
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
"version": "2.4.0",
|
||||
@@ -799,6 +800,14 @@
|
||||
"integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prompts": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.13.tgz",
|
||||
"integrity": "sha512-jwMOIGy49VruR/gYehhJYgpVzB+EVpEE7t7j9m1oTo4HMpOe7KmsyqdBuoxAzA5B4caUgx0cKrWr7wUEqMXJ7Q==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz",
|
||||
@@ -1938,6 +1947,23 @@
|
||||
"map-cache": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
|
||||
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
@@ -2022,8 +2048,7 @@
|
||||
"graceful-fs": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
|
||||
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
|
||||
},
|
||||
"growly": {
|
||||
"version": "1.3.0",
|
||||
@@ -3055,6 +3080,22 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||
@@ -4540,9 +4581,9 @@
|
||||
}
|
||||
},
|
||||
"ts-jest": {
|
||||
"version": "26.5.5",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.5.tgz",
|
||||
"integrity": "sha512-7tP4m+silwt1NHqzNRAPjW1BswnAhopTdc2K3HEkRZjF0ZG2F/e/ypVH0xiZIMfItFtD3CX0XFbwPzp9fIEUVg==",
|
||||
"version": "26.5.6",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
|
||||
"integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bs-logger": "0.x",
|
||||
@@ -4620,9 +4661,9 @@
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
||||
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz",
|
||||
"integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==",
|
||||
"dev": true
|
||||
},
|
||||
"union-value": {
|
||||
|
||||
@@ -38,13 +38,13 @@
|
||||
"homepage": "https://github.com/sasjs/lint#readme",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^15.0.2",
|
||||
"@types/node": "^15.12.2",
|
||||
"jest": "^26.6.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^26.5.5",
|
||||
"typescript": "^4.2.4"
|
||||
"ts-jest": "^26.5.6",
|
||||
"typescript": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "^2.12.0"
|
||||
"@sasjs/utils": "^2.19.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ describe('formatText', () => {
|
||||
new LintConfig(getLintConfigModule.DefaultLintConfiguration)
|
||||
)
|
||||
)
|
||||
const text = `%macro test
|
||||
const text = `%macro test;
|
||||
%put 'hello';\r\n%mend; `
|
||||
|
||||
const expectedOutput = `/**
|
||||
@file
|
||||
@brief <Your brief here>
|
||||
<h4> SAS Macros </h4>
|
||||
**/\n%macro test
|
||||
**/\n%macro test;
|
||||
%put 'hello';\n%mend test;`
|
||||
|
||||
const output = await formatText(text)
|
||||
@@ -38,9 +38,9 @@ describe('formatText', () => {
|
||||
})
|
||||
)
|
||||
)
|
||||
const text = `%macro test\n %put 'hello';\r\n%mend; `
|
||||
const text = `%macro test;\n %put 'hello';\r\n%mend; `
|
||||
|
||||
const expectedOutput = `/**\r\n @file\r\n @brief <Your brief here>\r\n <h4> SAS Macros </h4>\r\n**/\r\n%macro test\r\n %put 'hello';\r\n%mend test;`
|
||||
const expectedOutput = `/**\r\n @file\r\n @brief <Your brief here>\r\n <h4> SAS Macros </h4>\r\n**/\r\n%macro test;\r\n %put 'hello';\r\n%mend test;`
|
||||
|
||||
const output = await formatText(text)
|
||||
|
||||
|
||||
@@ -17,6 +17,32 @@ describe('hasDoxygenHeader - test', () => {
|
||||
expect(hasDoxygenHeader.test(content)).toEqual([])
|
||||
})
|
||||
|
||||
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([
|
||||
{
|
||||
message:
|
||||
'File not following Doxygen header style, use double asterisks',
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when the file has no header', () => {
|
||||
const content = `
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
@@ -86,7 +112,33 @@ describe('hasDoxygenHeader - fix', () => {
|
||||
expect(hasDoxygenHeader.fix!(content)).toEqual(content)
|
||||
})
|
||||
|
||||
it('should should add a doxygen header if not present', () => {
|
||||
it('should update single asterisks to double if a doxygen header is already present', () => {
|
||||
const contentOriginal = `
|
||||
/*
|
||||
@file
|
||||
@brief Returns an unused libref
|
||||
*/
|
||||
|
||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x={SAS002};
|
||||
%do x=0 %to &maxtries;`
|
||||
|
||||
const contentExpected = `
|
||||
/**
|
||||
@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.fix!(contentOriginal)).toEqual(contentExpected)
|
||||
})
|
||||
|
||||
it('should add a doxygen header if not present', () => {
|
||||
const content = `%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||
%local x libref;
|
||||
%let x={SAS002};
|
||||
|
||||
@@ -10,10 +10,28 @@ 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) => {
|
||||
const messageForSingleAsterisk =
|
||||
'File not following Doxygen header style, use double asterisks'
|
||||
const test = (value: string, config?: LintConfig) => {
|
||||
const lineEnding = config?.lineEndings === LineEndings.CRLF ? '\r\n' : '\n'
|
||||
try {
|
||||
const hasFileHeader = value.trimStart().startsWith('/*')
|
||||
const hasFileHeader = value.trimStart().startsWith('/**')
|
||||
if (hasFileHeader) return []
|
||||
|
||||
const hasFileHeaderWithSingleAsterisk = value.trimStart().startsWith('/*')
|
||||
if (hasFileHeaderWithSingleAsterisk)
|
||||
return [
|
||||
{
|
||||
message: messageForSingleAsterisk,
|
||||
lineNumber:
|
||||
(value.split('/*')![0]!.match(new RegExp(lineEnding, 'g')) ?? [])
|
||||
.length + 1,
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
]
|
||||
|
||||
return [
|
||||
{
|
||||
message,
|
||||
@@ -37,9 +55,12 @@ const test = (value: string) => {
|
||||
}
|
||||
|
||||
const fix = (value: string, config?: LintConfig): string => {
|
||||
if (test(value).length === 0) {
|
||||
const result = test(value, config)
|
||||
if (result.length === 0) {
|
||||
return value
|
||||
}
|
||||
} else if (result[0].message == messageForSingleAsterisk)
|
||||
return value.replace('/*', '/**')
|
||||
|
||||
const lineEndingConfig = config?.lineEndings || LineEndings.LF
|
||||
const lineEnding = lineEndingConfig === LineEndings.LF ? '\n' : '\r\n'
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('hasMacroNameInMend - test', () => {
|
||||
it('should return an array with a diagnostic for each macro missing an %mend statement', () => {
|
||||
const content = `%macro somemacro;
|
||||
%put &sysmacroname;
|
||||
%macro othermacro`
|
||||
%macro othermacro;`
|
||||
|
||||
expect(hasMacroNameInMend.test(content)).toEqual([
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ const test = (value: string, config?: LintConfig) => {
|
||||
const macros = parseMacros(value, config)
|
||||
const diagnostics: Diagnostic[] = []
|
||||
macros.forEach((macro) => {
|
||||
if (macro.startLineNumber === null && macro.endLineNumber !== null) {
|
||||
if (macro.startLineNumbers.length === 0 && macro.endLineNumber !== null) {
|
||||
const endLine = lines[macro.endLineNumber - 1]
|
||||
diagnostics.push({
|
||||
message: `%mend statement is redundant`,
|
||||
@@ -27,10 +27,13 @@ const test = (value: string, config?: LintConfig) => {
|
||||
getColumnNumber(endLine, '%mend') + macro.termination.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
} else if (macro.endLineNumber === null && macro.startLineNumber !== null) {
|
||||
} else if (
|
||||
macro.endLineNumber === null &&
|
||||
macro.startLineNumbers.length !== 0
|
||||
) {
|
||||
diagnostics.push({
|
||||
message: `Missing %mend statement for macro - ${macro.name}`,
|
||||
lineNumber: macro.startLineNumber,
|
||||
lineNumber: macro.startLineNumbers![0],
|
||||
startColumnNumber: 1,
|
||||
endColumnNumber: 1,
|
||||
severity: Severity.Warning
|
||||
@@ -73,7 +76,7 @@ const fix = (value: string, config?: LintConfig): string => {
|
||||
const macros = parseMacros(value, config)
|
||||
|
||||
macros.forEach((macro) => {
|
||||
if (macro.startLineNumber === null && macro.endLineNumber !== null) {
|
||||
if (macro.startLineNumbers.length === 0 && macro.endLineNumber !== null) {
|
||||
// %mend statement is redundant
|
||||
const endLine = lines[macro.endLineNumber - 1]
|
||||
const startColumnNumber = getColumnNumber(endLine, '%mend')
|
||||
@@ -83,7 +86,10 @@ const fix = (value: string, config?: LintConfig): string => {
|
||||
const beforeStatement = endLine.slice(0, startColumnNumber - 1)
|
||||
const afterStatement = endLine.slice(endColumnNumber)
|
||||
lines[macro.endLineNumber - 1] = beforeStatement + afterStatement
|
||||
} else if (macro.endLineNumber === null && macro.startLineNumber !== null) {
|
||||
} else if (
|
||||
macro.endLineNumber === null &&
|
||||
macro.startLineNumbers.length !== 0
|
||||
) {
|
||||
// missing %mend statement
|
||||
} else if (macro.mismatchedMendMacroName) {
|
||||
// mismatched macro name
|
||||
|
||||
@@ -139,18 +139,4 @@ describe('hasMacroParentheses', () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,36 +16,36 @@ const test = (value: string, config?: LintConfig) => {
|
||||
if (!macro.name) {
|
||||
diagnostics.push({
|
||||
message: 'Macro definition missing name',
|
||||
lineNumber: macro.startLineNumber!,
|
||||
startColumnNumber: getColumnNumber(macro.declarationLine, '%macro'),
|
||||
lineNumber: macro.startLineNumbers![0],
|
||||
startColumnNumber: getColumnNumber(
|
||||
macro.declarationLines![0],
|
||||
'%macro'
|
||||
),
|
||||
endColumnNumber:
|
||||
getColumnNumber(macro.declarationLine, '%macro') +
|
||||
getColumnNumber(macro.declarationLines![0], '%macro') +
|
||||
macro.declaration.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
} else if (!macro.declarationLine.includes('(')) {
|
||||
} else if (!macro.declarationLines.find((dl) => dl.includes('('))) {
|
||||
const macroNameLineIndex = macro.declarationLines.findIndex((dl) =>
|
||||
dl.includes(macro.name)
|
||||
)
|
||||
diagnostics.push({
|
||||
message,
|
||||
lineNumber: macro.startLineNumber!,
|
||||
startColumnNumber: getColumnNumber(macro.declarationLine, macro.name),
|
||||
lineNumber: macro.startLineNumbers![macroNameLineIndex],
|
||||
startColumnNumber: getColumnNumber(
|
||||
macro.declarationLines[macroNameLineIndex],
|
||||
macro.name
|
||||
),
|
||||
endColumnNumber:
|
||||
getColumnNumber(macro.declarationLine, macro.name) +
|
||||
getColumnNumber(
|
||||
macro.declarationLines[macroNameLineIndex],
|
||||
macro.name
|
||||
) +
|
||||
macro.name.length -
|
||||
1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
} else if (macro.name !== macro.name.trim()) {
|
||||
diagnostics.push({
|
||||
message: 'Macro definition contains space(s)',
|
||||
lineNumber: macro.startLineNumber!,
|
||||
startColumnNumber: getColumnNumber(macro.declarationLine, macro.name),
|
||||
endColumnNumber:
|
||||
getColumnNumber(macro.declarationLine, macro.name) +
|
||||
macro.name.length -
|
||||
1 +
|
||||
`()`.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -3,3 +3,4 @@ export { hasMacroNameInMend } from './hasMacroNameInMend'
|
||||
export { hasMacroParentheses } from './hasMacroParentheses'
|
||||
export { lineEndings } from './lineEndings'
|
||||
export { noNestedMacros } from './noNestedMacros'
|
||||
export { strictMacroDefinition } from './strictMacroDefinition'
|
||||
|
||||
@@ -22,17 +22,17 @@ const test = (value: string, config?: LintConfig) => {
|
||||
message: message
|
||||
.replace('{macro}', macro.name)
|
||||
.replace('{parent}', macro.parentMacro),
|
||||
lineNumber: macro.startLineNumber as number,
|
||||
lineNumber: macro.startLineNumbers![0] as number,
|
||||
startColumnNumber: getColumnNumber(
|
||||
lines[(macro.startLineNumber as number) - 1],
|
||||
lines[(macro.startLineNumbers![0] as number) - 1],
|
||||
'%macro'
|
||||
),
|
||||
endColumnNumber:
|
||||
getColumnNumber(
|
||||
lines[(macro.startLineNumber as number) - 1],
|
||||
lines[(macro.startLineNumbers![0] as number) - 1],
|
||||
'%macro'
|
||||
) +
|
||||
lines[(macro.startLineNumber as number) - 1].trim().length -
|
||||
lines[(macro.startLineNumbers![0] as number) - 1].trim().length -
|
||||
1,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
|
||||
216
src/rules/file/strictMacroDefinition.spec.ts
Normal file
216
src/rules/file/strictMacroDefinition.spec.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import { LintConfig, Severity } from '../../types'
|
||||
import { strictMacroDefinition } from './strictMacroDefinition'
|
||||
|
||||
describe('strictMacroDefinition', () => {
|
||||
it('should return an empty array when the content has correct macro definition syntax', () => {
|
||||
const content = '%macro somemacro;'
|
||||
expect(strictMacroDefinition.test(content)).toEqual([])
|
||||
|
||||
const content2 = '%macro somemacro();'
|
||||
expect(strictMacroDefinition.test(content2)).toEqual([])
|
||||
|
||||
const content3 = '%macro somemacro(var1);'
|
||||
expect(strictMacroDefinition.test(content3)).toEqual([])
|
||||
|
||||
const content4 = '%macro somemacro/minoperator;'
|
||||
expect(strictMacroDefinition.test(content4)).toEqual([])
|
||||
|
||||
const content5 = '%macro somemacro /minoperator;'
|
||||
expect(strictMacroDefinition.test(content5)).toEqual([])
|
||||
|
||||
const content6 = '%macro somemacro(var1, var2)/minoperator;'
|
||||
expect(strictMacroDefinition.test(content6)).toEqual([])
|
||||
|
||||
const content7 =
|
||||
' /* Some Comment */ %macro somemacro(var1, var2) /minoperator ; /* Some Comment */'
|
||||
expect(strictMacroDefinition.test(content7)).toEqual([])
|
||||
|
||||
const content8 =
|
||||
'%macro macroName( arr, arr/* / store source */3 ) /* / store source */;/* / store source */'
|
||||
expect(strictMacroDefinition.test(content8)).toEqual([])
|
||||
|
||||
const content9 = '%macro macroName(var1, var2=with space, var3=);'
|
||||
expect(strictMacroDefinition.test(content9)).toEqual([])
|
||||
|
||||
const content10 = '%macro macroName()/ /* some comment */ store source;'
|
||||
expect(strictMacroDefinition.test(content10)).toEqual([])
|
||||
|
||||
const content11 = '`%macro macroName() /* / store source */;'
|
||||
expect(strictMacroDefinition.test(content11)).toEqual([])
|
||||
|
||||
const content12 =
|
||||
'%macro macroName()/ /* some comment */ store des="some description";'
|
||||
expect(strictMacroDefinition.test(content12)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when Macro definition has space in param', () => {
|
||||
const content = '%macro somemacro(va r1);'
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Param 'va r1' cannot have space`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 18,
|
||||
endColumnNumber: 22,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a two diagnostics when Macro definition has space in params', () => {
|
||||
const content = '%macro somemacro(var1, var 2, v ar3, var4);'
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Param 'var 2' cannot have space`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 24,
|
||||
endColumnNumber: 28,
|
||||
severity: Severity.Warning
|
||||
},
|
||||
{
|
||||
message: `Param 'v ar3' cannot have space`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 31,
|
||||
endColumnNumber: 35,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a two diagnostics when Macro definition has space in params - special case', () => {
|
||||
const content =
|
||||
'%macro macroName( arr, ar r/* / store source */ 3 ) /* / store source */;/* / store source */'
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Param 'ar r 3' cannot have space`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 24,
|
||||
endColumnNumber: 49,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when Macro definition has invalid option', () => {
|
||||
const content = '%macro somemacro(var1, var2)/minXoperator;'
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Option 'minXoperator' is not valid`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 30,
|
||||
endColumnNumber: 41,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a two diagnostics when Macro definition has invalid options', () => {
|
||||
const content =
|
||||
'%macro somemacro(var1, var2)/ store invalidoption secure ;'
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Option 'invalidoption' is not valid`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 39,
|
||||
endColumnNumber: 51,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
describe('multi-content macro declarations', () => {
|
||||
it('should return an empty array when the content has correct macro definition syntax', () => {
|
||||
const content = `%macro mp_ds2cards(base_ds=, tgt_ds=\n ,cards_file="%sysfunc(pathname(work))/cardgen.sas"\n ,maxobs=max\n ,random_sample=NO\n ,showlog=YES\n ,outencoding=\n ,append=NO\n)/*/STORE SOURCE*/;`
|
||||
expect(strictMacroDefinition.test(content)).toEqual([])
|
||||
|
||||
const content2 = `%macro mm_createapplication(\n tree=/User Folders/sasdemo\n ,name=myApp\n ,ClassIdentifier=mcore\n ,desc=Created by mm_createapplication\n ,params= param1=1
param2=blah\n ,version=\n ,frefin=mm_in\n ,frefout=mm_out\n ,mDebug=1\n );`
|
||||
expect(strictMacroDefinition.test(content2)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when Macro definition has space in param', () => {
|
||||
const content = `%macro
|
||||
somemacro(va r1);`
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Param 'va r1' cannot have space`,
|
||||
lineNumber: 2,
|
||||
startColumnNumber: 18,
|
||||
endColumnNumber: 22,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a two diagnostics when Macro definition has space in params', () => {
|
||||
const content = `%macro somemacro(
|
||||
var1,
|
||||
var 2,
|
||||
v ar3,
|
||||
var4);`
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Param 'var 2' cannot have space`,
|
||||
lineNumber: 3,
|
||||
startColumnNumber: 7,
|
||||
endColumnNumber: 11,
|
||||
severity: Severity.Warning
|
||||
},
|
||||
{
|
||||
message: `Param 'v ar3' cannot have space`,
|
||||
lineNumber: 4,
|
||||
startColumnNumber: 7,
|
||||
endColumnNumber: 11,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a two diagnostics when Macro definition has space in params - special case', () => {
|
||||
const content = `%macro macroName(
|
||||
arr,
|
||||
ar r/* / store source */ 3
|
||||
) /* / store source */;/* / store source */`
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Param 'ar r 3' cannot have space`,
|
||||
lineNumber: 3,
|
||||
startColumnNumber: 7,
|
||||
endColumnNumber: 32,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when Macro definition has invalid option', () => {
|
||||
const content = `%macro somemacro(var1, var2)
|
||||
/minXoperator;`
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Option 'minXoperator' is not valid`,
|
||||
lineNumber: 2,
|
||||
startColumnNumber: 8,
|
||||
endColumnNumber: 19,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a two diagnostics when Macro definition has invalid options', () => {
|
||||
const content = `%macro
|
||||
somemacro(
|
||||
var1, var2
|
||||
)
|
||||
/ store
|
||||
invalidoption
|
||||
secure ;`
|
||||
expect(strictMacroDefinition.test(content)).toEqual([
|
||||
{
|
||||
message: `Option 'invalidoption' is not valid`,
|
||||
lineNumber: 6,
|
||||
startColumnNumber: 16,
|
||||
endColumnNumber: 28,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
169
src/rules/file/strictMacroDefinition.ts
Normal file
169
src/rules/file/strictMacroDefinition.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { Diagnostic, LintConfig, Macro, Severity } from '../../types'
|
||||
import { FileLintRule } from '../../types/LintRule'
|
||||
import { LintRuleType } from '../../types/LintRuleType'
|
||||
import { parseMacros } from '../../utils/parseMacros'
|
||||
|
||||
const name = 'strictMacroDefinition'
|
||||
const description = 'Enforce strictly rules of macro definition syntax.'
|
||||
const message = 'Incorrent Macro Definition Syntax'
|
||||
|
||||
const validOptions = [
|
||||
'CMD',
|
||||
'DES',
|
||||
'MINDELIMITER',
|
||||
'MINOPERATOR',
|
||||
'NOMINOPERATOR',
|
||||
'PARMBUFF',
|
||||
'SECURE',
|
||||
'NOSECURE',
|
||||
'STMT',
|
||||
'SOURCE',
|
||||
'SRC',
|
||||
'STORE'
|
||||
]
|
||||
|
||||
const processParams = (
|
||||
content: string,
|
||||
macro: Macro,
|
||||
diagnostics: Diagnostic[]
|
||||
): string => {
|
||||
const declaration = macro.declaration
|
||||
|
||||
const regExpParams = new RegExp(/(?<=\().*(?=\))/)
|
||||
const regExpParamsResult = regExpParams.exec(declaration)
|
||||
|
||||
let _declaration = declaration
|
||||
if (regExpParamsResult) {
|
||||
const paramsPresent = regExpParamsResult[0]
|
||||
|
||||
const params = paramsPresent.trim().split(',')
|
||||
params.forEach((param) => {
|
||||
const trimedParam = param.split('=')[0].trim()
|
||||
|
||||
let paramLineNumber: number = 1,
|
||||
paramStartIndex: number = 1,
|
||||
paramEndIndex: number = content.length
|
||||
|
||||
if (
|
||||
macro.declarationLines.findIndex(
|
||||
(dl) => dl.indexOf(trimedParam) !== -1
|
||||
) === -1
|
||||
) {
|
||||
const comment = '/\\*(.*?)\\*/'
|
||||
for (let i = 1; i < trimedParam.length; i++) {
|
||||
const paramWithComment =
|
||||
trimedParam.slice(0, i) + comment + trimedParam.slice(i)
|
||||
const regEx = new RegExp(paramWithComment)
|
||||
|
||||
const declarationLineIndex = macro.declarationLines.findIndex(
|
||||
(dl) => !!regEx.exec(dl)
|
||||
)
|
||||
|
||||
if (declarationLineIndex !== -1) {
|
||||
const declarationLine = macro.declarationLines[declarationLineIndex]
|
||||
const partFound = regEx.exec(declarationLine)![0]
|
||||
|
||||
paramLineNumber = macro.startLineNumbers[declarationLineIndex]
|
||||
paramStartIndex = declarationLine.indexOf(partFound)
|
||||
paramEndIndex =
|
||||
declarationLine.indexOf(partFound) + partFound.length
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const declarationLineIndex = macro.declarationLines.findIndex(
|
||||
(dl) => dl.indexOf(trimedParam) !== -1
|
||||
)
|
||||
const declarationLine = macro.declarationLines[declarationLineIndex]
|
||||
paramLineNumber = macro.startLineNumbers[declarationLineIndex]
|
||||
|
||||
paramStartIndex = declarationLine.indexOf(trimedParam)
|
||||
paramEndIndex =
|
||||
declarationLine.indexOf(trimedParam) + trimedParam.length
|
||||
}
|
||||
|
||||
if (trimedParam.includes(' ')) {
|
||||
diagnostics.push({
|
||||
message: `Param '${trimedParam}' cannot have space`,
|
||||
lineNumber: paramLineNumber,
|
||||
startColumnNumber: paramStartIndex + 1,
|
||||
endColumnNumber: paramEndIndex,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
_declaration = declaration.split(`(${paramsPresent})`)[1]
|
||||
}
|
||||
return _declaration
|
||||
}
|
||||
|
||||
const processOptions = (
|
||||
_declaration: string,
|
||||
macro: Macro,
|
||||
diagnostics: Diagnostic[]
|
||||
): void => {
|
||||
let optionsPresent = _declaration.split('/')?.[1]?.trim()
|
||||
|
||||
if (optionsPresent) {
|
||||
const regex = new RegExp(/="(.*?)"/, 'g')
|
||||
|
||||
let result = regex.exec(optionsPresent)
|
||||
|
||||
// removing Option's `="..."` part, e.g. des="..."
|
||||
while (result) {
|
||||
optionsPresent =
|
||||
optionsPresent.slice(0, result.index) +
|
||||
optionsPresent.slice(result.index + result[0].length)
|
||||
|
||||
result = regex.exec(optionsPresent)
|
||||
}
|
||||
|
||||
optionsPresent
|
||||
.split(' ')
|
||||
?.filter((o) => !!o)
|
||||
.forEach((option) => {
|
||||
const trimmedOption = option.trim()
|
||||
if (!validOptions.includes(trimmedOption.toUpperCase())) {
|
||||
const declarationLineIndex = macro.declarationLines.findIndex(
|
||||
(dl) => dl.indexOf(trimmedOption) !== -1
|
||||
)
|
||||
const declarationLine = macro.declarationLines[declarationLineIndex]
|
||||
|
||||
diagnostics.push({
|
||||
message: `Option '${trimmedOption}' is not valid`,
|
||||
lineNumber: macro.startLineNumbers[declarationLineIndex],
|
||||
startColumnNumber: declarationLine.indexOf(trimmedOption) + 1,
|
||||
endColumnNumber:
|
||||
declarationLine.indexOf(trimmedOption) + trimmedOption.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const test = (value: string, config?: LintConfig) => {
|
||||
const diagnostics: Diagnostic[] = []
|
||||
|
||||
const macros = parseMacros(value, config)
|
||||
|
||||
macros.forEach((macro) => {
|
||||
const _declaration = processParams(value, macro, diagnostics)
|
||||
|
||||
processOptions(_declaration, macro, diagnostics)
|
||||
})
|
||||
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks if a line has followed syntax for macro definition
|
||||
*/
|
||||
export const strictMacroDefinition: FileLintRule = {
|
||||
type: LintRuleType.File,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
@@ -3,4 +3,3 @@ export { maxLineLength } from './maxLineLength'
|
||||
export { noEncodedPasswords } from './noEncodedPasswords'
|
||||
export { noTabIndentation } from './noTabIndentation'
|
||||
export { noTrailingSpaces } from './noTrailingSpaces'
|
||||
export { strictMacroDefinition } from './strictMacroDefinition'
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import { LintConfig, Severity } from '../../types'
|
||||
import { strictMacroDefinition } from './strictMacroDefinition'
|
||||
|
||||
describe('strictMacroDefinition', () => {
|
||||
it('should return an empty array when the line has correct macro definition syntax', () => {
|
||||
const line = '%macro somemacro;'
|
||||
expect(strictMacroDefinition.test(line, 1)).toEqual([])
|
||||
|
||||
const line2 = '%macro somemacro();'
|
||||
expect(strictMacroDefinition.test(line2, 1)).toEqual([])
|
||||
|
||||
const line3 = '%macro somemacro(var1);'
|
||||
expect(strictMacroDefinition.test(line3, 1)).toEqual([])
|
||||
|
||||
const line4 = '%macro somemacro/minoperator;'
|
||||
expect(strictMacroDefinition.test(line4, 1)).toEqual([])
|
||||
|
||||
const line5 = '%macro somemacro /minoperator;'
|
||||
expect(strictMacroDefinition.test(line5, 1)).toEqual([])
|
||||
|
||||
const line6 = '%macro somemacro(var1, var2)/minoperator;'
|
||||
expect(strictMacroDefinition.test(line6, 1)).toEqual([])
|
||||
|
||||
const line7 =
|
||||
' /* Some Comment */ %macro somemacro(var1, var2) /minoperator ; /* Some Comment */'
|
||||
expect(strictMacroDefinition.test(line7, 1)).toEqual([])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when Macro definition has space in param', () => {
|
||||
const line = '%macro somemacro(va r1);'
|
||||
expect(strictMacroDefinition.test(line, 1)).toEqual([
|
||||
{
|
||||
message: `Param 'va r1' cannot have space`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 18,
|
||||
endColumnNumber: 22,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a two diagnostics when Macro definition has space in param', () => {
|
||||
const line = '%macro somemacro(var1, var 2, v ar3, var4);'
|
||||
expect(strictMacroDefinition.test(line, 1)).toEqual([
|
||||
{
|
||||
message: `Param 'var 2' cannot have space`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 24,
|
||||
endColumnNumber: 28,
|
||||
severity: Severity.Warning
|
||||
},
|
||||
{
|
||||
message: `Param 'v ar3' cannot have space`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 31,
|
||||
endColumnNumber: 35,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a single diagnostic when Macro definition has invalid option', () => {
|
||||
const line = '%macro somemacro(var1, var2)/minXoperator;'
|
||||
expect(strictMacroDefinition.test(line, 1)).toEqual([
|
||||
{
|
||||
message: `Option 'minXoperator' is not valid`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 30,
|
||||
endColumnNumber: 41,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should return an array with a two diagnostics when Macro definition has invalid options', () => {
|
||||
const line =
|
||||
'%macro somemacro(var1, var2)/ store invalidoption secure ;'
|
||||
expect(strictMacroDefinition.test(line, 1)).toEqual([
|
||||
{
|
||||
message: `Option 'invalidoption' is not valid`,
|
||||
lineNumber: 1,
|
||||
startColumnNumber: 39,
|
||||
endColumnNumber: 51,
|
||||
severity: Severity.Warning
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,88 +0,0 @@
|
||||
import { Diagnostic } from '../../types/Diagnostic'
|
||||
import { LintConfig } from '../../types'
|
||||
import { LineLintRule } from '../../types/LintRule'
|
||||
import { LintRuleType } from '../../types/LintRuleType'
|
||||
import { Severity } from '../../types/Severity'
|
||||
import { parseMacros } from '../../utils/parseMacros'
|
||||
|
||||
const name = 'strictMacroDefinition'
|
||||
const description = 'Enforce strictly rules of macro definition syntax.'
|
||||
const message = 'Incorrent Macro Definition Syntax'
|
||||
|
||||
const validOptions = [
|
||||
'CMD',
|
||||
'DES',
|
||||
'MINDELIMITER',
|
||||
'MINOPERATOR',
|
||||
'NOMINOPERATOR',
|
||||
'PARMBUFF',
|
||||
'SECURE',
|
||||
'NOSECURE',
|
||||
'STMT',
|
||||
'SOURCE',
|
||||
'SRC',
|
||||
'STORE'
|
||||
]
|
||||
|
||||
const test = (value: string, lineNumber: number) => {
|
||||
const diagnostics: Diagnostic[] = []
|
||||
|
||||
const macros = parseMacros(value)
|
||||
const declaration = macros[0]?.declaration
|
||||
if (!declaration) return []
|
||||
|
||||
const regExpParams = new RegExp(/\((.*?)\)/)
|
||||
const regExpParamsResult = regExpParams.exec(declaration)
|
||||
|
||||
let _declaration = declaration
|
||||
if (regExpParamsResult) {
|
||||
const paramsPresent = regExpParamsResult[1]
|
||||
|
||||
const paramsTrimmed = paramsPresent.trim()
|
||||
const params = paramsTrimmed.split(',')
|
||||
params.forEach((param) => {
|
||||
const trimedParam = param.split('=')[0].trim()
|
||||
if (trimedParam.includes(' ')) {
|
||||
diagnostics.push({
|
||||
message: `Param '${trimedParam}' cannot have space`,
|
||||
lineNumber,
|
||||
startColumnNumber: value.indexOf(trimedParam) + 1,
|
||||
endColumnNumber: value.indexOf(trimedParam) + trimedParam.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
_declaration = declaration.split(`(${paramsPresent})`)[1]
|
||||
}
|
||||
|
||||
const optionsPresent = _declaration.split('/')?.[1]?.trim().split(' ')
|
||||
|
||||
optionsPresent
|
||||
?.filter((o) => !!o)
|
||||
.forEach((option) => {
|
||||
const trimmedOption = option.trim()
|
||||
if (!validOptions.includes(trimmedOption.toUpperCase())) {
|
||||
diagnostics.push({
|
||||
message: `Option '${trimmedOption}' is not valid`,
|
||||
lineNumber,
|
||||
startColumnNumber: value.indexOf(trimmedOption) + 1,
|
||||
endColumnNumber: value.indexOf(trimmedOption) + trimmedOption.length,
|
||||
severity: Severity.Warning
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
/**
|
||||
* Lint rule that checks if a line has followed syntax for macro definition
|
||||
*/
|
||||
export const strictMacroDefinition: LineLintRule = {
|
||||
type: LintRuleType.Line,
|
||||
name,
|
||||
description,
|
||||
message,
|
||||
test
|
||||
}
|
||||
@@ -3,15 +3,15 @@ import {
|
||||
hasMacroNameInMend,
|
||||
noNestedMacros,
|
||||
hasMacroParentheses,
|
||||
lineEndings
|
||||
lineEndings,
|
||||
strictMacroDefinition
|
||||
} from '../rules/file'
|
||||
import {
|
||||
indentationMultiple,
|
||||
maxLineLength,
|
||||
noEncodedPasswords,
|
||||
noTabIndentation,
|
||||
noTrailingSpaces,
|
||||
strictMacroDefinition
|
||||
noTrailingSpaces
|
||||
} from '../rules/line'
|
||||
import { lowerCaseFileNames, noSpacesInFileNames } from '../rules/path'
|
||||
import { LineEndings } from './LineEndings'
|
||||
@@ -93,7 +93,7 @@ export class LintConfig {
|
||||
}
|
||||
|
||||
if (json?.strictMacroDefinition) {
|
||||
this.lineLintRules.push(strictMacroDefinition)
|
||||
this.fileLintRules.push(strictMacroDefinition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/types/Macro.ts
Normal file
12
src/types/Macro.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface Macro {
|
||||
name: string
|
||||
startLineNumbers: number[]
|
||||
endLineNumber: number | null
|
||||
declarationLines: string[]
|
||||
terminationLine: string
|
||||
declaration: string
|
||||
termination: string
|
||||
parentMacro: string
|
||||
hasMacroNameInMend: boolean
|
||||
mismatchedMendMacroName: string
|
||||
}
|
||||
@@ -4,3 +4,4 @@ export * from './LintConfig'
|
||||
export * from './LintRule'
|
||||
export * from './LintRuleType'
|
||||
export * from './Severity'
|
||||
export * from './Macro'
|
||||
|
||||
@@ -2,8 +2,8 @@ import * as fileModule from '@sasjs/utils/file'
|
||||
import { LintConfig } from '../types/LintConfig'
|
||||
import { getLintConfig } from './getLintConfig'
|
||||
|
||||
const expectedFileLintRulesCount = 4
|
||||
const expectedLineLintRulesCount = 6
|
||||
const expectedFileLintRulesCount = 5
|
||||
const expectedLineLintRulesCount = 5
|
||||
const expectedPathLintRulesCount = 2
|
||||
|
||||
describe('getLintConfig', () => {
|
||||
|
||||
@@ -3,173 +3,277 @@ import { parseMacros } from './parseMacros'
|
||||
|
||||
describe('parseMacros', () => {
|
||||
it('should return an array with a single macro', () => {
|
||||
const text = `%macro test;
|
||||
%put 'hello';
|
||||
%mend`
|
||||
const text = ` %macro test;\n %put 'hello';\n%mend`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(1)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'test',
|
||||
declarationLine: '%macro test;',
|
||||
declarationLines: [' %macro test;'],
|
||||
terminationLine: '%mend',
|
||||
declaration: '%macro test',
|
||||
termination: '%mend',
|
||||
startLineNumber: 1,
|
||||
startLineNumbers: [1],
|
||||
endLineNumber: 3,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
hasParentheses: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an array with a single macro having parameters', () => {
|
||||
const text = `%macro test(var,sum);
|
||||
%put 'hello';
|
||||
%mend`
|
||||
const text = `%macro test(var,sum);\n %put 'hello';\n%mend`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(1)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'test',
|
||||
declarationLine: '%macro test(var,sum);',
|
||||
declarationLines: ['%macro test(var,sum);'],
|
||||
terminationLine: '%mend',
|
||||
declaration: '%macro test(var,sum)',
|
||||
termination: '%mend',
|
||||
startLineNumber: 1,
|
||||
startLineNumbers: [1],
|
||||
endLineNumber: 3,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
hasParentheses: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an array with a single macro having PARMBUFF option', () => {
|
||||
const text = `%macro test/parmbuff;
|
||||
%put 'hello';
|
||||
%mend`
|
||||
const text = `%macro test/parmbuff;\n %put 'hello';\n%mend`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(1)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'test',
|
||||
declarationLine: '%macro test/parmbuff;',
|
||||
declarationLines: ['%macro test/parmbuff;'],
|
||||
terminationLine: '%mend',
|
||||
declaration: '%macro test/parmbuff',
|
||||
termination: '%mend',
|
||||
startLineNumber: 1,
|
||||
startLineNumbers: [1],
|
||||
endLineNumber: 3,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
hasParentheses: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an array with a single macro having paramerter & SOURCE option', () => {
|
||||
const text = `/* commentary */ %macro foobar(arg) /store source
|
||||
des="This macro does not do much";
|
||||
%put 'hello';
|
||||
%mend`
|
||||
const text = `/* commentary */ %macro foobar(arg) /store source\n des="This macro does not do much";\n %put 'hello';\n%mend`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(1)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'foobar',
|
||||
declarationLine: '/* commentary */ %macro foobar(arg) /store source',
|
||||
declarationLines: [
|
||||
'/* commentary */ %macro foobar(arg) /store source',
|
||||
' des="This macro does not do much";'
|
||||
],
|
||||
terminationLine: '%mend',
|
||||
declaration: '%macro foobar(arg) /store source',
|
||||
declaration:
|
||||
'%macro foobar(arg) /store source des="This macro does not do much"',
|
||||
termination: '%mend',
|
||||
startLineNumber: 1,
|
||||
startLineNumbers: [1, 2],
|
||||
endLineNumber: 4,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
hasParentheses: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an array with multiple macros', () => {
|
||||
const text = `%macro foo;
|
||||
%put 'foo';
|
||||
%mend;
|
||||
%macro bar();
|
||||
%put 'bar';
|
||||
%mend bar;`
|
||||
const text = `%macro foo;\n %put 'foo';\n%mend;\n%macro bar();\n %put 'bar';\n%mend bar;`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(2)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'foo',
|
||||
declarationLine: '%macro foo;',
|
||||
declarationLines: ['%macro foo;'],
|
||||
terminationLine: '%mend;',
|
||||
declaration: '%macro foo',
|
||||
termination: '%mend',
|
||||
startLineNumber: 1,
|
||||
startLineNumbers: [1],
|
||||
endLineNumber: 3,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
hasParentheses: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
expect(macros).toContainEqual({
|
||||
name: 'bar',
|
||||
declarationLine: '%macro bar();',
|
||||
declarationLines: ['%macro bar();'],
|
||||
terminationLine: '%mend bar;',
|
||||
declaration: '%macro bar()',
|
||||
termination: '%mend bar',
|
||||
startLineNumber: 4,
|
||||
startLineNumbers: [4],
|
||||
endLineNumber: 6,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: true,
|
||||
hasParentheses: true,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should detect nested macro definitions', () => {
|
||||
const text = `%macro test()
|
||||
%put 'hello';
|
||||
%macro test2
|
||||
%put 'world;
|
||||
%mend
|
||||
%mend test`
|
||||
const text = `%macro test();\n %put 'hello';\n %macro test2;\n %put 'world;\n %mend\n%mend test`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(2)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'test',
|
||||
declarationLine: '%macro test()',
|
||||
declarationLines: ['%macro test();'],
|
||||
terminationLine: '%mend test',
|
||||
declaration: '%macro test()',
|
||||
termination: '%mend test',
|
||||
startLineNumber: 1,
|
||||
startLineNumbers: [1],
|
||||
endLineNumber: 6,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: true,
|
||||
hasParentheses: true,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
expect(macros).toContainEqual({
|
||||
name: 'test2',
|
||||
declarationLine: ' %macro test2',
|
||||
declarationLines: [' %macro test2;'],
|
||||
terminationLine: ' %mend',
|
||||
declaration: '%macro test2',
|
||||
termination: '%mend',
|
||||
startLineNumber: 3,
|
||||
startLineNumbers: [3],
|
||||
endLineNumber: 5,
|
||||
parentMacro: 'test',
|
||||
hasMacroNameInMend: false,
|
||||
hasParentheses: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
describe(`multi-line macro declarations`, () => {
|
||||
it('should return an array with a single macro', () => {
|
||||
const text = `%macro \n test;\n %put 'hello';\n%mend`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(1)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'test',
|
||||
declarationLines: ['%macro ', ' test;'],
|
||||
terminationLine: '%mend',
|
||||
declaration: '%macro test',
|
||||
termination: '%mend',
|
||||
startLineNumbers: [1, 2],
|
||||
endLineNumber: 4,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an array with a single macro having parameters', () => {
|
||||
const text = `%macro \n test(\n var,\n sum);%put 'hello';\n%mend`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(1)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'test',
|
||||
declarationLines: [
|
||||
'%macro ',
|
||||
` test(`,
|
||||
` var,`,
|
||||
` sum);%put 'hello';`
|
||||
],
|
||||
terminationLine: '%mend',
|
||||
declaration: '%macro test( var, sum)',
|
||||
termination: '%mend',
|
||||
startLineNumbers: [1, 2, 3, 4],
|
||||
endLineNumber: 5,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an array with a single macro having PARMBUFF option', () => {
|
||||
const text = `%macro test\n /parmbuff;\n %put 'hello';\n%mend`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(1)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'test',
|
||||
declarationLines: ['%macro test', ' /parmbuff;'],
|
||||
terminationLine: '%mend',
|
||||
declaration: '%macro test /parmbuff',
|
||||
termination: '%mend',
|
||||
startLineNumbers: [1, 2],
|
||||
endLineNumber: 4,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an array with a single macro having paramerter & SOURCE option', () => {
|
||||
const text = `/* commentary */ %macro foobar/* commentary */(arg) \n /* commentary */\n /store\n /* commentary */source\n des="This macro does not do much";\n %put 'hello';\n%mend`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(1)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'foobar',
|
||||
declarationLines: [
|
||||
'/* commentary */ %macro foobar/* commentary */(arg) ',
|
||||
' /* commentary */',
|
||||
' /store',
|
||||
' /* commentary */source',
|
||||
' des="This macro does not do much";'
|
||||
],
|
||||
terminationLine: '%mend',
|
||||
declaration:
|
||||
'%macro foobar(arg) /store source des="This macro does not do much"',
|
||||
termination: '%mend',
|
||||
startLineNumbers: [1, 2, 3, 4, 5],
|
||||
endLineNumber: 7,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an array with a single macro having semi-colon in params', () => {
|
||||
const text = `\n%macro mm_createapplication(\n tree=/User Folders/sasdemo\n ,name=myApp\n ,ClassIdentifier=mcore\n ,desc=Created by mm_createapplication\n ,params= param1=1
param2=blah\n ,version=\n ,frefin=mm_in\n ,frefout=mm_out\n ,mDebug=1\n );`
|
||||
|
||||
const macros = parseMacros(text, new LintConfig())
|
||||
|
||||
expect(macros.length).toEqual(1)
|
||||
expect(macros).toContainEqual({
|
||||
name: 'mm_createapplication',
|
||||
declarationLines: [
|
||||
`%macro mm_createapplication(`,
|
||||
` tree=/User Folders/sasdemo`,
|
||||
` ,name=myApp`,
|
||||
` ,ClassIdentifier=mcore`,
|
||||
` ,desc=Created by mm_createapplication`,
|
||||
` ,params= param1=1
param2=blah`,
|
||||
` ,version=`,
|
||||
` ,frefin=mm_in`,
|
||||
` ,frefout=mm_out`,
|
||||
` ,mDebug=1`,
|
||||
` );`
|
||||
],
|
||||
terminationLine: '',
|
||||
declaration:
|
||||
'%macro mm_createapplication( tree=/User Folders/sasdemo ,name=myApp ,ClassIdentifier=mcore ,desc=Created by mm_createapplication ,params= param1=1
param2=blah ,version= ,frefin=mm_in ,frefout=mm_out ,mDebug=1 )',
|
||||
termination: '',
|
||||
startLineNumbers: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||
endLineNumber: null,
|
||||
parentMacro: '',
|
||||
hasMacroNameInMend: false,
|
||||
mismatchedMendMacroName: ''
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,21 +1,7 @@
|
||||
import { LintConfig } from '../types/LintConfig'
|
||||
import { LintConfig, Macro } from '../types'
|
||||
import { LineEndings } from '../types/LineEndings'
|
||||
import { trimComments } from './trimComments'
|
||||
|
||||
interface Macro {
|
||||
name: string
|
||||
startLineNumber: number | null
|
||||
endLineNumber: number | null
|
||||
declarationLine: string
|
||||
terminationLine: string
|
||||
declaration: string
|
||||
termination: string
|
||||
parentMacro: string
|
||||
hasMacroNameInMend: boolean
|
||||
hasParentheses: boolean
|
||||
mismatchedMendMacroName: string
|
||||
}
|
||||
|
||||
export const parseMacros = (text: string, config?: LintConfig): Macro[] => {
|
||||
const lineEnding = config?.lineEndings === LineEndings.CRLF ? '\r\n' : '\n'
|
||||
const lines: string[] = text ? text.split(lineEnding) : []
|
||||
@@ -23,39 +9,107 @@ export const parseMacros = (text: string, config?: LintConfig): Macro[] => {
|
||||
|
||||
let isCommentStarted = false
|
||||
let macroStack: Macro[] = []
|
||||
lines.forEach((line, index) => {
|
||||
let isReadingMacroDefinition = false
|
||||
let isStatementContinues = true
|
||||
let tempMacroDeclaration = ''
|
||||
let tempMacroDeclarationLines: string[] = []
|
||||
let tempStartLineNumbers: number[] = []
|
||||
lines.forEach((line, lineIndex) => {
|
||||
const { statement: trimmedLine, commentStarted } = trimComments(
|
||||
line,
|
||||
isCommentStarted
|
||||
)
|
||||
isCommentStarted = commentStarted
|
||||
const statements: string[] = trimmedLine ? trimmedLine.split(';') : []
|
||||
|
||||
statements.forEach((statement) => {
|
||||
isStatementContinues = !trimmedLine.endsWith(';')
|
||||
|
||||
const statements: string[] = trimmedLine.split(';')
|
||||
|
||||
if (isReadingMacroDefinition) {
|
||||
// checking if code is split into statements based on `;` is a part of HTML Encoded Character
|
||||
// if it happened, merges two statements into one
|
||||
statements.forEach((statement, statementIndex) => {
|
||||
if (/&[^\s]{1,5}$/.test(statement)) {
|
||||
const next = statements[statementIndex]
|
||||
const updatedStatement = `${statement};${
|
||||
statements[statementIndex + 1]
|
||||
}`
|
||||
statements.splice(statementIndex, 1, updatedStatement)
|
||||
statements.splice(statementIndex + 1, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
statements.forEach((statement, statementIndex) => {
|
||||
const { statement: trimmedStatement, commentStarted } = trimComments(
|
||||
statement,
|
||||
isCommentStarted
|
||||
)
|
||||
isCommentStarted = commentStarted
|
||||
|
||||
if (isReadingMacroDefinition) {
|
||||
tempMacroDeclaration =
|
||||
tempMacroDeclaration +
|
||||
(trimmedStatement ? ' ' + trimmedStatement : '')
|
||||
tempMacroDeclarationLines.push(line)
|
||||
tempStartLineNumbers.push(lineIndex + 1)
|
||||
|
||||
if (!Object.is(statements.length - 1, statementIndex)) {
|
||||
isReadingMacroDefinition = false
|
||||
|
||||
const name = tempMacroDeclaration
|
||||
.slice(7, tempMacroDeclaration.length)
|
||||
.trim()
|
||||
.split('/')[0]
|
||||
.split('(')[0]
|
||||
.trim()
|
||||
macroStack.push({
|
||||
name,
|
||||
startLineNumbers: tempStartLineNumbers,
|
||||
endLineNumber: null,
|
||||
parentMacro: macroStack.length
|
||||
? macroStack[macroStack.length - 1].name
|
||||
: '',
|
||||
hasMacroNameInMend: false,
|
||||
mismatchedMendMacroName: '',
|
||||
declarationLines: tempMacroDeclarationLines,
|
||||
terminationLine: '',
|
||||
declaration: tempMacroDeclaration,
|
||||
termination: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (trimmedStatement.startsWith('%macro')) {
|
||||
const startLineNumber = index + 1
|
||||
const startLineNumber = lineIndex + 1
|
||||
|
||||
if (
|
||||
isStatementContinues &&
|
||||
Object.is(statements.length - 1, statementIndex)
|
||||
) {
|
||||
tempMacroDeclaration = trimmedStatement
|
||||
tempMacroDeclarationLines = [line]
|
||||
tempStartLineNumbers = [startLineNumber]
|
||||
isReadingMacroDefinition = true
|
||||
return
|
||||
}
|
||||
|
||||
const name = trimmedStatement
|
||||
.slice(7, trimmedStatement.length)
|
||||
.trim()
|
||||
.split('/')[0]
|
||||
.split('(')[0]
|
||||
.trim()
|
||||
macroStack.push({
|
||||
name,
|
||||
startLineNumber,
|
||||
startLineNumbers: [startLineNumber],
|
||||
endLineNumber: null,
|
||||
parentMacro: macroStack.length
|
||||
? macroStack[macroStack.length - 1].name
|
||||
: '',
|
||||
hasParentheses: trimmedStatement.endsWith('()'),
|
||||
hasMacroNameInMend: false,
|
||||
mismatchedMendMacroName: '',
|
||||
declarationLine: line,
|
||||
declarationLines: [line],
|
||||
terminationLine: '',
|
||||
declaration: trimmedStatement,
|
||||
termination: ''
|
||||
@@ -65,7 +119,7 @@ export const parseMacros = (text: string, config?: LintConfig): Macro[] => {
|
||||
const macro = macroStack.pop() as Macro
|
||||
const mendMacroName =
|
||||
trimmedStatement.split(' ').filter((s: string) => !!s)[1] || ''
|
||||
macro.endLineNumber = index + 1
|
||||
macro.endLineNumber = lineIndex + 1
|
||||
macro.hasMacroNameInMend = mendMacroName === macro.name
|
||||
macro.mismatchedMendMacroName = macro.hasMacroNameInMend
|
||||
? ''
|
||||
@@ -76,13 +130,12 @@ export const parseMacros = (text: string, config?: LintConfig): Macro[] => {
|
||||
} else {
|
||||
macros.push({
|
||||
name: '',
|
||||
startLineNumber: null,
|
||||
endLineNumber: index + 1,
|
||||
startLineNumbers: [],
|
||||
endLineNumber: lineIndex + 1,
|
||||
parentMacro: '',
|
||||
hasParentheses: false,
|
||||
hasMacroNameInMend: false,
|
||||
mismatchedMendMacroName: '',
|
||||
declarationLine: '',
|
||||
declarationLines: [],
|
||||
terminationLine: line,
|
||||
declaration: '',
|
||||
termination: trimmedStatement
|
||||
|
||||
@@ -7,6 +7,27 @@ describe('trimComments', () => {
|
||||
/* some comment */ some code;
|
||||
`)
|
||||
).toEqual({ statement: 'some code;', commentStarted: false })
|
||||
|
||||
expect(
|
||||
trimComments(`
|
||||
/*/ some comment */ some code;
|
||||
`)
|
||||
).toEqual({ statement: 'some code;', commentStarted: false })
|
||||
|
||||
expect(
|
||||
trimComments(`
|
||||
some code;/*/ some comment */ some code;
|
||||
`)
|
||||
).toEqual({ statement: 'some code; some code;', commentStarted: false })
|
||||
|
||||
expect(
|
||||
trimComments(`/* some comment */
|
||||
/* some comment */ CODE_Keyword1 /* some comment */ CODE_Keyword2/* some comment */;/* some comment */
|
||||
/* some comment */`)
|
||||
).toEqual({
|
||||
statement: 'CODE_Keyword1 CODE_Keyword2;',
|
||||
commentStarted: false
|
||||
})
|
||||
})
|
||||
|
||||
it('should return statment, having multi-line comment', () => {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
export const trimComments = (
|
||||
statement: string,
|
||||
commentStarted: boolean = false
|
||||
commentStarted: boolean = false,
|
||||
trimEnd: boolean = false
|
||||
): { statement: string; commentStarted: boolean } => {
|
||||
let trimmed = (statement || '').trim()
|
||||
let trimmed = trimEnd ? (statement || '').trimEnd() : (statement || '').trim()
|
||||
|
||||
if (commentStarted || trimmed.startsWith('/*')) {
|
||||
const parts = trimmed.split('*/')
|
||||
const parts = trimmed.startsWith('/*')
|
||||
? trimmed.slice(2).split('*/')
|
||||
: trimmed.split('*/')
|
||||
if (parts.length === 2) {
|
||||
return {
|
||||
statement: (parts.pop() as string).trim(),
|
||||
@@ -17,6 +20,19 @@ export const trimComments = (
|
||||
} else {
|
||||
return { statement: '', commentStarted: true }
|
||||
}
|
||||
} else if (trimmed.includes('/*')) {
|
||||
const statementBeforeCommentStarts = trimmed.slice(0, trimmed.indexOf('/*'))
|
||||
trimmed = trimmed.slice(trimmed.indexOf('/*') + 2)
|
||||
const remainingStatement = trimmed.slice(trimmed.indexOf('*/') + 2)
|
||||
|
||||
const result = trimComments(remainingStatement, false, true)
|
||||
const completeStatement = statementBeforeCommentStarts + result.statement
|
||||
return {
|
||||
statement: trimEnd
|
||||
? completeStatement.trimEnd()
|
||||
: completeStatement.trim(),
|
||||
commentStarted: result.commentStarted
|
||||
}
|
||||
}
|
||||
return { statement: trimmed, commentStarted: false }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user