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

feat: use cosmiconfig #830

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ CHANGELOG.md

# Report created by test
report.html

# malformed test files
test/config/bad.json
74 changes: 39 additions & 35 deletions dist/cli/htmlhint.js

Large diffs are not rendered by default.

92 changes: 23 additions & 69 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@
"async": "3.2.2",
"chalk": "^4.1.2",
"commander": "^8.3.0",
"cosmiconfig": "^7.0.1",
"glob": "^7.2.0",
"node-fetch": "^2.6.2",
"parse-glob": "3.0.4",
"strip-json-comments": "3.1.0",
"xml": "1.0.1"
},
"devDependencies": {
Expand Down
90 changes: 40 additions & 50 deletions src/cli/htmlhint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import { queue as asyncQueue, series as asyncSeries } from 'async'
import * as chalk from 'chalk'
import { Command } from 'commander'
import { cosmiconfigSync } from 'cosmiconfig'
import { existsSync, readFileSync, statSync } from 'fs'
import * as glob from 'glob'
import { IGlob } from 'glob'
import * as parseGlob from 'parse-glob'
import { dirname, resolve, sep } from 'path'
import { resolve, sep } from 'path'
import fetch from 'node-fetch'
import * as stripJsonComments from 'strip-json-comments'
import type { HTMLHint as IHTMLHint } from '../core/core'
import type { Hint, Ruleset } from '../core/types'
import { Formatter } from './formatter'
Expand Down Expand Up @@ -248,7 +248,44 @@ function hintAllFiles(
// init ruleset
let ruleset = options.ruleset
if (ruleset === undefined) {
ruleset = getConfig(cliOptions.config, globInfo.base, formatter)
let result
const explorerSync = cosmiconfigSync('htmlhint')
explorerSync.clearCaches() //debugging

try {
let configPath = cliOptions.config

// load config if directly passed
if (configPath) {
result = explorerSync.load(configPath)

if (result == null) {
console.error(`Could not resolve config file at ${configPath}`)
process.exit(2)
} else if (result.isEmpty) {
console.error('Config file is empty')
process.exit(2)
}
ruleset = result.config as Ruleset
} else {
result = explorerSync.search(globInfo.base)
if (result == null || result.isEmpty) {
ruleset = {} as Ruleset
configPath = ''
} else {
ruleset = result.config as Ruleset
configPath = result.filepath
}
}

formatter.emit('config', {
ruleset: ruleset,
configPath: configPath,
})
} catch (err) {
console.log(err)
process.exit(2)
}
}

// hint queue
Expand Down Expand Up @@ -367,53 +404,6 @@ function getGlobInfo(target: string): {
}
}

// search and load config
function getConfig(
configPath: string | undefined,
base: string,
formatter: Formatter
) {
if (configPath === undefined && existsSync(base)) {
// find default config file in parent directory
if (statSync(base).isDirectory() === false) {
base = dirname(base)
}

while (base) {
const tmpConfigFile = resolve(base, '.htmlhintrc')

if (existsSync(tmpConfigFile)) {
configPath = tmpConfigFile
break
}

if (!base) {
break
}

base = base.substring(0, base.lastIndexOf(sep))
}
}

// TODO: can configPath be undefined here?
if (configPath !== undefined && existsSync(configPath)) {
const config = readFileSync(configPath, 'utf-8')
let ruleset: Ruleset = {}

try {
ruleset = JSON.parse(stripJsonComments(config))
formatter.emit('config', {
ruleset: ruleset,
configPath: configPath,
})
} catch (e) {
// ignore
}

return ruleset
}
}

// walk path
function walkPath(
globInfo: { base: string; pattern: string; ignore?: string },
Expand Down
1 change: 1 addition & 0 deletions test/config/bad.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{
3 changes: 3 additions & 0 deletions test/config/good.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"tag-pair": true
}
36 changes: 36 additions & 0 deletions test/executable.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,39 @@ describe('Executable', () => {
})
}
})

describe('Configfiles', () => {
it('Malformed config should error', (done) => {
ChildProcess.exec(
[
'node',
path.resolve(__dirname, '../../bin/htmlhint'),
path.resolve(__dirname, 'example.html'),
'--config',
'config/bad.json',
].join(' '),
(error, stdout, stderr) => {
expect(error).to.be.an('object')
expect(error.code).to.be.equal(2)
}
)
done()
})

it('Good config should load and show enabled error', (done) => {
ChildProcess.exec(
[
'node',
path.resolve(__dirname, '../../bin/htmlhint'),
path.resolve(__dirname, 'example.html'),
'--config',
'config/good.json',
].join(' '),
(error, stdout, stderr) => {
expect(error).to.be.an('object')
expect(error.code).to.be.equal(2)
}
)
done()
})
})