Pre-configured formatting and lint tool combining the best of prettier and eslint. Aka, fandl.
npm i @pkgdev/format-and-lint
npx fandl lint # runs lint checks only with no changes to files
npx fandl # fixes what it can and reports on the rest
npx fandl --files '**/weird-src/**/*.{js,mjs,cjs,jsx}' # specify files pattern
import { formatAndLint } from '@pkgdev/format-and-lint'
// in the API, we provide actual file paths, which may be relative or absolute
const files = ['index.js', 'src/foo.js', 'src/bar.js']
const { eslint, lintResults } = formatAndLint({ files })
// process the results; the following is essentially what the fandl CLI does
const formatter = await eslint.loadFormatter('stylish')
const resultText = formatter.format(lintResults)
stdout.write(resultText)
// if we had something to say, then that indicates an error/warning in the source
if (resultText !== '') {
process.exit(1)
}
API generated with dmd-readme-api.
formatAndLint()
: Parses, lints, and (whencheck
is false) reformats thefiles
text.linebreakTypesExcept()
: A helper function used to sanely build 'blankline' entries in the '@stylistic/padding-line-between-statements' rule.
formatAndLint(options)
⇒ Promise.<{eslint: object, lintResults: Array.<object>}>
↱source code ⇧global index
Parses, lints, and (when check
is false) reformats the files
text. By default, this function will update the
files
in-place.
Param | Type | Default | Description |
---|---|---|---|
options |
object |
The input options. | |
[options.check ] |
boolean |
false |
If true then the files are linted, but not reformatted. |
[options.noWrite ] |
boolean |
false |
If true , then the files are not updated in placed. Has no effect when check = false , but when combined with check = true , means that the text is reformatted and attached to the LintResult s, but the files themselves are not updated. You can access reformatted text as part of the result.lintResults[0].output . Unlike results directly from ESLint , output is always present on the LintResult object (rather than only being set if the text is changed. |
[options.eslintConfig ] |
object |
<default eslint config> |
A flat (9.x) style array of eslint configuration object to be used in place of the default, out of the box configuration. This may not be specified along with eslintConfigComponents . |
[options.eslintConfigComponents ] |
object |
An object with zero or more keys corresponding to the base , jsdoc , jsx , test , or additional as discussed in the component based configuration. This may not be specified along with eslintConfig . |
|
[options.prettierConfig ] |
object |
<default prettier config> |
A prettier options object. |
[options.eslint ] |
object |
A pre-configured ESLint instance. If this is defined, then eslintConfig and eslintConfigComponents will be ignored. |
|
[options.outputDir ] |
string |
If provided, then output files (whether reformatted or not) will be written to the specified directory relative to their location in the source. With src/index.mjs => <outputDir>/src/index.mjs , src/foo/bar.mjs => <outputDir>/src/foo/bar.mjs . This option has no effect if check = true or noWrite = true . The relative starting point is controlled with the relativeStem option. |
|
[options.relativeStem ] |
string |
process.cwd() | Controls the starting point for determining the relative position of files when emitting to outputDir rather than updating in place. Impossible stems will result in an error. E.g., given file src/index.mjs , relativeStem = 'src/foo' is invalid. |
Returns: Promise.<{eslint: object, lintResults: Array.<object>}>
- Resolves to an object with two fields. eslint
points
to the an instance of ESLint
. lintResults
points to an array of LintResult
s.
linebreakTypesExcept(...types)
⇒ Array.<string>
↱source code ⇧global index
A helper function used to sanely build 'blankline' entries in the '@stylistic/padding-line-between-statements' rule. Basically, what we often want is to say "we want a blank line between expression type A and all other expression except for B, C, and D." This is useful because the '@stylistic/padding-line-between-statements' rule requires you specify each type where a blank line is required, but it's generally easier to specify a set of expression types for which a blank line is NOT required.E.g.:
'@stylistic/padding-line-between-statements' : [
'error',
{ blankLine : 'always', prev : '*', next : 'class' },
{
blankLine : 'always',
prev : linebreakTypesExcept('cjs-export', 'export'),
next : 'export',
},
]
Would require (and/or add) a blank line between a class declaration and anything else, and a blank line between
import
statements and all other statements except import
or cjs-import
statements. That way, all your import
statements would be grouped together, but would have a blank line between the last import
and whatever the next
non-import statement is.
Param | Type | Description |
---|---|---|
...types |
string |
A list of the types to exclude from the rule (meaning all other known types are included). |
Returns: Array.<string>
- - An array of the non-excluded types.
Fandl breaks up the configuration into 5 components:
- 'base' which applies to all Javascript src files,
- 'jsdoc' which defines JSDoc specific configuration and rules for all src files,
- 'jsx' which defines additional configuration and rules for JSX files,
- 'test' which defines additional configuration and rules for test files, and
- 'additional' which is just a catch all for whatever else you might want to add.
Rather than being forced to redefine the entire default configuration, you can override any one of the components individually by specifying options.eslintConfigComponents
.
Note, the component structure is essentially a prototype at this point. Future versions will:
- Break up 'base' (which is very large) into different semantic types such "correctness", "complexity", and "style".
- Support arbitrary additional configuration components.
- Support turning off individual configuration components.
- The code is run through prettier first mainly because it does a much better job properly indenting code and fitting it within a target width (80 chars).1
- The partially reformatted code is then reformatted by eslint because prettier (purposely) has very few options and we don't agree with all of them. Specifically, our out of the box configuration:
- places operators at the beginning of the next line rather than the end of the previous line in multi-line expressions; e.g.:
const foo = bar // fandl style; generally accepted as easier to read && baz // vs const foo = bar && // prettier style baz
- places
else if
/else
/catch
, etc. on a newline; e.g.:
if (a === 1) { // fandl style (Stroustrup); more consistent and allows for end comments ... } // here we can say something about what the preceding block did else { // and here we can say something about what this block will do } // vs if (a === 1) { // prettier style ("one true brace style") ... } /* I gess you could do this */ else { // but IDK, just looks weird }
- aligns the colons in object declarations; e.g.:
{ // fandl style; easier to read, like a table foo : 'hey', longFieldName : 'how are you?', } // vs { // prettier style foo : 'hey', longFieldName : 'how are you?', }
- The dual-formatting is also compatible with the eslint configuration, so the code final format will pass the eslint based lint checking.
Footnotes
-
I perhaps falsely remember eslint actually doing a better re indenting code, but in any case there are two issue with the latest eslint based reformatting. First, it miscounts the correct indention level where '('s were involved in boolean expressions. Second, eslint failed automatically break up long lines. (As of @stylistic/eslint-plugin: 2.6.4, eslint: 8.50.0; have since upgraded but not retested since it's working as is.) ↩