Skip to content

Commit

Permalink
Switch from strip-json-comments to jsonc-parser for better handling o…
Browse files Browse the repository at this point in the history
…f JSONC (e.g., trailing commas) (fixes #267).
  • Loading branch information
DavidAnson committed Jan 14, 2024
1 parent 551e6ba commit 24b02c7
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 43 deletions.
50 changes: 22 additions & 28 deletions markdownlint-cli2.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ const utf8 = "utf8";
// No-op function
const noop = () => null;

// Gets a synchronous function to parse JSONC text
const getJsoncParse = async () => {
const { "default": stripJsonComments } =
// eslint-disable-next-line no-inline-comments
await import(/* webpackMode: "eager" */ "strip-json-comments");
return (text) => JSON.parse(stripJsonComments(text));
// Synchronous function to parse JSONC text
const jsoncParse = (text) => {
const { parse, printParseErrorCode } = require("jsonc-parser");
const errors = [];
const result = parse(text, errors, { "allowTrailingComma": true });
if (errors.length > 0) {
const aggregate = errors.map(
(err) => `${printParseErrorCode(err.error)} (offset ${err.offset}, length ${err.length})`
).join(", ");
throw new Error(`Unable to parse JSON(C) content, ${aggregate}`);
}
return result;
};

// Synchronous function to parse YAML text
Expand All @@ -67,12 +73,10 @@ const readConfig = (fs, dir, name, otherwise) => {
const file = pathPosix.join(dir, name);
return () => fs.promises.access(file).
then(
() => getJsoncParse().then(
(jsoncParse) => markdownlintReadConfig(
file,
[ jsoncParse, yamlParse ],
fs
)
() => markdownlintReadConfig(
file,
[ jsoncParse, yamlParse ],
fs
),
otherwise
);
Expand Down Expand Up @@ -139,9 +143,8 @@ const importOrRequireConfig = (fs, dir, name, noRequire, otherwise) => {
};

// Extend a config object if it has 'extends' property
const getExtendedConfig = async (config, configPath, fs) => {
const getExtendedConfig = (config, configPath, fs) => {
if (config.extends) {
const jsoncParse = await getJsoncParse();
return markdownlintExtendConfig(
config,
configPath,
Expand All @@ -150,7 +153,7 @@ const getExtendedConfig = async (config, configPath, fs) => {
);
}

return config;
return Promise.resolve(config);
};

// Read an options or config file in any format and return the object
Expand All @@ -160,7 +163,6 @@ const readOptionsOrConfig = async (configPath, fs, noRequire) => {
let options = null;
let config = null;
if (basename.endsWith(".markdownlint-cli2.jsonc")) {
const jsoncParse = await getJsoncParse();
options = jsoncParse(await fs.promises.readFile(configPath, utf8));
} else if (basename.endsWith(".markdownlint-cli2.yaml")) {
options = yamlParse(await fs.promises.readFile(configPath, utf8));
Expand All @@ -177,7 +179,6 @@ const readOptionsOrConfig = async (configPath, fs, noRequire) => {
basename.endsWith(".markdownlint.yaml") ||
basename.endsWith(".markdownlint.yml")
) {
const jsoncParse = await getJsoncParse();
config =
await markdownlintReadConfig(configPath, [ jsoncParse, yamlParse ], fs);
} else if (
Expand Down Expand Up @@ -319,10 +320,7 @@ const getAndProcessDirInfo = (
then(
() => fs.promises.
readFile(markdownlintCli2Jsonc, utf8).
then(
(content) => getJsoncParse().
then((jsoncParse) => jsoncParse(content))
),
then(jsoncParse),
() => fs.promises.access(markdownlintCli2Yaml).
then(
() => fs.promises.
Expand All @@ -346,11 +344,8 @@ const getAndProcessDirInfo = (
then(
() => fs.promises.
readFile(packageJson, utf8).
then(
(content) => getJsoncParse().
then((jsoncParse) => jsoncParse(content)).
then((obj) => obj[packageName])
),
then(jsoncParse).
then((obj) => obj[packageName]),
noop
)
)
Expand Down Expand Up @@ -743,8 +738,7 @@ const createDirInfos = async (
};

// Lint files in groups by shared configuration
const lintFiles = async (fs, dirInfos, fileContents) => {
const jsoncParse = await getJsoncParse();
const lintFiles = (fs, dirInfos, fileContents) => {
const tasks = [];
// For each dirInfo
for (const dirInfo of dirInfos) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@
],
"dependencies": {
"globby": "14.0.0",
"jsonc-parser": "3.2.0",
"markdownlint": "0.33.0",
"markdownlint-cli2-formatter-default": "0.0.4",
"micromatch": "4.0.5",
"strip-json-comments": "5.0.1",
"yaml": "2.3.4"
},
"devDependencies": {
Expand Down
7 changes: 7 additions & 0 deletions test/jsonc-trailing-comma/.markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
// Comment
"config": {
"MD032": false,
"no-multiple-blanks": false,
}
}
3 changes: 3 additions & 0 deletions test/jsonc-trailing-comma/dir/.markdownlint.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"first-line-heading": false,
}
3 changes: 3 additions & 0 deletions test/jsonc-trailing-comma/dir/info.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Information
Text ` code1` text `code2 ` text

14 changes: 14 additions & 0 deletions test/jsonc-trailing-comma/viewme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Title

> Tagline

# Description

Text text text
Text text text
Text text text

## Summary

Text text text
19 changes: 12 additions & 7 deletions test/markdownlint-cli2-test-cases.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ const testCases = ({
"name": "markdownlint-json-invalid",
"args": [ ".*" ],
"exitCode": 2,
"stderrRe": /(?:Unexpected end)|(?:Expected property name)/u
"stderrRe": /Unable to parse JSON\(C\) content/u
});

testCase({
Expand Down Expand Up @@ -388,7 +388,7 @@ const testCases = ({
"name": "markdownlint-cli2-jsonc-mismatch",
"args": [ "viewme.md" ],
"exitCode": 2,
"stderrRe": /Unexpected token/u
"stderrRe": /Unable to parse JSON\(C\) content/u
});

testCase({
Expand All @@ -415,7 +415,7 @@ const testCases = ({
"name": "markdownlint-cli2-jsonc-mismatch-config",
"args": [ "--config", "../markdownlint-cli2-jsonc-mismatch/.markdownlint-cli2.jsonc", "viewme.md" ],
"exitCode": 2,
"stderrRe": /Unexpected token/u,
"stderrRe": /Unable to parse JSON\(C\) content/u,
"cwd": "no-config",
});

Expand Down Expand Up @@ -446,7 +446,7 @@ const testCases = ({
"name": "markdownlint-cli2-jsonc-invalid",
"args": [ ".*" ],
"exitCode": 2,
"stderrRe": /(?:Unexpected end)|(?:Expected property name)/u
"stderrRe": /Unable to parse JSON\(C\) content/u
});

testCase({
Expand Down Expand Up @@ -665,8 +665,7 @@ const testCases = ({
});
}

const unexpectedJsonRe =
/(?:Unexpected end of JSON input)|(?:Expected property name)/u;
const unexpectedJsonRe = /Unable to parse JSON\(C\) content/u;
const unableToRequireRe = /Unable to require or import module/u;
const unableToParseRe = /Unable to parse/u;
const invalidConfigFiles = [
Expand Down Expand Up @@ -775,7 +774,7 @@ const testCases = ({
"name": "package-json-invalid",
"args": [ "**/*.md" ],
"exitCode": 2,
"stderrRe": /(?:Unexpected end)|(?:Expected property name)/u
"stderrRe": /Unable to parse JSON\(C\) content/u
});

testCase({
Expand Down Expand Up @@ -1134,6 +1133,12 @@ const testCases = ({
"usesRequire": true
});

testCase({
"name": "jsonc-trailing-comma",
"args": [ "**/*.md" ],
"exitCode": 1
});

};

module.exports = testCases;
12 changes: 5 additions & 7 deletions test/markdownlint-cli2-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const fs = require("node:fs/promises");
const path = require("node:path");
const Ajv = require("ajv");
const test = require("ava").default;
const jsoncParser = require("jsonc-parser");
const { "main": markdownlintCli2 } = require("../markdownlint-cli2.js");
const FsMock = require("./fs-mock");

Expand Down Expand Up @@ -63,7 +64,7 @@ test("README files", (t) => {
});

test("validateMarkdownlintConfigSchema", async (t) => {
t.plan(25);
t.plan(26);

// Validate schema
// @ts-ignore
Expand All @@ -81,8 +82,6 @@ test("validateMarkdownlintConfigSchema", async (t) => {
);

// Validate instances
// @ts-ignore
const { "default": stripJsonComments } = await import("strip-json-comments");
const { globby } = await import("globby");
const files = await globby(
[
Expand All @@ -99,7 +98,7 @@ test("validateMarkdownlintConfigSchema", async (t) => {
);
return Promise.all(files.map(async (file) => {
const content = await fs.readFile(file, "utf8");
const json = JSON.parse(stripJsonComments(content));
const json = jsoncParser.parse(content);
const instanceResult = validateConfigSchema(json);
t.truthy(
instanceResult,
Expand All @@ -109,7 +108,7 @@ test("validateMarkdownlintConfigSchema", async (t) => {
});

test("validateMarkdownlintCli2ConfigSchema", async (t) => {
t.plan(87);
t.plan(88);

// Validate schema
// @ts-ignore
Expand All @@ -129,7 +128,6 @@ test("validateMarkdownlintCli2ConfigSchema", async (t) => {
);

// Validate instances
const { "default": stripJsonComments } = await import("strip-json-comments");
const { globby } = await import("globby");
const files = await globby(
[
Expand All @@ -147,7 +145,7 @@ test("validateMarkdownlintCli2ConfigSchema", async (t) => {
);
return Promise.all(files.map(async (file) => {
const content = await fs.readFile(file, "utf8");
const json = JSON.parse(stripJsonComments(content));
const json = jsoncParser.parse(content);
const instanceResult = validateConfigSchema(json);
t.truthy(
instanceResult,
Expand Down
26 changes: 26 additions & 0 deletions test/snapshots/markdownlint-cli2-test-exec.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -6350,3 +6350,29 @@ Generated by [AVA](https://avajs.dev).
Summary: 14 error(s)␊
`,
}

## jsonc-trailing-comma (exec)

> Snapshot 1
{
exitCode: 1,
formatterCodeQuality: '',
formatterJson: '',
formatterJunit: '',
formatterSarif: '',
stderr: `dir/info.md:1 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## Information"]␊
dir/info.md:2:6 MD038/no-space-in-code Spaces inside code span elements [Context: "\` code1\`"]␊
dir/info.md:2:20 MD038/no-space-in-code Spaces inside code span elements [Context: "\`code2 \`"]␊
dir/info.md:4 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2]␊
viewme.md:3:10 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1]␊
viewme.md:6 MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "# Description"]␊
viewme.md:12:1 MD019/no-multiple-space-atx Multiple spaces after hash on atx style heading [Context: "## Summary"]␊
viewme.md:14:14 MD047/single-trailing-newline Files should end with a single newline character␊
`,
stdout: `markdownlint-cli2 vX.Y.Z (markdownlint vX.Y.Z)␊
Finding: **/*.md␊
Linting: 2 file(s)␊
Summary: 8 error(s)␊
`,
}
Binary file modified test/snapshots/markdownlint-cli2-test-exec.js.snap
Binary file not shown.
26 changes: 26 additions & 0 deletions test/snapshots/markdownlint-cli2-test-fs.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -2473,3 +2473,29 @@ Generated by [AVA](https://avajs.dev).
Summary: 2 error(s)␊
`,
}

## jsonc-trailing-comma (fs)

> Snapshot 1
{
exitCode: 1,
formatterCodeQuality: '',
formatterJson: '',
formatterJunit: '',
formatterSarif: '',
stderr: `dir/info.md:1 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## Information"]␊
dir/info.md:2:6 MD038/no-space-in-code Spaces inside code span elements [Context: "\` code1\`"]␊
dir/info.md:2:20 MD038/no-space-in-code Spaces inside code span elements [Context: "\`code2 \`"]␊
dir/info.md:4 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2]␊
viewme.md:3:10 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1]␊
viewme.md:6 MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "# Description"]␊
viewme.md:12:1 MD019/no-multiple-space-atx Multiple spaces after hash on atx style heading [Context: "## Summary"]␊
viewme.md:14:14 MD047/single-trailing-newline Files should end with a single newline character␊
`,
stdout: `markdownlint-cli2 vX.Y.Z (markdownlint vX.Y.Z)␊
Finding: **/*.md␊
Linting: 2 file(s)␊
Summary: 8 error(s)␊
`,
}
Binary file modified test/snapshots/markdownlint-cli2-test-fs.js.snap
Binary file not shown.
26 changes: 26 additions & 0 deletions test/snapshots/markdownlint-cli2-test-main.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -5580,3 +5580,29 @@ Generated by [AVA](https://avajs.dev).
Summary: 14 error(s)␊
`,
}

## jsonc-trailing-comma (main)

> Snapshot 1
{
exitCode: 1,
formatterCodeQuality: '',
formatterJson: '',
formatterJunit: '',
formatterSarif: '',
stderr: `dir/info.md:1 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## Information"]␊
dir/info.md:2:6 MD038/no-space-in-code Spaces inside code span elements [Context: "\` code1\`"]␊
dir/info.md:2:20 MD038/no-space-in-code Spaces inside code span elements [Context: "\`code2 \`"]␊
dir/info.md:4 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2]␊
viewme.md:3:10 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1]␊
viewme.md:6 MD025/single-title/single-h1 Multiple top-level headings in the same document [Context: "# Description"]␊
viewme.md:12:1 MD019/no-multiple-space-atx Multiple spaces after hash on atx style heading [Context: "## Summary"]␊
viewme.md:14:14 MD047/single-trailing-newline Files should end with a single newline character␊
`,
stdout: `markdownlint-cli2 vX.Y.Z (markdownlint vX.Y.Z)␊
Finding: **/*.md␊
Linting: 2 file(s)␊
Summary: 8 error(s)␊
`,
}
Binary file modified test/snapshots/markdownlint-cli2-test-main.js.snap
Binary file not shown.

0 comments on commit 24b02c7

Please sign in to comment.