Skip to content

Commit

Permalink
Add JSDoc based types
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jul 30, 2021
1 parent a94fa25 commit f160b63
Show file tree
Hide file tree
Showing 37 changed files with 444 additions and 372 deletions.
8 changes: 7 additions & 1 deletion .gitignore
@@ -1,5 +1,11 @@
node_modules/
coverage/
node_modules/
packages/rehype/*.d.ts
packages/rehype-cli/*.d.ts
packages/rehype-parse/lib/*.d.ts
packages/rehype-stringify/lib/*.d.ts
script/*.d.ts
test/*.d.ts
.DS_Store
*.log
yarn.lock
18 changes: 15 additions & 3 deletions package.json
Expand Up @@ -10,29 +10,35 @@
},
"type": "module",
"devDependencies": {
"@types/tape": "^4.0.0",
"bail": "^2.0.0",
"c8": "^7.0.0",
"dtslint": "^4.0.0",
"hast-util-assert": "^3.0.0",
"lerna": "^4.0.0",
"mdast-zone": "^5.0.0",
"prettier": "^2.0.0",
"remark-cli": "^9.0.0",
"remark-preset-wooorm": "^8.0.0",
"rimraf": "^3.0.0",
"tape": "^5.0.0",
"to-vfile": "^7.0.0",
"type-coverage": "^2.0.0",
"type-fest": "^0.21.3",
"typescript": "^4.0.0",
"unified": "^10.0.0",
"unist-builder": "^3.0.0",
"unist-util-remove-position": "^4.0.0",
"xo": "^0.38.0"
},
"scripts": {
"postinstall": "lerna bootstrap --no-ci",
"build-workspace": "lerna run build",
"build-monorepo": "rimraf \"*.d.ts\" \"{test,script}/**/*.d.ts\" && tsc && type-coverage",
"build": "npm run build-workspace && npm run build-monorepo",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node --conditions development test/index.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node --conditions development test/index.js",
"test-types": "dtslint packages/rehype-parse/types && dtslint packages/rehype-stringify/types && dtslint packages/rehype/types",
"test": "npm run format && npm run test-coverage && npm run test-types"
"test": "npm run build && npm run format && npm run test-coverage"
},
"prettier": {
"tabWidth": 2,
Expand All @@ -53,5 +59,11 @@
"preset-wooorm",
"./script/parse-error.js"
]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true,
"ignoreCatch": true
}
}
1 change: 1 addition & 0 deletions packages/rehype-cli/cli.js
Expand Up @@ -8,6 +8,7 @@ const proc = require('rehype/package.json')
const cli = require('./package.json')

start({
// @ts-expect-error: fine.
processor: rehype,
name: proc.name,
description: cli.description,
Expand Down
12 changes: 10 additions & 2 deletions packages/rehype-cli/package.json
Expand Up @@ -32,6 +32,14 @@
"rehype": "^11.0.0",
"unified-args": "^8.0.0"
},
"scripts": {},
"xo": false
"scripts": {
"build": "rimraf \"*.d.ts\" && tsc && type-coverage"
},
"xo": false,
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true,
"ignoreCatch": true
}
}
4 changes: 4 additions & 0 deletions packages/rehype-cli/tsconfig.json
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["*.js"]
}
7 changes: 7 additions & 0 deletions packages/rehype-parse/index.d.ts
@@ -0,0 +1,7 @@
// This wrapper exists because JS in TS can’t export a `@type` of a function.
import type {Options, ErrorCode, ErrorSeverity} from './lib/index.js'
import type {Root} from 'hast'
import type {Plugin} from 'unified'
const rehypeParse: Plugin<[Options] | [], Root, string>
export default rehypeParse
export type {Options, ErrorCode, ErrorSeverity}
93 changes: 2 additions & 91 deletions packages/rehype-parse/index.js
@@ -1,91 +1,2 @@
import Parser5 from 'parse5/lib/parser/index.js'
import {fromParse5} from 'hast-util-from-parse5'
import {errors} from './errors.js'

const base = 'https://html.spec.whatwg.org/multipage/parsing.html#parse-error-'

const fatalities = {2: true, 1: false, 0: null}

export default function rehypeParse(options) {
const settings = Object.assign({}, options, this.data('settings'))
let position = settings.position

position = typeof position === 'boolean' ? position : true

this.Parser = parser

function parser(doc, file) {
const fn = settings.fragment ? 'parseFragment' : 'parse'
const onParseError = settings.emitParseErrors ? onerror : null
const parse5 = new Parser5({
sourceCodeLocationInfo: position,
onParseError,
scriptingEnabled: false
})

return fromParse5(parse5[fn](doc), {
space: settings.space,
file,
verbose: settings.verbose
})

function onerror(error) {
const code = error.code
const name = camelcase(code)
const setting = settings[name]
const config = setting === undefined || setting === null ? true : setting
const level = typeof config === 'number' ? config : config ? 1 : 0
const start = {
line: error.startLine,
column: error.startCol,
offset: error.startOffset
}
const end = {
line: error.endLine,
column: error.endCol,
offset: error.endOffset
}
let info
let message

if (level) {
/* c8 ignore next */
info = errors[name] || {reason: '', description: ''}

message = file.message(format(info.reason), {start, end})
message.source = 'parse-error'
message.ruleId = code
message.fatal = fatalities[level]
message.note = format(info.description)
message.url = info.url === false ? null : base + code
}

function format(value) {
return value.replace(/%c(?:-(\d+))?/g, char).replace(/%x/g, encodedChar)
}

function char($0, $1) {
const offset = $1 ? -Number.parseInt($1, 10) : 0
const char = doc.charAt(error.startOffset + offset)
return char === '`' ? '` ` `' : char
}

function encodedChar() {
const char = doc
.charCodeAt(error.startOffset)
.toString(16)
.toUpperCase()

return '0x' + char
}
}
}
}

function camelcase(value) {
return value.replace(/-[a-z]/g, replacer)
}

function replacer($0) {
return $0.charAt(1).toUpperCase()
}
import rehypeParse from './lib/index.js'
export default rehypeParse
File renamed without changes.
126 changes: 126 additions & 0 deletions packages/rehype-parse/lib/index.js
@@ -0,0 +1,126 @@
/**
* @typedef {import('hast').Root} Root
* @typedef {Pick<import('hast-util-from-parse5').Options, 'space' | 'verbose'>} FromParse5Options
*
* @typedef {keyof errors} ErrorCode
* @typedef {0|1|2|boolean|null|undefined} ErrorSeverity
* @typedef {Partial<Record<ErrorCode, ErrorSeverity>>} ErrorFields
*
* @typedef ParseFields
* @property {boolean|undefined} [position=true]
* @property {boolean|undefined} [fragment=false]
* Specify whether to parse a fragment, instead of a complete document.
* In document mode, unopened `html`, `head`, and `body` elements are opened
* in just the right places.
* @property {boolean|undefined} [emitParseErrors=false]
* > ⚠️ Parse errors are currently being added to HTML.
* > Not all errors emitted by parse5 (or rehype-parse) are specced yet.
* > Some documentation may still be missing.
*
* Emit parse errors while parsing on the vfile.
* Setting this to `true` starts emitting HTML parse errors.
*
* Specific rules can be turned off by setting them to `false` (or `0`).
* The default, when `emitParseErrors: true`, is `true` (or `1`), and means
* that rules emit as warnings.
* Rules can also be configured with `2`, to turn them into fatal errors.
*
* @typedef {FromParse5Options & ParseFields & ErrorFields} Options
*/

// @ts-expect-error: remove when typed
import Parser5 from 'parse5/lib/parser/index.js'
import {fromParse5} from 'hast-util-from-parse5'
import {errors} from './errors.js'

const base = 'https://html.spec.whatwg.org/multipage/parsing.html#parse-error-'

const fatalities = {2: true, 1: false, 0: null}

/** @type {import('unified').Plugin<[Options?] | void[], string, Root>} */
export default function rehypeParse(options) {
const processorSettings = /** @type {Options} */ (this.data('settings'))
const settings = Object.assign({}, options, processorSettings)
const position =
typeof settings.position === 'boolean' ? settings.position : true

Object.assign(this, {Parser: parser})

/** @type {import('unified').ParserFunction<Root>} */
function parser(doc, file) {
const fn = settings.fragment ? 'parseFragment' : 'parse'
const onParseError = settings.emitParseErrors ? onerror : null
const parse5 = new Parser5({
sourceCodeLocationInfo: position,
onParseError,
scriptingEnabled: false
})

// @ts-expect-error: `parse5` returns document or fragment, which are always
// mapped to roots.
return fromParse5(parse5[fn](doc), {
space: settings.space,
file,
verbose: settings.verbose
})

/**
* @param {{code: string, startLine: number, startCol: number, startOffset: number, endLine: number, endCol: number, endOffset: number}} error
*/
function onerror(error) {
const code = error.code
const name = camelcase(code)
const setting = settings[name]
const config = setting === undefined || setting === null ? true : setting
const level = typeof config === 'number' ? config : config ? 1 : 0
const start = {
line: error.startLine,
column: error.startCol,
offset: error.startOffset
}
const end = {
line: error.endLine,
column: error.endCol,
offset: error.endOffset
}
if (level) {
/* c8 ignore next */
const info = errors[name] || {reason: '', description: '', url: ''}
const message = file.message(format(info.reason), {start, end})
message.source = 'parse-error'
message.ruleId = code
message.fatal = fatalities[level]
message.note = format(info.description)
message.url = 'url' in info && info.url === false ? null : base + code
}

/**
* @param {string} value
* @returns {string}
*/
function format(value) {
return value
.replace(/%c(?:-(\d+))?/g, (_, /** @type {string} */ $1) => {
const offset = $1 ? -Number.parseInt($1, 10) : 0
const char = doc.charAt(error.startOffset + offset)
return char === '`' ? '` ` `' : char
})
.replace(
/%x/g,
() =>
'0x' +
doc.charCodeAt(error.startOffset).toString(16).toUpperCase()
)
}
}
}
}

/**
* @param {string} value
* @returns {ErrorCode}
*/
function camelcase(value) {
// @ts-expect-error: this returns a valid error code.
return value.replace(/-[a-z]/g, ($0) => $0.charAt(1).toUpperCase())
}
21 changes: 16 additions & 5 deletions packages/rehype-parse/package.json
Expand Up @@ -29,15 +29,26 @@
"sideEffects": false,
"type": "module",
"main": "index.js",
"types": "types/index.d.ts",
"types": "index.d.ts",
"files": [
"types/index.d.ts",
"errors.js",
"lib/",
"index.d.ts",
"index.js"
],
"dependencies": {
"@types/hast": "^2.0.0",
"hast-util-from-parse5": "^7.0.0",
"parse5": "^6.0.0"
"parse5": "^6.0.0",
"unified": "^10.0.0"
},
"xo": false
"scripts": {
"build": "rimraf \"lib/**/*.d.ts\" && tsc && type-coverage"
},
"xo": false,
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true,
"ignoreCatch": true
}
}
2 changes: 1 addition & 1 deletion packages/rehype-parse/readme.md
Expand Up @@ -169,7 +169,7 @@ back when [**exiting**][exit].
Emit parse errors while parsing on the [vfile][] (`boolean`, default: `false`).

Setting this to true starts emitting [HTML parse errors][parse-errors].
Setting this to `true` starts emitting [HTML parse errors][parse-errors].

Specific rules can be turned off by setting them to `false` (or `0`).
The default, when `emitParseErrors: true`, is `true` (or `1`), and means that
Expand Down
4 changes: 4 additions & 0 deletions packages/rehype-parse/tsconfig.json
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["lib/**/*.js"]
}

0 comments on commit f160b63

Please sign in to comment.