Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strip comments before parsing JSON config files #10

Merged
merged 1 commit into from
Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,70 @@ $ pnpm jsdoc
../../../build/jsdoc/tomcat-servlet-testing-example-frontend/0.0.0/index.html
```

## Development

Uses [pnpm][] and [Vitest][] for building and testing.

### JSON with comments

You may want to configure your editor to recognize comments in JSON files, since
this project and [JSDoc][] both support them.

#### [Vim][]

Add to your `~/.vimrc`, based on advice from [Stack Overflow: Why does Vim
highlight all my JSON comments in red?][so-vim]:

```vim
" With a little help from:
" - https://stackoverflow.com/questions/55669954/why-does-vim-highlight-all-my-json-comments-in-red
autocmd FileType json syntax match Comment "//.*"
autocmd FileType json syn region jsonBlockComment start="/\*" end="\*/" fold
autocmd FileType json hi def link jsonBlockComment Comment
```

#### [Visual Studio Code][]

[VS Code supports JSON with Comments][vsc-jsonc]. Following the good advice from
[Stack Overflow: In VS Code, disable error "Comments are not permitted in
JSON"][so-vsc]:

##### Method 1, verbatim from <https://stackoverflow.com/a/47834826>

1. Click on the letters JSON in the bottom right corner. (A drop-down will
appear to "Select the Language Mode.")
2. Select "Configure File Association for '.json'..."
3. Type "jsonc" and press Enter.

##### Method 2, nearly verbatim from <https://stackoverflow.com/a/48773989>

Add this to your User Settings:

```json
"files.associations": {
"*.json": "jsonc"
},
```

If you don't already have a user settings file, you can create one. Hit
**&#8984;, or CTRL-,** (that's a comma) to open your settings, then hit
the Open Settings (JSON) button in the upper right. (It looks like a page with a
little curved arrow over it.)

- Or invoke the **[Preferences: Open User Settings (JSON)][vsc-settings]**
command.

#### [IntelliJ IDEA][]

You can effectively enable comments by [extending the JSON5 syntax to all JSON
files][idea-json5]:

1. In the Settings dialog (**&#8984;,** or **CTRL-,**), go to **Editor | File
Types**.
2. In the **Recognized File Types** list, select **JSON5**.
3. In the **File Name Patterns** area, click **&#65291; (Add)** and type `.json`
in the **Add Wildcard** dialog that opens.

## Background

I developed this while experimenting with JSDoc on
Expand All @@ -150,6 +214,15 @@ Node.js, JSDoc, and [npm packaging][] exercise as well.
[pnpm]: https://pnpm.io/
[mbland/tomcat-servlet-testing-example]: https://github.com/mbland/tomcat-servlet-testing-example
[Gradle]: https://gradle.org/
[Vitest]: https://vitest.dev/
[Vim]: https://www.vim.org/
[so-vim]: https://stackoverflow.com/questions/55669954/why-does-vim-highlight-all-my-json-comments-in-red
[Visual Studio Code]: https://code.visualstudio.com/
[vsc-jsonc]: https://code.visualstudio.com/Docs/languages/json#_json-with-comments
[so-vsc]: https://stackoverflow.com/questions/47834825/in-vs-code-disable-error-comments-are-not-permitted-in-json
[vsc-settings]: https://code.visualstudio.com/docs/getstarted/settings#_settingsjson
[IntelliJ IDEA]: https://www.jetbrains.com/idea/
[idea-json5]: https://www.jetbrains.com/help/idea/json.html#ws_json_choose_version_procedure
[Bash]: https://www.gnu.org/software/bash/
[Node.js]: https://nodejs.org/
[npm packaging]: https://docs.npmjs.com/packages-and-modules
86 changes: 80 additions & 6 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ export async function getPath(cmdName, env, platform) {

/**
* Analyzes JSDoc CLI args to determine if JSDoc will generate docs and where
*
* Expects any JSON config files specified via -c or --configure to be UTF-8
* encoded.
* @param {string[]} argv - JSDoc command line interface arguments
* @returns {Promise<ArgvResults>} analysis results
*/
Expand All @@ -108,22 +111,20 @@ export async function analyzeArgv(argv) {
for (let i = 0; i !== argv.length; ++i) {
const arg = argv[i]
const nextArg = argv[i+1]
let config = null

switch (arg) {
case '-c':
case '--configure':
if (!cmdLineDest && validArg(nextArg)) {
config = JSON.parse(await readFile(nextArg))
if (config.opts !== undefined) {
destination = config.opts.destination
}
const jsonSrc = await readFile(nextArg, {encoding: 'utf8'})
const config = JSON.parse(stripJsonComments(jsonSrc))
if (config.opts !== undefined) destination = config.opts.destination
}
break

case '-d':
case '--destination':
if (nextArg !== undefined && validArg(nextArg)) {
if (validArg(nextArg)) {
destination = nextArg
cmdLineDest = true
}
Expand All @@ -143,6 +144,79 @@ export async function analyzeArgv(argv) {
return {willGenerate, destination}
}

/**
* Replaces all comments and trailing commas in a JSON string with spaces
*
* Replaces rather than removes characters so that any JSON.parse() errors line
* up with the original. Preserves all existing whitespace as is, including
* newlines, carriage returns, and horizontal tabs.
*
* Details to be aware of:
*
* - Replaces trailing commas before the next ']' or '}' with a space.
* - "/* /" (without the space) is a complete block comment. (Since this
* documentation is in a block comment, the space is necessary here.)
* - If the next character after the end of a block comment ("* /" without the
* space) is:
* - '*': reopens the block comment
* - '/': opens a line comment
*
* If you really want to strip all the extra whitespace out:
*
* ```js
* JSON.stringify(JSON.parse(stripJsonComments(jsonStr)))
* ```
*
* If you want to reformat it to your liking, e.g., using two space indents:
*
* ```js
* JSON.stringify(JSON.parse(stripJsonComments(jsonStr)), null, 2)
* ```
*
* This function is necessary because the `jsdoc` command depends upon the
* extremely popular strip-json-comments npm. Otherwise analyzeArgs() would
* choke on config.json files containing comments.
*
* This implementation was inspired by strip-json-comments, but is a completely
* original implementation to avoid adding any dependencies. It may become its
* own separate package one day, likely scoped to avoid conflicts with
* strip-json-comments.
* @param {string} jsonStr - JSON text to strip
* @returns {string} jsonStr with comments, trailing commas replaced by space
*/
export function stripJsonComments(jsonStr) {
let inString = false
let escaped = false
let inComment = null
let result = ''

for (let i = 0; i !== jsonStr.length; ++i) {
const prevChar = i !== 0 ? jsonStr[i-1] : null
let curChar = jsonStr[i]

if (inString) {
inString = curChar !== '"' || escaped
escaped = curChar === '\\' && !escaped
} else if (inComment) {
if ((inComment === 'line' && curChar === '\n') ||
(inComment === 'block' && prevChar === '*' && curChar === '/')) {
inComment = null
}
if (curChar.trimStart() !== '') curChar = ' '
} else if (curChar === '"') {
inString = true
} else if (prevChar === '/') {
if (curChar === '/') inComment = 'line'
else if (curChar === '*') inComment = 'block'
if (inComment) curChar = ' ' // otherwise prevChar closed a block comment
} else if (curChar === '/') {
curChar = ' ' // opening a line or block comment
}
result += curChar
}
return result.replaceAll(/,(\s*)([\]}])/g, ' $1$2')
}

/**
* Searches for filename within a directory tree via breadth-first search
* @param {string} dirname - current directory to search
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/analyzeArgv/conf-bar.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{
"opts": {
"destination": "bar"
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/analyzeArgv/conf-foo.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{
"opts": {
"destination": "foo"
Expand Down
7 changes: 6 additions & 1 deletion test/fixtures/analyzeArgv/conf-undef-dest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{
"opts": {}
"opts": {} // This will override opts.destination, since opts is defined.
}
7 changes: 6 additions & 1 deletion test/fixtures/analyzeArgv/conf-undef-opts.json
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
{}
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{/* This won't override opts.destination, since opts is undefined. */}
Loading