Skip to content

Commit d006274

Browse files
author
Christopher Quadflieg
committed
feat: add config attribute extends
1 parent d0c71dd commit d006274

File tree

4 files changed

+62
-33
lines changed

4 files changed

+62
-33
lines changed

src/cli/formatter.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { EventEmitter } from 'events'
33
import { sync as globSync } from 'glob'
44
import { parse, resolve } from 'path'
55
import type { HTMLHint as IHTMLHint } from '../core/core'
6-
import type { Hint, Ruleset } from '../core/types'
6+
import type { Configuration, Hint } from '../core/types'
77

88
let HTMLHint: typeof IHTMLHint
99
let options: { nocolor?: boolean }
@@ -45,7 +45,7 @@ export interface FormatterFileEvent {
4545
}
4646

4747
export interface FormatterConfigEvent {
48-
ruleset: Ruleset
48+
config?: Configuration
4949
configPath?: string
5050
}
5151

src/cli/htmlhint.ts

+29-24
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { dirname, resolve, sep } from 'path'
1111
import * as request from 'request'
1212
import * as stripJsonComments from 'strip-json-comments'
1313
import type { HTMLHint as IHTMLHint } from '../core/core'
14-
import type { Hint, Ruleset } from '../core/types'
14+
import type { Configuration, Hint } from '../core/types'
1515
import { Formatter } from './formatter'
1616

1717
const HTMLHint: typeof IHTMLHint = require('../htmlhint.js').HTMLHint
@@ -96,9 +96,11 @@ if (format) {
9696
formatter.setFormat(format)
9797
}
9898

99+
// TODO: parse and validate `program.rules`
100+
99101
hintTargets(arrTargets, {
100102
rulesdir: program.rulesdir,
101-
ruleset: program.rules,
103+
config: program.rules ? { rules: program.rules } : undefined,
102104
formatter: formatter,
103105
ignore: program.ignore,
104106
})
@@ -121,7 +123,7 @@ function hintTargets(
121123
arrTargets: string[],
122124
options: {
123125
formatter: Formatter
124-
ruleset?: Ruleset
126+
config?: Configuration
125127
rulesdir?: string
126128
ignore?: string
127129
}
@@ -213,7 +215,7 @@ function hintAllFiles(
213215
options: {
214216
ignore?: string
215217
formatter: Formatter
216-
ruleset?: Ruleset
218+
config?: Configuration
217219
},
218220
onFinised: (result: {
219221
targetFileCount: number
@@ -241,22 +243,22 @@ function hintAllFiles(
241243
time: number
242244
}> = []
243245

244-
// init ruleset
245-
let ruleset = options.ruleset
246-
if (ruleset === undefined) {
247-
ruleset = getConfig(program.config, globInfo.base, formatter)
246+
// init config
247+
let config = options.config
248+
if (config === undefined) {
249+
config = getConfig(program.config, globInfo.base, formatter)
248250
}
249251

250252
// hint queue
251253
const hintQueue = asyncQueue<string>((filepath, next) => {
252254
const startTime = new Date().getTime()
253255

254256
if (filepath === 'stdin') {
255-
hintStdin(ruleset, hintNext)
257+
hintStdin(config, hintNext)
256258
} else if (/^https?:\/\//.test(filepath)) {
257-
hintUrl(filepath, ruleset, hintNext)
259+
hintUrl(filepath, config, hintNext)
258260
} else {
259-
const messages = hintFile(filepath, ruleset)
261+
const messages = hintFile(filepath, config)
260262
hintNext(messages)
261263
}
262264

@@ -370,14 +372,15 @@ function getConfig(
370372
configPath: string | undefined,
371373
base: string,
372374
formatter: Formatter
373-
) {
375+
): Configuration | undefined {
374376
if (configPath === undefined && existsSync(base)) {
375377
// find default config file in parent directory
376378
if (statSync(base).isDirectory() === false) {
377379
base = dirname(base)
378380
}
379381

380382
while (base) {
383+
// TODO: load via cosmiconfig (https://github.com/htmlhint/HTMLHint/issues/126)
381384
const tmpConfigFile = resolve(base, '.htmlhintrc')
382385

383386
if (existsSync(tmpConfigFile)) {
@@ -395,20 +398,22 @@ function getConfig(
395398

396399
// TODO: can configPath be undefined here?
397400
if (configPath !== undefined && existsSync(configPath)) {
398-
const config = readFileSync(configPath, 'utf-8')
399-
let ruleset: Ruleset = {}
401+
const configContent = readFileSync(configPath, 'utf-8')
402+
let config: Configuration | undefined
400403

401404
try {
402-
ruleset = JSON.parse(stripJsonComments(config))
405+
config = JSON.parse(stripJsonComments(configContent))
403406
formatter.emit('config', {
404-
ruleset: ruleset,
405-
configPath: configPath,
407+
configPath,
408+
config,
406409
})
407410
} catch (e) {
408411
// ignore
409412
}
410413

411-
return ruleset
414+
// TODO: validate config
415+
416+
return config
412417
}
413418
}
414419

@@ -456,7 +461,7 @@ function walkPath(
456461
}
457462

458463
// hint file
459-
function hintFile(filepath: string, ruleset?: Ruleset) {
464+
function hintFile(filepath: string, config?: Configuration) {
460465
let content = ''
461466

462467
try {
@@ -465,12 +470,12 @@ function hintFile(filepath: string, ruleset?: Ruleset) {
465470
// ignore
466471
}
467472

468-
return HTMLHint.verify(content, { rules: ruleset })
473+
return HTMLHint.verify(content, config)
469474
}
470475

471476
// hint stdin
472477
function hintStdin(
473-
ruleset: Ruleset | undefined,
478+
config: Configuration | undefined,
474479
callback: (messages: Hint[]) => void
475480
) {
476481
process.stdin.setEncoding('utf8')
@@ -483,20 +488,20 @@ function hintStdin(
483488

484489
process.stdin.on('end', () => {
485490
const content = buffers.join('')
486-
const messages = HTMLHint.verify(content, { rules: ruleset })
491+
const messages = HTMLHint.verify(content, config)
487492
callback(messages)
488493
})
489494
}
490495

491496
// hint url
492497
function hintUrl(
493498
url: string,
494-
ruleset: Ruleset | undefined,
499+
config: Configuration | undefined,
495500
callback: (messages: Hint[]) => void
496501
) {
497502
request.get(url, (error, response, body) => {
498503
if (!error && response.statusCode == 200) {
499-
const messages = HTMLHint.verify(body, { rules: ruleset })
504+
const messages = HTMLHint.verify(body, config)
500505
callback(messages)
501506
} else {
502507
callback([])

src/core/core.ts

+30-7
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ export interface FormatOptions {
1515
indent?: number
1616
}
1717

18-
class HTMLHintCore {
19-
public rules: { [id: string]: Rule } = {}
20-
public readonly defaultRuleset: Ruleset = {
18+
const HTMLHINT_RECOMMENDED = 'htmlhint:recommended'
19+
const HTMLHINT_LEGACY = 'htmlhint:legacy'
20+
21+
const DEFAULT_RULESETS: Record<string, Ruleset> = {
22+
[HTMLHINT_RECOMMENDED]: {
23+
// TODO: Define recommended rules
24+
},
25+
[HTMLHINT_LEGACY]: {
2126
'tagname-lowercase': 'error',
2227
'attr-lowercase': 'error',
2328
'attr-value-double-quotes': 'error',
@@ -28,19 +33,37 @@ class HTMLHintCore {
2833
'src-not-empty': 'error',
2934
'attr-no-duplication': 'error',
3035
'title-require': 'error',
31-
}
36+
},
37+
}
38+
39+
class HTMLHintCore {
40+
public rules: { [id: string]: Rule } = {}
3241

3342
public addRule(rule: Rule) {
3443
this.rules[rule.id] = rule
3544
}
3645

3746
public verify(
3847
html: string,
39-
config: Configuration = { rules: this.defaultRuleset }
48+
config: Configuration = { extends: [HTMLHINT_LEGACY] }
4049
) {
41-
let ruleset = config.rules ?? this.defaultRuleset
50+
let ruleset: Ruleset = {}
51+
52+
// Iterate through extensions and merge rulesets into ruleset
53+
for (const extend of config.extends ?? []) {
54+
if (typeof extend === 'string') {
55+
const extendRuleset = DEFAULT_RULESETS[extend] ?? {}
56+
ruleset = { ...ruleset, ...extendRuleset }
57+
}
58+
}
59+
60+
// Apply self-configured rules
61+
ruleset = { ...ruleset, ...(config.rules ?? {}) }
62+
63+
// If no rules have been configured, return immediately
4264
if (Object.keys(ruleset).length === 0) {
43-
ruleset = this.defaultRuleset
65+
// console.log('Please configure some HTMLHint rules')
66+
return []
4467
}
4568

4669
// parse inline ruleset

src/core/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { HTMLParser } from './core'
22
import { ReportMessageCallback } from './reporter'
33

44
export interface Configuration {
5+
extends?: string[]
56
rules?: Ruleset
67
}
78

0 commit comments

Comments
 (0)