Skip to content

Commit f84ef39

Browse files
committed
Add JSDoc based types
1 parent 1481591 commit f84ef39

File tree

10 files changed

+244
-81
lines changed

10 files changed

+244
-81
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
2+
*.d.ts
23
*.log
34
coverage/
45
node_modules/

index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1+
/**
2+
* @typedef {import('./lib/types.js').Author} Author
3+
* @typedef {import('./lib/types.js').Enclosure} Enclosure
4+
* @typedef {import('./lib/types.js').Channel} Channel
5+
* @typedef {import('./lib/types.js').Entry} Entry
6+
*/
7+
18
export {rss} from './lib/rss.js'
29
export {atom} from './lib/atom.js'

lib/atom.js

Lines changed: 61 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,58 @@
1+
/**
2+
* @typedef {import('xast').Element} Element
3+
* @typedef {import('xast').Root} Root
4+
* @typedef {import('./types.js').Author} Author
5+
* @typedef {import('./types.js').Enclosure} Enclosure
6+
* @typedef {import('./types.js').Channel} Channel
7+
* @typedef {import('./types.js').Entry} Entry
8+
*/
9+
10+
import {URL} from 'url'
111
import {u} from 'unist-builder'
212
import {x} from 'xastscript'
313
import bcp47 from 'bcp-47-normalize'
4-
import {toAuthor} from './util.js'
5-
14+
import {toAuthor, toDate} from './util.js'
15+
16+
/**
17+
* Build an Atom feed.
18+
* Same API as `rss` otherwise.
19+
*
20+
* @param {Channel} channel
21+
* @param {Array.<Entry>} [data]
22+
* @returns {Root}
23+
*/
624
export function atom(channel, data) {
725
var now = new Date()
8-
var meta = channel || {}
26+
/** @type {Channel} */
27+
var meta = channel || {title: null, url: null}
28+
/** @type {Array.<Element>} */
929
var items = []
1030
var index = -1
31+
/** @type {number} */
1132
var offset
33+
/** @type {Array.<Element>} */
1234
var children
35+
/** @type {Entry} */
1336
var datum
14-
var value
37+
/** @type {string} */
38+
var url
39+
/** @type {Author} */
40+
var author
41+
/** @type {Enclosure} */
42+
var enclosure
1543

1644
if (meta.title == null) throw new Error('Expected `channel.title` to be set')
1745
if (meta.url == null) throw new Error('Expected `channel.url` to be set')
1846

19-
value = new URL(meta.url).href
47+
url = new URL(meta.url).href
2048

2149
items.push(
2250
x('title', String(meta.title)),
2351
x('subtitle', String(meta.description || '') || null),
2452
// `rel: 'alternate'` is the default.
25-
x('link', value),
26-
x('id', value),
53+
x('link', url),
54+
x('id', url),
55+
// @ts-ignore `toGTMString` is exactly what we need.
2756
x('updated', now.toGMTString())
2857
)
2958

@@ -38,10 +67,10 @@ export function atom(channel, data) {
3867
}
3968

4069
if (meta.author) {
41-
value = toAuthor(meta.author)
70+
author = toAuthor(meta.author)
4271
items.push(
43-
x('rights', '© ' + now.getUTCFullYear() + ' ' + value.name),
44-
createAuthor(value)
72+
x('rights', '© ' + now.getUTCFullYear() + ' ' + author.name),
73+
createAuthor(author)
4574
)
4675
}
4776

@@ -78,22 +107,16 @@ export function atom(channel, data) {
78107
}
79108

80109
if (datum.url) {
81-
value = new URL(datum.url).href
82-
children.push(x('link', {href: value}), x('id', value))
110+
url = new URL(datum.url).href
111+
children.push(x('link', {href: url}), x('id', url))
83112
}
84113

85-
value = datum.published
86-
87-
if (value != null) {
88-
if (typeof value !== 'object') value = new Date(value)
89-
children.push(x('published', value.toISOString()))
114+
if (datum.published != null) {
115+
children.push(x('published', toDate(datum.published).toISOString()))
90116
}
91117

92-
value = datum.modified
93-
94-
if (value != null) {
95-
if (typeof value !== 'object') value = new Date(value)
96-
children.push(x('updated', value.toISOString()))
118+
if (datum.modified != null) {
119+
children.push(x('updated', toDate(datum.modified).toISOString()))
97120
}
98121

99122
if (datum.tags) {
@@ -103,24 +126,24 @@ export function atom(channel, data) {
103126
}
104127
}
105128

106-
value = datum.enclosure
129+
enclosure = datum.enclosure
107130

108-
if (value) {
109-
if (!value.url) {
131+
if (enclosure) {
132+
if (!enclosure.url) {
110133
throw new Error(
111134
'Expected either `enclosure.url` to be set in entry `' + index + '`'
112135
)
113136
}
114137

115-
if (!value.size) {
138+
if (!enclosure.size) {
116139
throw new Error(
117140
'Expected either `enclosure.size` to be set in entry `' +
118141
index +
119142
'`'
120143
)
121144
}
122145

123-
if (!value.type) {
146+
if (!enclosure.type) {
124147
throw new Error(
125148
'Expected either `enclosure.type` to be set in entry `' +
126149
index +
@@ -129,17 +152,14 @@ export function atom(channel, data) {
129152
}
130153

131154
// Can’t use `xastscript` because of `length`
132-
children.push({
133-
type: 'element',
134-
name: 'link',
135-
attributes: {
155+
children.push(
156+
x('link', {
136157
rel: 'enclosure',
137-
href: new URL(value.url).href,
138-
length: String(value.size),
139-
type: value.type
140-
},
141-
children: []
142-
})
158+
href: new URL(enclosure.url).href,
159+
length: String(enclosure.size),
160+
type: enclosure.type
161+
})
162+
)
143163
}
144164

145165
if (datum.descriptionHtml || datum.description) {
@@ -170,6 +190,10 @@ export function atom(channel, data) {
170190
])
171191
}
172192

193+
/**
194+
* @param {Author} value
195+
* @returns {Element}
196+
*/
173197
function createAuthor(value) {
174198
return x('author', [
175199
x('name', String(value.name)),

lib/rss.js

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,51 @@
1+
/**
2+
* @typedef {import('xast').Element} Element
3+
* @typedef {import('xast').Root} Root
4+
* @typedef {import('./types.js').Author} Author
5+
* @typedef {import('./types.js').Enclosure} Enclosure
6+
* @typedef {import('./types.js').Channel} Channel
7+
* @typedef {import('./types.js').Entry} Entry
8+
*/
9+
10+
import {URL} from 'url'
111
import {u} from 'unist-builder'
212
import {x} from 'xastscript'
313
import bcp47 from 'bcp-47-normalize'
4-
import {toAuthor} from './util.js'
5-
14+
import {toAuthor, toDate} from './util.js'
15+
16+
/**
17+
* Build an RSS feed.
18+
* Same API as `atom` otherwise.
19+
*
20+
* @param {Channel} channel
21+
* @param {Array.<Entry>} [data]
22+
* @returns {Root}
23+
*/
624
export function rss(channel, data) {
725
var now = new Date()
8-
var meta = channel || {}
26+
/** @type {Channel} */
27+
var meta = channel || {title: null, url: null}
28+
/** @type {Array.<Element>} */
929
var items = []
1030
var index = -1
31+
/** @type {boolean} */
1132
var atom
33+
/** @type {number} */
1234
var offset
35+
/** @type {Array.<Element>} */
1336
var children
37+
/** @type {Entry} */
1438
var datum
15-
var value
39+
/** @type {string} */
40+
var lang
41+
/** @type {string} */
42+
var copy
43+
/** @type {string} */
44+
var url
45+
/** @type {Author} */
46+
var author
47+
/** @type {Enclosure} */
48+
var enclosure
1649

1750
if (meta.title == null) throw new Error('Expected `channel.title` to be set')
1851
if (meta.url == null) throw new Error('Expected `channel.url` to be set')
@@ -21,6 +54,7 @@ export function rss(channel, data) {
2154
x('title', String(meta.title)),
2255
x('description', String(meta.description || '') || null),
2356
x('link', new URL(meta.url).href),
57+
// @ts-ignore `toGTMString` is exactly what we need.
2458
x('lastBuildDate', now.toGMTString()),
2559
x('dc:date', now.toISOString())
2660
)
@@ -37,13 +71,13 @@ export function rss(channel, data) {
3771
}
3872

3973
if (meta.lang) {
40-
value = bcp47(meta.lang)
41-
items.push(x('language', value), x('dc:language', value))
74+
lang = bcp47(meta.lang)
75+
items.push(x('language', lang), x('dc:language', lang))
4276
}
4377

4478
if (meta.author) {
45-
value = '© ' + now.getUTCFullYear() + ' ' + meta.author
46-
items.push(x('copyright', value), x('dc:rights', value))
79+
copy = '© ' + now.getUTCFullYear() + ' ' + meta.author
80+
items.push(x('copyright', copy), x('dc:rights', copy))
4781
}
4882

4983
if (meta.tags) {
@@ -69,41 +103,36 @@ export function rss(channel, data) {
69103
if (datum.title) children.push(x('title', String(datum.title)))
70104

71105
if (datum.author) {
72-
value = toAuthor(datum.author)
73-
children.push(x('dc:creator', value.name))
106+
author = toAuthor(datum.author)
107+
children.push(x('dc:creator', author.name))
74108

75-
if (value.email) {
76-
children.push(x('author', value.email + ' (' + value.name + ')'))
109+
if (author.email) {
110+
children.push(x('author', author.email + ' (' + author.name + ')'))
77111
}
78112
}
79113

80114
if (datum.url) {
81-
value = new URL(datum.url).href
115+
url = new URL(datum.url).href
82116
children.push(
83-
x('link', value),
117+
x('link', url),
84118
// Do not treat it as a URL, just an opaque identifier.
85119
// `<link>` is already used by readers for the URL.
86120
// Now, the value we have here is a URL, but we can’t know if it’s
87121
// “permanent”, so, set `false`.
88-
x('guid', {isPermaLink: 'false'}, value)
122+
x('guid', {isPermaLink: 'false'}, url)
89123
)
90124
}
91125

92-
value = datum.published
93-
94-
if (value != null) {
95-
if (typeof value !== 'object') value = new Date(value)
126+
if (datum.published != null) {
96127
children.push(
97-
x('pubDate', value.toGMTString()),
98-
x('dc:date', value.toISOString())
128+
// @ts-ignore `toGTMString` is exactly what we need.
129+
x('pubDate', toDate(datum.published).toGMTString()),
130+
x('dc:date', toDate(datum.published).toISOString())
99131
)
100132
}
101133

102-
value = datum.modified
103-
104-
if (value != null) {
105-
if (typeof value !== 'object') value = new Date(value)
106-
children.push(x('dc:modified', value.toISOString()))
134+
if (datum.modified != null) {
135+
children.push(x('dc:modified', toDate(datum.modified).toISOString()))
107136
}
108137

109138
if (datum.tags) {
@@ -113,23 +142,23 @@ export function rss(channel, data) {
113142
}
114143
}
115144

116-
value = datum.enclosure
117-
if (value) {
118-
if (!value.url) {
145+
enclosure = datum.enclosure
146+
if (enclosure) {
147+
if (!enclosure.url) {
119148
throw new Error(
120149
'Expected either `enclosure.url` to be set in entry `' + index + '`'
121150
)
122151
}
123152

124-
if (!value.size) {
153+
if (!enclosure.size) {
125154
throw new Error(
126155
'Expected either `enclosure.size` to be set in entry `' +
127156
index +
128157
'`'
129158
)
130159
}
131160

132-
if (!value.type) {
161+
if (!enclosure.type) {
133162
throw new Error(
134163
'Expected either `enclosure.type` to be set in entry `' +
135164
index +
@@ -138,16 +167,13 @@ export function rss(channel, data) {
138167
}
139168

140169
// Can’t use `xastscript` because of `length`.
141-
children.push({
142-
type: 'element',
143-
name: 'enclosure',
144-
attributes: {
145-
url: new URL(value.url).href,
146-
length: String(value.size),
147-
type: value.type
148-
},
149-
children: []
150-
})
170+
children.push(
171+
x('enclosure', {
172+
url: new URL(enclosure.url).href,
173+
length: String(enclosure.size),
174+
type: enclosure.type
175+
})
176+
)
151177
}
152178

153179
if (datum.descriptionHtml || datum.description) {

0 commit comments

Comments
 (0)