Skip to content

Commit

Permalink
feat: Read options from configuration files
Browse files Browse the repository at this point in the history
  • Loading branch information
prantlf committed May 3, 2022
1 parent 83037b8 commit 7eebd76
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 78 deletions.
181 changes: 122 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ This is a fork of the original project ([zaach/jsonlint](https://github.com/zaac
* Supports [JSON Schema] drafts 04, 06 and 07.
* Offers pretty-printing including comment-stripping and object keys without quotes (JSON5).
* Prefers the native JSON parser if possible to run [7x faster than the custom parser].
* Reports errors with rich additional information. From the schema validation too.
* Implements JavaScript modules using [UMD] to work everywhere.
* Reports errors with rich additional information. From the JSON Schema validation too.
* Consumes configuration from both command line and [configuration files](configuration).
* Implements JavaScript modules using [UMD] to work in Node.js, in a browser, everywhere.
* Depends on up-to-date npm modules with no installation warnings.
* Small size - 18.2 kB minified, 6.3 kB gzipped.
* Small size - 18.7 kB minified, 6.54 kB gzipped, 5.16 kB brotlied.

**Note:** In comparison with the original project, this package exports only the `parse` method; not the `Parser` object.

Expand Down Expand Up @@ -52,9 +53,11 @@ Example of an error message:

## Command-line Interface

Install `jsonlint` with `npm`` globally to be able to use the command-line interface in any directory:
Install `jsonlint` with `npm`, `pnpm` or `yarn` globally to be able to use the command-line interface in any directory:

npm i @prantlf/jsonlint -g
npm i -g @prantlf/jsonlint
pnpm i -g @prantlf/jsonlint
yarn add --global @prantlf/jsonlint

Validate a single file:

Expand All @@ -64,9 +67,9 @@ or pipe the JSON input into `stdin`:

cat myfile.json | jsonlint

or process all `.json` files in a directory:
or process all `.json` files in a directory and rewriting them with the pretty-printed output:

jsonlint mydir
jsonlint --in-place --pretty-print mydir

By default, `jsonlint` will either report a syntax error with details or pretty-print the source if it is valid.

Expand All @@ -75,58 +78,118 @@ A more complex example: check all JSON files in a Node.js project, except for de
jsonlint --comments --trailing-commas --no-duplicate-keys \
--log-files --compact --continue '**/*.json' '!**/node_modules'

### Options

$ jsonlint -h

Usage: jsonlint [options] [<file, directory, pattern> ...]

JSON parser, syntax and schema validator and pretty-printer.

Options:
-s, --sort-keys sort object keys (not when prettifying)
-E, --extensions [ext] file extensions to process for directory walk
(default: ["json","JSON"])
-i, --in-place overwrite the input files
-t, --indent [num|char] number of spaces or specific characters
to use for indentation (default: 2)
-c, --compact compact error display
-M, --mode [mode] set other parsing flags according to a format
type (default: "json")
-C, --comments recognize and ignore JavaScript-style comments
-S, --single-quoted-strings support single quotes as string delimiters
-T, --trailing-commas ignore trailing commas in objects and arrays
-D, --no-duplicate-keys report duplicate object keys as an error
-V, --validate [file] JSON schema file to use for validation
-e, --environment [env] which specification of JSON Schema the
validation file uses
-l, --log-files print only the parsed file names to stdout
-q, --quiet do not print the parsed json to stdout
-n, --continue continue with other files if an error occurs
-p, --pretty-print prettify the input instead of stringifying
the parsed object
-P, --pretty-print-invalid force pretty-printing even for invalid input
-r, --trailing-newline ensure a line break at the end of the output
-R, --no-trailing-newline ensure no line break at the end of the output
--prune-comments omit comments from the prettified output
--strip-object-keys strip quotes from object keys if possible
(JSON5)
--enforce-double-quotes surrounds all strings with double quotes
--enforce-single-quotes surrounds all strings with single quotes
(JSON5)
--trim-trailing-commas omit trailing commas from objects and arrays
(JSON5)
-v, --version output the version number
-h, --help output usage information

You can use BASH patterns for including and excluding files (only files).
Patterns are case-sensitive and have to use slashes as a path separators.
A pattern to exclude from processing starts with "!".

Parsing mode can be "cjson" or "json5" to enable other flags automatically.
If no files or directories are specified, stdin will be parsed. Environments
for JSON schema validation are "json-schema-draft-04", "json-schema-draft-06"
or "json-schema-draft-07". If not specified, it will be auto-detected.
The same parameters can be passed from a configuration file:

```json
{
"comments": true,
"trailing-commas": true,
"duplicate-keys": false,
"log-files": true,
"compact": true,
"continue": true,
"patterns": ["**/*.json", "!**/node_modules"]
}
```

### Usage

Usage: `jsonlint [options] [<file, directory, pattern> ...]`

#### Options

-f, --config [file] read options from a custom configuration file
-F, --no-config disable searching for configuration file
-s, --sort-keys sort object keys (not when prettifying)
-E, --extensions [ext] file extensions to process for directory walk
(default: ["json","JSON"])
-i, --in-place overwrite the input files
-t, --indent [num|char] number of spaces or specific characters
to use for indentation (default: 2)
-c, --compact compact error display
-M, --mode [mode] set other parsing flags according to a format
type (default: "json")
-C, --comments recognize and ignore JavaScript-style comments
-S, --single-quoted-strings support single quotes as string delimiters
-T, --trailing-commas ignore trailing commas in objects and arrays
-D, --no-duplicate-keys report duplicate object keys as an error
-V, --validate [file] JSON schema file to use for validation
-e, --environment [env] which specification of JSON Schema the
validation file uses
-l, --log-files print only the parsed file names to stdout
-q, --quiet do not print the parsed json to stdout
-n, --continue continue with other files if an error occurs
-p, --pretty-print prettify the input instead of stringifying
the parsed object
-P, --pretty-print-invalid force pretty-printing even for invalid input
-r, --trailing-newline ensure a line break at the end of the output
-R, --no-trailing-newline ensure no line break at the end of the output
--prune-comments omit comments from the prettified output
--strip-object-keys strip quotes from object keys if possible
(JSON5)
--enforce-double-quotes surrounds all strings with double quotes
--enforce-single-quotes surrounds all strings with single quotes
(JSON5)
--trim-trailing-commas omit trailing commas from objects and arrays
(JSON5)
-v, --version output the version number
-h, --help output usage information

You can use BASH patterns for including and excluding files (only files).
Patterns are case-sensitive and have to use slashes as a path separators.
A pattern to exclude from processing starts with "!".

Parsing mode can be "cjson" or "json5" to enable other flags automatically.
If no files or directories are specified, stdin will be parsed. Environments
for JSON schema validation are "json-schema-draft-04", "json-schema-draft-06"
or "json-schema-draft-07". If not specified, it will be auto-detected.

### Configuration

In addition to the command line parameters, the options can be supplied from the following files:

package.json, key jsonlint
.jsonlintrc
.jsonlintrc.json
.jsonlintrc.yaml
.jsonlintrc.yml
.jsonlintrc.js
.jsonlintrc.cjs
jsonlint.config.js
jsonlint.config.cjs

The automatic search for one of the following locations above can be disabled by the command-line parameter `-F|--no-config`. A concrete configuration file can be specified by the command-line parameter `-f|--config [file]`. Parameters from the command line will have higher priority than parameters from a configuration file.

The configuration is an object with the following properties, described above, which can be entered either in the kebab-case or in the camel-case:

| Parameter | Alias |
| --------- | ----- |
| patterns | |
| sort-keys | sortKeys |
| extensions | |
| in-place | inPlace |
| indent | |
| compact | |
| mode | |
| comments | |
| single-quoted-strings | singleQuotedStrings |
| trailing-commas | trailingCommas |
| duplicate-keys | duplicateKeys |
| validate | |
| environment | |
| log-files | logFiles |
| quiet | |
| continue | |
| pretty-print | prettyPrint |
| pretty-print-invalid | prettyPrintInvalid |
| trailing-newline | trailingNewline'
| prune-comments | pruneComments |
| strip-object-keys | stripObjectKeys |
| enforce-double-quotes | enforceDoubleQuotes |
| enforce-single-quotes | enforceSingleQuotes |
| trim-trailing-commas | trimTrailingCommas |

The parameter `config` will be ignored in configuration files. The extra parameter `patterns` can be set to an array of strings with paths or patterns instead of putting them to the command line.

## Module Interface

Expand Down
57 changes: 55 additions & 2 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const { readdirSync, readFileSync, statSync, writeFileSync } = require('fs')
const { extname, join, normalize } = require('path')
const { isDynamicPattern, sync } = require('fast-glob')
const { cosmiconfigSync } = require('cosmiconfig')
const { parse, tokenize } = require('./jsonlint')
const { format } = require('./formatter')
const { print } = require('./printer')
Expand All @@ -16,6 +17,8 @@ const commander = require('commander')
.name('jsonlint')
.usage('[options] [<file, directory, pattern> ...]')
.description(description)
.option('-f, --config [file]', 'read options from a custom configuration file')
.option('-F, --no-config', 'disable searching for configuration files')
.option('-s, --sort-keys', 'sort object keys (not when prettifying)')
.option('-E, --extensions [ext]', 'file extensions to process for directory walk', collectValues, ['json', 'JSON'])
.option('-i, --in-place', 'overwrite the input files')
Expand Down Expand Up @@ -54,9 +57,56 @@ const commander = require('commander')
})
.parse(process.argv)

const options = commander.opts()
const paramNames = {
'trailing-commas': 'trailingCommas',
'single-quoted-strings': 'singleQuotedStrings',
'duplicate-keys': 'duplicateKeys',
'pretty-print': 'prettyPrint',
'prune-comments': 'pruneComments',
'strip-object-keys': 'stripObjectKeys',
'enforce-double-quotes': 'enforceDoubleQuotes',
'enforce-single-quotes': 'enforceSingleQuotes',
'trim-trailing-commas': 'trimTrailingCommas',
'sort-keys': 'sortKeys',
'pretty-print-invalid': 'prettyPrintInvalid',
'log-files': 'logFiles',
'in-place': 'inPlace',
'trailing-newline': 'trailingNewline'
}

const params = commander.opts()
let options
if (params.config === false) {
options = params
} else {
const configurator = cosmiconfigSync('jsonlint')
const { config = {} } = (params.config && configurator.load(params.config)) ||
configurator.search() || {}
options = mergeOptions({}, convertConfig(config), params)
}

const extensions = options.extensions.map(extension => '.' + extension)

function convertConfig (config) {
const result = {}
for (const key in config) {
const name = paramNames[key] || key
result[name] = config[key]
}
return result
}

function mergeOptions (target, ...sources) {
for (const source of sources) {
for (const key in source) {
if (target[key] == null) {
target[key] = source[key]
}
}
}
return target
}

function logNormalError (error, file) {
if (process.exitCode > 0) {
console.log()
Expand Down Expand Up @@ -207,7 +257,10 @@ function processPatterns (patterns) {
}

function main () {
const { args: files } = commander
let { args: files } = commander
if (!files.length) {
files = options.patterns || []
}
if (files.length) {
const dynamic = files.some(file => isDynamicPattern(file))
if (dynamic) {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"build": "npm run compile && npm run compile:tests",
"compile": "node scripts/bundle-jsonlint && terser -o web/jsonlint.min.js --source-map \"filename='jsonlint.js',url='jsonlint.min.js.map',includeSources=true\" lib/jsonlint.js && terser -o web/validator.min.js --source-map \"filename='validator.js',url='validator.min.js.map',includeSources=true\" lib/validator.js && terser -o web/formatter.min.js --source-map \"filename='formatter.js',url='formatter.min.js.map',includeSources=true\" lib/formatter.js && terser -o web/sorter.min.js --source-map \"filename='sorter.js',url='sorter.min.js.map',includeSources=true\" lib/sorter.js && terser -o web/printer.min.js --source-map \"filename='printer.js',url='printer.min.js.map',includeSources=true\" lib/printer.js && node scripts/bundle-schema-drafts && terser -o web/schema-drafts.min.js --source-map \"filename='schema-drafts.js',url='schema-drafts.min.js.map',includeSources=true\" lib/schema-drafts.js && terser -o web/ajv.min.js --source-map \"filename='ajv.js',url='ajv.min.js.map',includeSources=true\" node_modules/ajv/dist/ajv.bundle.js",
"compile:tests": "tsc --lib es6 test/typings.test.ts",
"test": "nyc --silent node test/typings.test.js && nyc --silent --no-clean node test/parse1 && nyc --silent --no-clean node test/parse1 --native-parser && nyc --silent --no-clean node test/parse2 && nyc --silent --no-clean node test/parse3 && nyc --silent --no-clean node test/parse4 && nyc --silent --no-clean node test/parse5 && nyc --silent --no-clean node test/portable && nyc --silent --no-clean node test/tokenize && nyc --silent --no-clean node test/print && nyc --silent --no-clean node lib/cli package.json test/recursive && nyc --silent --no-clean node lib/cli -sq test/passes/hasOwnProperty.json && nyc --silent --no-clean node lib/cli -s -e json-schema-draft-04 -V test/passes/3.schema.json test/passes/3.json && nyc --silent --no-clean node lib/cli -C test/passes/comments.txt && nyc --silent --no-clean node lib/cli -pS test/passes/strings.txt && nyc --silent --no-clean node lib/cli -M json5 test/passes/json5.text && nyc --silent --no-clean node lib/cli -v && nyc --silent --no-clean node lib/cli -h && nyc --silent --no-clean node lib/cli -Pc test/fails/10.json || nyc --silent --no-clean node lib/cli -l 'test/**/*.json' '!**/fails' && nyc report",
"test": "nyc --silent node test/typings.test.js && nyc --silent --no-clean node test/parse1 && nyc --silent --no-clean node test/parse1 --native-parser && nyc --silent --no-clean node test/parse2 && nyc --silent --no-clean node test/parse3 && nyc --silent --no-clean node test/parse4 && nyc --silent --no-clean node test/parse5 && nyc --silent --no-clean node test/portable && nyc --silent --no-clean node test/tokenize && nyc --silent --no-clean node test/print && nyc --silent --no-clean node lib/cli package.json test/recursive && nyc --silent --no-clean node lib/cli -sq test/passes/hasOwnProperty.json && nyc --silent --no-clean node lib/cli -s -e json-schema-draft-04 -V test/passes/3.schema.json test/passes/3.json && nyc --silent --no-clean node lib/cli -C test/passes/comments.txt && nyc --silent --no-clean node lib/cli -pS test/passes/strings.txt && nyc --silent --no-clean node lib/cli -M json5 test/passes/json5.text && nyc --silent --no-clean node lib/cli -v && nyc --silent --no-clean node lib/cli -h && nyc --silent --no-clean node lib/cli -Pc test/fails/10.json || nyc --silent --no-clean node lib/cli -f test/.jsonrc.yml 'test/**/*.json' '!**/fails' && nyc report",
"start": "http-server -c 5",
"web": "npm run web:sync && npm run web:deploy",
"web:clone": "test ! -d ../jsonlint-pages && git clone --single-branch --branch gh-pages `git remote get-url origin` ../jsonlint-pages",
Expand Down Expand Up @@ -77,6 +77,7 @@
"dependencies": {
"ajv": "6.12.6",
"commander": "9.2.0",
"cosmiconfig": "^7.0.1",
"fast-glob": "3.2.11"
},
"devDependencies": {
Expand Down
Loading

1 comment on commit 7eebd76

@prantlf
Copy link
Owner Author

@prantlf prantlf commented on 7eebd76 Mar 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes #9.

Please sign in to comment.