Skip to content

Commit 2cc686a

Browse files
fix: robust README config parsing
1 parent 264dff6 commit 2cc686a

2 files changed

Lines changed: 75 additions & 10 deletions

File tree

scripts/check-modules/__tests__/module-analyzer.test.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ async function createTempModule() {
2121
"Update steps",
2222
"",
2323
"## Config",
24+
"```javascript",
2425
"{",
2526
" module: \"MMM-Remote-Control\",",
2627
" config: {",
28+
" credentials: {",
29+
" clientId: \"\"",
30+
" },",
2731
" foo: true",
28-
" },",
32+
" }",
2933
"},",
34+
"```",
3035
""
3136
].join("\n")
3237
);

scripts/check-modules/module-analyzer.ts

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -369,12 +369,73 @@ function isExcludedForMomentScan(modulePath: string, filePath: string): boolean
369369
|| segments.includes("coverage");
370370
}
371371

372-
function searchRegexInFile(content: string, pattern: RegExp): boolean {
373-
try {
374-
return pattern.test(content);
375-
} catch {
376-
return false;
372+
function findReadmeConfigObjects(content: string): Array<{ hasTrailingComma: boolean }> {
373+
const lines = content.split(/\r?\n/u);
374+
const objects: Array<{ hasTrailingComma: boolean }> = [];
375+
const headingRegex = /^(#{2,6})\s+(.+?)\s*$/u;
376+
const fenceOpenRegex = /^```([A-Za-z0-9_-]+)?\s*$/u;
377+
const fenceCloseRegex = /^```\s*$/u;
378+
379+
let inConfigSection = false;
380+
let configSectionLevel = 0;
381+
let inFence = false;
382+
let fenceLanguage = "";
383+
let fenceLines: string[] = [];
384+
let fenceWasInConfigSection = false;
385+
386+
for (const line of lines) {
387+
if (!inFence) {
388+
const headingMatch = line.match(headingRegex);
389+
if (headingMatch) {
390+
const headingLevel = headingMatch[1].length;
391+
const headingText = headingMatch[2].trim().toLowerCase();
392+
393+
if (headingText === "config" || headingText === "configuration") {
394+
inConfigSection = true;
395+
configSectionLevel = headingLevel;
396+
}
397+
else if (inConfigSection && headingLevel <= configSectionLevel) {
398+
inConfigSection = false;
399+
}
400+
}
401+
402+
const fenceOpenMatch = line.match(fenceOpenRegex);
403+
if (fenceOpenMatch) {
404+
inFence = true;
405+
fenceLanguage = (fenceOpenMatch[1] ?? "").toLowerCase();
406+
fenceLines = [];
407+
fenceWasInConfigSection = inConfigSection;
408+
}
409+
410+
continue;
411+
}
412+
413+
if (fenceCloseRegex.test(line)) {
414+
const isJavaScriptFence = fenceLanguage === "" || fenceLanguage === "js" || fenceLanguage === "javascript";
415+
if (fenceWasInConfigSection && isJavaScriptFence) {
416+
const blockText = fenceLines.join("\n");
417+
const hasModuleKey = /\bmodule\s*:/u.test(blockText);
418+
const hasConfigKey = /\bconfig\s*:/u.test(blockText);
419+
420+
if (hasModuleKey && hasConfigKey) {
421+
const lastNonEmptyLine = [...fenceLines].reverse().find((fenceLine) => fenceLine.trim().length > 0) ?? "";
422+
objects.push({
423+
hasTrailingComma: /^\s*\},\s*(?:\/\/.*)?$/u.test(lastNonEmptyLine)
424+
});
425+
}
426+
}
427+
428+
inFence = false;
429+
fenceLanguage = "";
430+
fenceLines = [];
431+
fenceWasInConfigSection = false;
432+
continue;
433+
}
434+
435+
fenceLines.push(line);
377436
}
437+
438+
return objects;
378439
}
379440

380441
/**
@@ -513,8 +574,8 @@ export async function analyzeModule(
513574
}
514575

515576
// Check for config example
516-
const configRegex = /\{\s*[^}]*?\s*config:\s*\{\s*[^}]*\}(?:[,\s]\s*[^}]*?)}/;
517-
const hasConfigExample = searchRegexInFile(content, configRegex);
577+
const configObjects = findReadmeConfigObjects(content);
578+
const hasConfigExample = configObjects.length > 0;
518579

519580
if (!hasConfigExample) {
520581
const falsePositivesConfig = ["MMM-CalendarExt2"];
@@ -525,8 +586,7 @@ export async function analyzeModule(
525586
}
526587
} else {
527588
// Check for trailing comma
528-
const trailingCommaRegex = /\{\s*[^}]*?\s*config:\s*\{\s*[^}]*\}(?:[,\s]\s*[^}]*?)},/;
529-
const hasTrailingComma = searchRegexInFile(content, trailingCommaRegex);
589+
const hasTrailingComma = configObjects.some((object) => object.hasTrailingComma);
530590
const falsePositivesTrailing = ["MMM-MealieMenu", "MMM-Remote-Control"];
531591
if (!hasTrailingComma && !falsePositivesTrailing.includes(moduleName)) {
532592
issues.push(

0 commit comments

Comments
 (0)