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 May 2, 2021
1 parent ed90b0e commit ba41ded
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 33 deletions.
93 changes: 73 additions & 20 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
/**
* @typedef {import('hast').Parent} HastParent
* @typedef {import('hast').Root} HastRoot
* @typedef {import('hast').DocType} HastDoctype
* @typedef {import('hast').Element} HastElement
* @typedef {import('hast').Text} HastText
* @typedef {import('hast').Comment} HastComment
* @typedef {HastParent['children'][number]} HastChild
* @typedef {HastChild|HastRoot} HastNode
*/

import {webNamespaces} from 'web-namespaces'
import {h, s} from 'hastscript'

Expand All @@ -8,32 +19,49 @@ const DOCUMENT_NODE = 9
const DOCUMENT_TYPE_NODE = 10
const DOCUMENT_FRAGMENT_NODE = 11

function transform(value) {
const node = value || {}

/**
* @param {Node} node
* @returns {HastNode|null}
*/
function transform(node) {
switch (node.nodeType) {
case ELEMENT_NODE:
// @ts-ignore TypeScript is wrong.
return element(node)
case DOCUMENT_NODE:
case DOCUMENT_FRAGMENT_NODE:
// @ts-ignore TypeScript is wrong.
return root(node)
case TEXT_NODE:
// @ts-ignore TypeScript is wrong.
return text(node)
case COMMENT_NODE:
// @ts-ignore TypeScript is wrong.
return comment(node)
case DOCUMENT_TYPE_NODE:
// @ts-ignore TypeScript is wrong.
return doctype(node)
default:
return null
}
}

// Transform a document.
/**
* Transform a document.
*
* @param {Document|DocumentFragment} node
* @returns {HastRoot}
*/
function root(node) {
return {type: 'root', children: all(node)}
}

// Transform a doctype.
/**
* Transform a doctype.
*
* @param {DocumentType} node
* @returns {HastDoctype}
*/
function doctype(node) {
return {
type: 'doctype',
Expand All @@ -43,57 +71,82 @@ function doctype(node) {
}
}

// Transform text.
/**
* Transform a text.
*
* @param {Text} node
* @returns {HastText}
*/
function text(node) {
return {type: 'text', value: node.nodeValue}
}

// Transform a comment.
/**
* Transform a comment.
*
* @param {Comment} node
* @returns {HastComment}
*/
function comment(node) {
return {type: 'comment', value: node.nodeValue}
}

// Transform an element.
/**
* Transform an element.
*
* @param {Element} node
* @returns {HastElement}
*/
function element(node) {
const space = node.namespaceURI
const fn = space === webNamespaces.svg ? s : h
const tagName =
space === webNamespaces.html ? node.tagName.toLowerCase() : node.tagName
/** @type {DocumentFragment|Element} */
const content =
// @ts-ignore Types are wrong.
space === webNamespaces.html && tagName === 'template' ? node.content : node
const attributes = node.getAttributeNames()
const {length} = attributes
/** @type {Object.<string, string>} */
const props = {}
let index = 0
let index = -1

while (index < length) {
const key = attributes[index]
props[key] = node.getAttribute(key)
index += 1
while (++index < attributes.length) {
props[attributes[index]] = node.getAttribute(attributes[index])
}

return fn(tagName, props, all(content))
}

/**
* Transform an element.
*
* @param {Document|DocumentFragment|Element} node
* @returns {Array.<HastChild>}
*/
function all(node) {
const nodes = node.childNodes
const {length} = nodes
/** @type {Array.<HastChild>} */
const children = []
let index = 0
let index = -1

while (index < length) {
while (++index < nodes.length) {
const child = transform(nodes[index])

if (child !== null) {
// @ts-ignore Assume no document inside document.
children.push(child)
}

index += 1
}

return children
}

/**
* @param {Node} node
* @returns {HastNode}
*/
export function fromDom(node) {
return transform(node) || {type: 'root', children: []}
// @ts-ignore Code can handle empty “node”.
return transform(node || {}) || {type: 'root', children: []}
}
24 changes: 17 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,38 @@
"module": "dist/hast-util-from-dom.mjs",
"sideEffects": false,
"type": "module",
"types": "index.d.ts",
"files": [
"lib/",
"index.d.ts",
"index.js"
],
"dependencies": {
"hastscript": "^7.0.0",
"web-namespaces": "^2.0.0"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@rollup/plugin-babel": "^5.0.0",
"@types/jsdom": "^16.0.0",
"@types/tape": "^4.0.0",
"c8": "^7.0.0",
"glob": "^7.0.0",
"jsdom": "^16.5.3",
"jsdom": "^16.0.0",
"prettier": "^2.0.0",
"remark-cli": "^9.0.0",
"remark-preset-wooorm": "^8.0.0",
"rollup": "^2.0.0",
"tape": "^5.2.2",
"rimraf": "^3.0.0",
"tape": "^5.0.0",
"type-coverage": "^2.0.0",
"typescript": "^4.0.0",
"xo": "^0.39.0"
},
"scripts": {
"prepack": "npm run build && npm run format",
"build": "rimraf \"{lib/**,test/**,}*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node test/index.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test/index.js",
"test": "npm run format && npm run test-coverage"
"test": "npm run build && npm run format && npm run test-coverage"
},
"prettier": {
"tabWidth": 2,
Expand All @@ -71,5 +76,10 @@
"plugins": [
"preset-wooorm"
]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true
}
}
14 changes: 8 additions & 6 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import glob from 'glob'
import {JSDOM} from 'jsdom'
import {fromDom} from '../index.js'

globalThis.window = new JSDOM().window
const window = new JSDOM().window
globalThis.document = window.document

test('hast-util-from-dom', (t) => {
Expand Down Expand Up @@ -116,6 +116,7 @@ test('hast-util-from-dom', (t) => {
)

t.deepEqual(
// @ts-ignore runtime.
fromDom(),
{type: 'root', children: []},
'should handle a missing DOM tree'
Expand All @@ -135,15 +136,16 @@ test('fixtures', (t) => {

t.end()

function each(fixturePath) {
function each(/** @type {string} */ fixturePath) {
const input = path.join(fixturePath, 'index.html')
const output = path.join(fixturePath, 'index.json')
const fixtureHtml = fs.readFileSync(input)
const fixtureHtml = String(fs.readFileSync(input))
const actual = fromDom(doc(fixtureHtml))
/** @type {unknown} */
let parsedExpected

try {
parsedExpected = JSON.parse(fs.readFileSync(output))
parsedExpected = JSON.parse(String(fs.readFileSync(output)))
} catch {
fs.writeFileSync(output, JSON.stringify(actual, null, 2))
return
Expand All @@ -153,7 +155,7 @@ test('fixtures', (t) => {
}
})

function fragment(htmlString) {
function fragment(/** @type {string} */ htmlString) {
const node = document.createDocumentFragment()
const temporary = document.createElement('body')

Expand All @@ -170,6 +172,6 @@ function fragment(htmlString) {
return node
}

function doc(htmlString) {
function doc(/** @type {string} */ htmlString) {
return new JSDOM(htmlString).window.document
}
15 changes: 15 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"include": ["*.js", "lib/**/*.js", "test/**/*.js"],
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM"],
"module": "ES2020",
"moduleResolution": "node",
"allowJs": true,
"checkJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
}
}

0 comments on commit ba41ded

Please sign in to comment.