Skip to content

Commit 82b9851

Browse files
committed
Add JSDoc based types
1 parent 84f75af commit 82b9851

File tree

6 files changed

+88
-21
lines changed

6 files changed

+88
-21
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
*.log
21
.DS_Store
2+
*.d.ts
3+
*.log
34
coverage/
45
node_modules/
56
yarn.lock

index.js

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,80 @@
1+
/**
2+
* @typedef {import('hast').Element} HastElement
3+
*
4+
* @typedef {string|number} ConditionalPrimitive
5+
* @typedef {Object.<string, boolean>} ConditionalMap
6+
* @typedef {ConditionalPrimitive|ConditionalMap|Array.<ConditionalPrimitive|ConditionalMap|Array.<ConditionalPrimitive|ConditionalMap>>} Conditional
7+
*
8+
* @typedef {Object.<string, boolean>} ClassMap
9+
*
10+
*/
11+
112
import {parse} from 'space-separated-tokens'
213

314
var own = {}.hasOwnProperty
415

5-
// A bit inspired by <https://github.com/JedWatson/classnames>, but for hast.
6-
export function classnames(node) {
16+
/**
17+
* A bit inspired by <https://github.com/JedWatson/classnames>, but for hast.
18+
*
19+
* @param {HastElement|Conditional} [node]
20+
* @param {Array.<Conditional>} [conditionals]
21+
*/
22+
export function classnames(node, ...conditionals) {
23+
var index = -1
24+
/** @type {ClassMap} */
725
var map = Object.create(null)
26+
/** @type {Array.<string>} */
827
var list = []
9-
var mutate = node && typeof node === 'object' && 'type' in node
10-
var index = -1
28+
/** @type {string} */
1129
var key
1230

13-
if (mutate) {
31+
if (isNode(node)) {
1432
if (node.type !== 'element') throw new Error('Expected element node')
1533

1634
if (!node.properties) node.properties = {}
1735

18-
if (node.properties.className) add(map, node.properties.className)
36+
if (node.properties.className) {
37+
// @ts-ignore Assume `classname` is `Array.<string>`
38+
add(map, node.properties.className)
39+
}
1940

2041
node.properties.className = list
21-
22-
index++
42+
} else {
43+
conditionals.unshift(node)
2344
}
2445

25-
while (++index < arguments.length) {
26-
add(map, arguments[index])
46+
while (++index < conditionals.length) {
47+
add(map, conditionals[index])
2748
}
2849

2950
for (key in map) {
3051
if (map[key]) list.push(key)
3152
}
3253

33-
return mutate ? node : list
54+
return isNode(node) ? node : list
3455
}
3556

57+
/**
58+
* @param {ClassMap} result
59+
* @param {Conditional} conditional
60+
*/
3661
function add(result, conditional) {
3762
var index = -1
63+
/** @type {string} */
3864
var key
65+
/** @type {Array.<string>} */
66+
var list
3967

4068
if (typeof conditional === 'number') {
4169
result[conditional] = true
4270
} else if (typeof conditional === 'string') {
43-
conditional = parse(conditional)
71+
list = parse(conditional)
4472

45-
while (++index < conditional.length) {
46-
result[conditional[index]] = true
73+
while (++index < list.length) {
74+
result[list[index]] = true
4775
}
4876
} else if (conditional && typeof conditional === 'object') {
49-
if ('length' in conditional) {
77+
if (Array.isArray(conditional)) {
5078
while (++index < conditional.length) {
5179
add(result, conditional[index])
5280
}
@@ -59,3 +87,11 @@ function add(result, conditional) {
5987
}
6088
}
6189
}
90+
91+
/**
92+
* @param {HastElement|Conditional} [value]
93+
* @returns {value is HastElement}
94+
*/
95+
function isNode(value) {
96+
return value && typeof value === 'object' && 'type' in value
97+
}

package.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,36 @@
2727
"sideEffects": false,
2828
"type": "module",
2929
"main": "index.js",
30+
"types": "index.d.ts",
3031
"files": [
32+
"index.d.ts",
3133
"index.js"
3234
],
3335
"dependencies": {
36+
"@types/hast": "^2.0.0",
3437
"space-separated-tokens": "^2.0.0"
3538
},
3639
"devDependencies": {
40+
"@types/tape": "^4.0.0",
3741
"c8": "^7.0.0",
3842
"hastscript": "^7.0.0",
3943
"prettier": "^2.0.0",
4044
"remark-cli": "^9.0.0",
4145
"remark-preset-wooorm": "^8.0.0",
46+
"rimraf": "^3.0.0",
4247
"tape": "^5.0.0",
48+
"type-coverage": "^2.0.0",
49+
"typescript": "^4.0.0",
4350
"unist-builder": "^3.0.0",
4451
"xo": "^0.39.0"
4552
},
4653
"scripts": {
54+
"prepack": "npm run build && npm run format",
55+
"build": "rimraf \"*.d.ts\" && tsc && type-coverage",
4756
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
4857
"test-api": "node test.js",
4958
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js",
50-
"test": "npm run format && npm run test-coverage"
59+
"test": "npm run build && npm run format && npm run test-coverage"
5160
},
5261
"prettier": {
5362
"tabWidth": 2,
@@ -68,5 +77,10 @@
6877
"plugins": [
6978
"preset-wooorm"
7079
]
80+
},
81+
"typeCoverage": {
82+
"atLeast": 100,
83+
"detail": true,
84+
"strict": true
7185
}
7286
}

readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ A value that is either:
7070

7171
* `string` — One or more space-separated tokens (example: `alpha bravo`)
7272
* `number` — Single token that is cast to string (example: `123`)
73-
* `Object.<boolean>` — Map where each field is a token, and each value turns
74-
it either on or off
73+
* `Object.<string, boolean>` — Map where each field is a token, and each value
74+
turns it either on or off
7575
* `Conditional[]` — List of more conditionals
7676
* Other values are ignored
7777

test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {classnames} from './index.js'
66
test('hast-util-classnames', function (t) {
77
t.throws(
88
function () {
9+
// @ts-ignore runtime.
910
classnames(u('comment', '?'))
1011
},
1112
/Expected element node/,
@@ -33,7 +34,7 @@ test('hast-util-classnames', function (t) {
3334
)
3435

3536
t.deepEqual(
36-
classnames(h('a'), {alpha: true, bravo: false, charlie: 1}),
37+
classnames(h('a'), {alpha: true, bravo: false, charlie: true}),
3738
h('a.alpha.charlie'),
3839
'should support an object'
3940
)
@@ -73,7 +74,7 @@ test('hast-util-classnames', function (t) {
7374
)
7475

7576
t.deepEqual(
76-
classnames('alpha', ['bravo', 123], {charlie: 1}),
77+
classnames('alpha', ['bravo', 123], {charlie: true}),
7778
['123', 'alpha', 'bravo', 'charlie'],
7879
'should return a list if w/o element'
7980
)

tsconfig.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"include": ["*.js"],
3+
"compilerOptions": {
4+
"target": "ES2020",
5+
"lib": ["ES2020"],
6+
"module": "ES2020",
7+
"moduleResolution": "node",
8+
"allowJs": true,
9+
"checkJs": true,
10+
"declaration": true,
11+
"emitDeclarationOnly": true,
12+
"allowSyntheticDefaultImports": true,
13+
"skipLibCheck": true
14+
}
15+
}

0 commit comments

Comments
 (0)