From 93429abe97dce8d119ebd88fd0b7446a02bc05ba Mon Sep 17 00:00:00 2001 From: Dan MacTough Date: Thu, 19 Mar 2026 22:07:53 -0400 Subject: [PATCH 1/6] Add bundled TypeScript type definitions Introduces index.d.ts with clean, public-API-focused types so TypeScript users get types automatically without needing @types/feedparser. Fixes several issues in the DefinitelyTyped version: removes private internals, corrects base class to stream.Transform, makes options optional, adds missing strict option, source property, and namespace index signatures. Co-Authored-By: Claude Sonnet 4.6 --- index.d.ts | 95 +++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 18 +++++++++ package.json | 3 ++ 3 files changed, 116 insertions(+) create mode 100644 index.d.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..16c63db --- /dev/null +++ b/index.d.ts @@ -0,0 +1,95 @@ +/// + +import stream = require("stream"); + +export = FeedParser; + +declare class FeedParser extends stream.Transform { + constructor(options?: FeedParser.Options); + meta: FeedParser.Meta; + options: FeedParser.Options; + + read(): FeedParser.Item | null; + resumeSaxError(): void; + + on(event: 'meta', listener: (meta: FeedParser.Meta) => void): this; + on(event: 'error', listener: (error: Error) => void): this; + on(event: string, listener: (...args: any[]) => void): this; + + addListener(event: 'meta', listener: (meta: FeedParser.Meta) => void): this; + addListener(event: 'error', listener: (error: Error) => void): this; + addListener(event: string, listener: (...args: any[]) => void): this; + + once(event: 'meta', listener: (meta: FeedParser.Meta) => void): this; + once(event: 'error', listener: (error: Error) => void): this; + once(event: string, listener: (...args: any[]) => void): this; +} + +declare namespace FeedParser { + type Type = "atom" | "rss" | "rdf"; + + interface Options { + strict?: boolean; + normalize?: boolean; + addmeta?: boolean; + feedurl?: string; + resume_saxerror?: boolean; + MAX_BUFFER_LENGTH?: number; + } + + interface Image { + url: string; + title: string; + } + + interface Meta { + "#ns": Array<{ [key: string]: string }>; + "#type": Type; + "#version": string; + "@": { [key: string]: any }; + title: string; + description: string; + date: Date | null; + pubdate: Date | null; + link: string; + xmlurl: string; + author: string; + language: string; + image: Image; + favicon: string; + copyright: string; + generator: string; + categories: string[]; + [key: string]: any; + } + + interface Enclosure { + url: string; + type?: string; + length?: string; + } + + interface Source { + title: string; + url: string; + } + + interface Item { + title: string; + description: string; + summary: string; + date: Date | null; + pubdate: Date | null; + link: string; + origlink: string; + author: string; + guid: string; + comments: string; + image: { url: string }; + categories: string[]; + source: Source; + enclosures: Enclosure[]; + meta: Meta; + [key: string]: any; + } +} diff --git a/package-lock.json b/package-lock.json index e8c5821..a0e4104 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "feedparser": "bin/feedparser.js" }, "devDependencies": { + "@types/node": "^25.5.0", "eslint": "^6.8.0", "iconv-lite": "^0.5.1", "mocha": "^7.1.2", @@ -64,6 +65,16 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, "node_modules/acorn": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", @@ -2230,6 +2241,13 @@ "node": ">=8" } }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", diff --git a/package.json b/package.json index f2c544b..be4c324 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,10 @@ "url": "http://github.com/danmactough/node-feedparser/issues" }, "main": "index.js", + "types": "./index.d.ts", "files": [ "index.js", + "index.d.ts", "bin/*.js", "lib" ], @@ -46,6 +48,7 @@ "sax": ">=1.2.4 <1.4.4" }, "devDependencies": { + "@types/node": "^25.5.0", "eslint": "^6.8.0", "iconv-lite": "^0.5.1", "mocha": "^7.1.2", From 5680b3b508bbae95d8f5b50a726b9f876d5ba8b5 Mon Sep 17 00:00:00 2001 From: Dan MacTough Date: Thu, 19 Mar 2026 22:11:07 -0400 Subject: [PATCH 2/6] Add automated typecheck for type definitions Adds test/types.ts exercising the full public API surface, a typecheck script running tsc --strict, and wires it into pretest alongside lint. Also adds .eslintignore to skip .ts files and typescript as a devDep. Co-Authored-By: Claude Sonnet 4.6 --- .eslintignore | 1 + package-lock.json | 17 ++++++++++- package.json | 6 ++-- test/types.ts | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 .eslintignore create mode 100644 test/types.ts diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..63e7ae6 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +**/*.ts diff --git a/package-lock.json b/package-lock.json index a0e4104..cebedd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,8 @@ "eslint": "^6.8.0", "iconv-lite": "^0.5.1", "mocha": "^7.1.2", - "node-fetch": "^2.6.0" + "node-fetch": "^2.6.0", + "typescript": "^5.9.3" }, "engines": { "node": ">= 10.18.1" @@ -2241,6 +2242,20 @@ "node": ">=8" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", diff --git a/package.json b/package.json index be4c324..ac59fcc 100644 --- a/package.json +++ b/package.json @@ -52,11 +52,13 @@ "eslint": "^6.8.0", "iconv-lite": "^0.5.1", "mocha": "^7.1.2", - "node-fetch": "^2.6.0" + "node-fetch": "^2.6.0", + "typescript": "^5.9.3" }, "scripts": { "lint": "eslint .", - "pretest": "npm run lint", + "typecheck": "tsc --noEmit --strict test/types.ts --lib es6 --module commonjs", + "pretest": "npm run lint && npm run typecheck", "test": "mocha", "version": "git changelog ; git add History.md" }, diff --git a/test/types.ts b/test/types.ts new file mode 100644 index 0000000..883ba4d --- /dev/null +++ b/test/types.ts @@ -0,0 +1,77 @@ +import FeedParser = require('../index'); + +// Constructor with no options +const fp1 = new FeedParser(); + +// Constructor with all options +const fp2 = new FeedParser({ + strict: false, + normalize: true, + addmeta: true, + feedurl: 'https://example.com/feed', + resume_saxerror: true, + MAX_BUFFER_LENGTH: 1024 * 1024, +}); + +// meta event +fp2.on('meta', (meta: FeedParser.Meta) => { + const title: string = meta.title; + const description: string = meta.description; + const type: FeedParser.Type = meta['#type']; + const version: string = meta['#version']; + const ns: Array<{ [key: string]: string }> = meta['#ns']; + const attrs: { [key: string]: any } = meta['@']; + const date: Date | null = meta.date; + const pubdate: Date | null = meta.pubdate; + const link: string = meta.link; + const xmlurl: string = meta.xmlurl; + const author: string = meta.author; + const language: string = meta.language; + const image: FeedParser.Image = meta.image; + const imageUrl: string = image.url; + const favicon: string = meta.favicon; + const copyright: string = meta.copyright; + const generator: string = meta.generator; + const categories: string[] = meta.categories; + // namespace-prefixed properties via index signature + const itunesAuthor = meta['itunes:author']; +}); + +// error event +fp2.on('error', (err: Error) => { + err.message; +}); + +// readable event + read() +fp2.on('readable', () => { + let item: FeedParser.Item | null; + while ((item = fp2.read()) !== null) { + const title: string = item.title; + const description: string = item.description; + const summary: string = item.summary; + const date: Date | null = item.date; + const pubdate: Date | null = item.pubdate; + const link: string = item.link; + const origlink: string = item.origlink; + const author: string = item.author; + const guid: string = item.guid; + const comments: string = item.comments; + const image: { url: string } = item.image; + const categories: string[] = item.categories; + const source: FeedParser.Source = item.source; + const sourceTitle: string = source.title; + const sourceUrl: string = source.url; + const enclosures: FeedParser.Enclosure[] = item.enclosures; + const enc: FeedParser.Enclosure = enclosures[0]; + const encUrl: string = enc.url; + const meta: FeedParser.Meta = item.meta; + // namespace-prefixed properties via index signature + const mediaContent = item['media:content']; + } +}); + +// once and addListener overloads +fp2.once('meta', (_meta: FeedParser.Meta) => {}); +fp2.addListener('error', (_err: Error) => {}); + +fp2.resumeSaxError(); From 3fcd344ddf6a0979523426de60800e4da496b307 Mon Sep 17 00:00:00 2001 From: Dan MacTough Date: Thu, 19 Mar 2026 22:17:13 -0400 Subject: [PATCH 3/6] Add tsconfig.json to fix VSCode TypeScript errors Without a tsconfig.json VSCode defaulted to ESM module mode, making import X = require() invalid. Explicit module: commonjs aligns VSCode with the typecheck script, which is simplified to just tsc. Co-Authored-By: Claude Sonnet 4.6 --- package.json | 2 +- tsconfig.json | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 tsconfig.json diff --git a/package.json b/package.json index ac59fcc..cec200f 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ }, "scripts": { "lint": "eslint .", - "typecheck": "tsc --noEmit --strict test/types.ts --lib es6 --module commonjs", + "typecheck": "tsc", "pretest": "npm run lint && npm run typecheck", "test": "mocha", "version": "git changelog ; git add History.md" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..292ba13 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "noEmit": true, + "strict": true, + "module": "commonjs", + "lib": ["es6"] + }, + "files": [ + "test/types.ts" + ] +} From afff5f430278cdaff91e40b7e501ceabd5545adb Mon Sep 17 00:00:00 2001 From: Dan MacTough Date: Thu, 19 Mar 2026 23:10:40 -0400 Subject: [PATCH 4/6] Skip typecheck on old versions of node that aren't compatible --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cec200f..586ff82 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ }, "scripts": { "lint": "eslint .", - "typecheck": "tsc", + "typecheck": "node -e \"process.exit(process.release.lts < 'Gallium' ? 0 : 1)\" || tsc", "pretest": "npm run lint && npm run typecheck", "test": "mocha", "version": "git changelog ; git add History.md" From 356607dbac65f15046cdb079c25d72fa10b0d8dd Mon Sep 17 00:00:00 2001 From: Dan MacTough Date: Fri, 20 Mar 2026 07:52:24 -0400 Subject: [PATCH 5/6] Add 'readable' event overload with typed this context Adds typed overloads for `on`, `addListener`, and `once` for the 'readable' event so that `this` is typed as `FeedParser` when using a regular function callback. Co-Authored-By: Claude Sonnet 4.6 --- index.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.d.ts b/index.d.ts index 16c63db..6e581af 100644 --- a/index.d.ts +++ b/index.d.ts @@ -13,14 +13,17 @@ declare class FeedParser extends stream.Transform { resumeSaxError(): void; on(event: 'meta', listener: (meta: FeedParser.Meta) => void): this; + on(event: 'readable', listener: (this: FeedParser) => void): this; on(event: 'error', listener: (error: Error) => void): this; on(event: string, listener: (...args: any[]) => void): this; addListener(event: 'meta', listener: (meta: FeedParser.Meta) => void): this; + addListener(event: 'readable', listener: (this: FeedParser) => void): this; addListener(event: 'error', listener: (error: Error) => void): this; addListener(event: string, listener: (...args: any[]) => void): this; once(event: 'meta', listener: (meta: FeedParser.Meta) => void): this; + once(event: 'readable', listener: (this: FeedParser) => void): this; once(event: 'error', listener: (error: Error) => void): this; once(event: string, listener: (...args: any[]) => void): this; } From 3892eb23e8a08020aad8edafa8e9c3ec2aa87856 Mon Sep 17 00:00:00 2001 From: Dan MacTough Date: Fri, 20 Mar 2026 16:17:37 -0400 Subject: [PATCH 6/6] Add TypeScript type support for development Co-Authored-By: Claude Sonnet 4.6 --- lib/feedparser/index.js | 124 ++++++++++++++++++++++++++++++++++++---- lib/utils.js | 30 ++++++---- package-lock.json | 23 ++++++++ package.json | 5 +- tsconfig.src.json | 16 ++++++ 5 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 tsconfig.src.json diff --git a/lib/feedparser/index.js b/lib/feedparser/index.js index 916356f..da2861c 100644 --- a/lib/feedparser/index.js +++ b/lib/feedparser/index.js @@ -55,8 +55,8 @@ var sax = require('sax') * - generator {String} * - categories {Array} * - * @param {Object} options - * @api public + * @this {FeedParserInstance} + * @param {import('../../index').Options} [options] */ function FeedParser (options) { if (!(this instanceof FeedParser)) return new FeedParser(options); @@ -72,9 +72,13 @@ function FeedParser (options) { if (!('normalize' in this.options)) this.options.normalize = true; if (!('addmeta' in this.options)) this.options.addmeta = true; if (!('resume_saxerror' in this.options)) this.options.resume_saxerror = true; + // MAX_BUFFER_LENGTH is not part of the public API of sax, but we need to be + // able to handle nodes that are larger than the 64K default if ('MAX_BUFFER_LENGTH' in this.options) { + // @ts-expect-error - private API of sax sax.MAX_BUFFER_LENGTH = this.options.MAX_BUFFER_LENGTH; // set to Infinity to have unlimited buffers } else { + // @ts-expect-error sax.MAX_BUFFER_LENGTH = 16 * 1024 * 1024; // 16M versus the 64K default } if (this.options.feedurl) this.xmlbase.unshift({ '#name': 'xml', '#': this.options.feedurl}); @@ -96,6 +100,7 @@ util.inherits(FeedParser, TransformStream); * * Initializes the class-variables */ +/** @this {FeedParserInstance} */ FeedParser.prototype.init = function (){ this.meta = { '#ns': [], @@ -113,6 +118,7 @@ FeedParser.prototype.init = function (){ this.errors = []; }; +/** @this {FeedParserInstance} */ FeedParser.prototype.handleEnd = function (){ // We made it to the end without throwing, but let's make sure we were actually // parsing a feed @@ -123,6 +129,7 @@ FeedParser.prototype.handleEnd = function (){ this.push(null); }; +/** @this {FeedParserInstance} */ FeedParser.prototype.handleSaxError = function (e) { this.emit('error', e); if (this.options.resume_saxerror) { @@ -130,6 +137,7 @@ FeedParser.prototype.handleSaxError = function (e) { } }; +/** @this {FeedParserInstance} */ FeedParser.prototype.resumeSaxError = function () { if (this.stream._parser) { this.stream._parser.error = null; @@ -137,12 +145,17 @@ FeedParser.prototype.resumeSaxError = function () { } }; +/** @this {FeedParserInstance} */ FeedParser.prototype.handleError = function (e){ this.emit('error', e); }; // parses the xml declaration, which looks like: // +/** + * @this {FeedParserInstance} + * @param {SaxProcessingInstruction} node + */ FeedParser.prototype.handleProcessingInstruction = function (node) { if (node.name === 'xml') { this.meta['#xml'] = node.body.trim().split(/\s+/).reduce(function (map, attr) { @@ -155,6 +168,10 @@ FeedParser.prototype.handleProcessingInstruction = function (node) { } }; +/** + * @this {FeedParserInstance} + * @param {import('sax').QualifiedTag} node + */ FeedParser.prototype.handleOpenTag = function (node){ var n = {}; n['#name'] = node.name; // Avoid namespace collissions later... @@ -204,6 +221,7 @@ FeedParser.prototype.handleOpenTag = function (node){ this.stack.unshift(n); }; +/** @this {FeedParserInstance} */ FeedParser.prototype.handleCloseTag = function (el){ var node = { '#name': el, @@ -355,6 +373,10 @@ FeedParser.prototype.handleCloseTag = function (el){ } }; +/** + * @this {FeedParserInstance} + * @param {string} text + */ FeedParser.prototype.handleText = function (text){ if (this.in_xhtml) { this.xhtml['#'] += text; @@ -369,6 +391,12 @@ FeedParser.prototype.handleText = function (text){ } }; +/** + * @this {FeedParserInstance} + * @param {Object.} attrs + * @param {string} el + * @returns {Object.} + */ FeedParser.prototype.handleAttributes = function handleAttributes (attrs, el) { /* * Using the sax.js option { xmlns: true } @@ -382,14 +410,14 @@ FeedParser.prototype.handleAttributes = function handleAttributes (attrs, el) { */ var basepath = '' - , simplifiedAttributes = {} + , simplifiedAttributes = /** @type {Object.} */ ({}) ; if (this.xmlbase && this.xmlbase.length) { basepath = this.xmlbase[0]['#']; } - Object.keys(attrs).forEach(function(key){ + Object.keys(attrs).forEach(/** @this {FeedParserInstance} */ function(key){ var attr = attrs[key] , ns = {} , prefix = '' @@ -425,6 +453,13 @@ FeedParser.prototype.handleAttributes = function handleAttributes (attrs, el) { return simplifiedAttributes; }; +/** + * @this {FeedParserInstance} + * @param {ParsedNode} node + * @param {import('../../index').Type} type + * @param {import('../../index').Options} options + * @returns {Object} + */ FeedParser.prototype.handleMeta = function handleMeta (node, type, options) { if (!type || !node) return {}; @@ -772,6 +807,13 @@ FeedParser.prototype.handleMeta = function handleMeta (node, type, options) { return meta; }; +/** + * @this {FeedParserInstance} + * @param {ParsedNode} node + * @param {import('../../index').Type} type + * @param {import('../../index').Options} options + * @returns {Object} + */ FeedParser.prototype.handleItem = function handleItem (node, type, options){ if (!type || !node) return {}; @@ -830,7 +872,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){ if (link['@']['rel'] == 'self' && !item.link) item.link = link['@']['href']; if (link['@']['rel'] == 'replies') item.comments = link['@']['href']; if (link['@']['rel'] == 'enclosure') { - enclosure = {}; + enclosure = /** @type {import('../../index').Enclosure} */ ({}); enclosure.url = link['@']['href']; enclosure.type = _.get(link['@'], 'type'); enclosure.length = _.get(link['@'], 'length'); @@ -853,7 +895,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){ if (el['@']['rel'] == 'self' && !item.link) item.link = el['@']['href']; if (el['@']['rel'] == 'replies') item.comments = el['@']['href']; if (el['@']['rel'] == 'enclosure') { - enclosure = {}; + enclosure = /** @type {import('../../index').Enclosure} */ ({}); enclosure.url = el['@']['href']; enclosure.type = _.get(el['@'], 'type'); enclosure.length = _.get(el['@'], 'length'); @@ -932,7 +974,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){ case('enclosure'): if (Array.isArray(el)) { el.forEach(function (enc){ - enclosure = {}; + enclosure = /** @type {import('../../index').Enclosure} */ ({}); enclosure.url = _.get(enc['@'], 'url'); enclosure.type = _.get(enc['@'], 'type'); enclosure.length = _.get(enc['@'], 'length'); @@ -943,7 +985,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){ } }); } else { - enclosure = {}; + enclosure = /** @type {import('../../index').Enclosure} */ ({}); enclosure.url = _.get(el['@'], 'url'); enclosure.type = _.get(el['@'], 'type'); enclosure.length = _.get(el['@'], 'length'); @@ -958,7 +1000,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){ var optionalAttributes = ['bitrate', 'framerate', 'samplingrate', 'duration', 'height', 'width']; if (Array.isArray(el)) { el.forEach(function (enc){ - enclosure = {}; + enclosure = /** @type {import('../../index').Enclosure} */ ({}); enclosure.url = _.get(enc['@'], 'url'); enclosure.type = _.get(enc['@'], 'type') || _.get(enc['@'], 'medium'); enclosure.length = _.get(enc['@'], 'filesize'); @@ -976,7 +1018,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){ } }); } else { - enclosure = {}; + enclosure = /** @type {import('../../index').Enclosure} */ ({}); enclosure.url = _.get(el['@'], 'url'); enclosure.type = _.get(el['@'], 'type') || _.get(el['@'], 'medium'); enclosure.length = _.get(el['@'], 'filesize'); @@ -1112,6 +1154,7 @@ FeedParser.prototype.handleItem = function handleItem (node, type, options){ }; // Naive Stream API +/** @this {FeedParserInstance} */ FeedParser.prototype._transform = function (data, encoding, done) { try { this.stream.write(data); @@ -1123,6 +1166,7 @@ FeedParser.prototype._transform = function (data, encoding, done) { } }; +/** @this {FeedParserInstance} */ FeedParser.prototype._flush = function (done) { try { this.stream.end(); @@ -1133,4 +1177,64 @@ FeedParser.prototype._flush = function (done) { } }; +/** + * @typedef {Object} ParsedNode + * The internal accumulator object that handleOpenTag builds and pushes onto + * this.stack. Keys accumulate as child elements are parsed. String keys + * '#name', '#prefix', '#local', '#uri' hold element namespace info; '@' holds + * simplified attributes; '#' holds text content. Named keys hold child element + * values which may be strings, nested ParsedNodes, or arrays of either. + */ + +/** + * @typedef {Object} XmlBaseEntry + * An entry in the this.xmlbase stack. The '#name' key holds the element name + * that established the base URL; the '#' key holds the xml:base URL value. + */ + +/** + * @typedef {Object} SaxProcessingInstruction + * Payload of the sax 'processinginstruction' event. + * @property {string} name - Processing instruction target, e.g. "xml" + * @property {string} body - The rest of the processing instruction content + */ + +/** + * @typedef {Object} AddressParserResult + * Shape of each item returned by the addressparser module. + * @property {string} [name] + * @property {string} [address] + */ + +/** + * @typedef {Object} FeedParserState + * Instance properties set up by FeedParser.prototype.init and the constructor. + * @property {Object} meta - Parsed feed metadata; shape evolves during parsing + * @property {import('../../index').Options} options + * @property {Object.} _namespaces + * @property {boolean} _emitted_meta + * @property {Array.} stack + * @property {Array.} xmlbase + * @property {boolean} in_xhtml + * @property {Object} xhtml + * @property {Error[]} errors + * @property {import('sax').SAXStream} stream - The underlying sax stream + * @property {function(): void} init + * @property {function(): void} handleEnd + * @property {function(Error): void} handleSaxError + * @property {function(): void} resumeSaxError + * @property {function(Error): void} handleError + * @property {function(SaxProcessingInstruction): void} handleProcessingInstruction + * @property {function(import('sax').QualifiedTag): void} handleOpenTag + * @property {function(string): void} handleCloseTag + * @property {function(string): void} handleText + * @property {function(Object., string): Object.} handleAttributes + * @property {function(ParsedNode, import('../../index').Type, import('../../index').Options): Object} handleMeta + * @property {function(ParsedNode, import('../../index').Type, import('../../index').Options): Object} handleItem + */ + +/** + * @typedef {import('readable-stream').Transform & FeedParserState} FeedParserInstance + */ + exports = module.exports = FeedParser; diff --git a/lib/utils.js b/lib/utils.js index 86a8a10..5142716 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -20,8 +20,9 @@ var _get = require('lodash.get'); * // => 'baz' * * @param {Object} obj - * @param {String} [subkey="#"] By default, use the '#' key, but you may pass any key you like - * @return Returns the value of the selected key or 'null' if undefined. + * @param {string} [subkey="#"] By default, use the '#' key, but you may pass any key you like + * @param {*} [defaultValue=null] + * @returns {*} The value of the selected key, or null if undefined. * @private */ function get(obj, subkey, defaultValue) { @@ -44,6 +45,8 @@ exports.get = get; /** * Safely trim a value if it's a String + * @param {*} val + * @returns {*} * @private */ function safeTrim (val) { @@ -58,6 +61,9 @@ exports.safeTrim = safeTrim; * Resolve a URL against a base URL, returning the original pathUrl if * either parameter isn't provided or if the URL is not resolvable (e.g. * tag: URIs and other non-http schemes that the URL constructor rejects). + * @param {string} baseUrl + * @param {string} pathUrl + * @returns {string} * @private */ function resolve (baseUrl, pathUrl) { @@ -72,7 +78,8 @@ exports.resolve = resolve; /* * Check whether a given uri is an absolute URL - * @param {String} uri + * @param {string} uri + * @returns {boolean} * @private */ function isAbsoluteUrl (uri) { @@ -88,9 +95,9 @@ exports.isAbsoluteUrl = isAbsoluteUrl; /* * Check whether a given namespace URI matches the given default * - * @param {String} URI - * @param {String} default, e.g., 'atom' - * @return {Boolean} + * @param {string} uri + * @param {string} def - e.g., 'atom' + * @returns {boolean} * @private */ function nslookup (uri, def) { @@ -101,8 +108,8 @@ exports.nslookup = nslookup; /* * Return the "default" namespace prefix for a given namespace URI * - * @param {String} URI - * @return {String} + * @param {string} uri + * @returns {string} * @private */ function nsprefix (uri) { @@ -114,8 +121,8 @@ exports.nsprefix = nsprefix; * Walk a node and re-resolve the urls using the given baseurl * * @param {Object} node - * @param {String} baseurl - * @return {Object} modified node + * @param {string} baseurl + * @returns {Object|false} modified node, or false if no node or baseurl * @private */ function reresolve (node, baseurl) { @@ -173,7 +180,8 @@ exports.reresolve = reresolve; * Pulled out of node-resanitize because it was all that was being used * and it's way lighter... * - * @param {String} str + * @param {string} str + * @returns {string} * @private */ function stripHtml (str) { diff --git a/package-lock.json b/package-lock.json index cebedd8..46a783a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,8 @@ }, "devDependencies": { "@types/node": "^25.5.0", + "@types/readable-stream": "^2.3.15", + "@types/sax": "^1.2.7", "eslint": "^6.8.0", "iconv-lite": "^0.5.1", "mocha": "^7.1.2", @@ -76,6 +78,27 @@ "undici-types": "~7.18.0" } }, + "node_modules/@types/readable-stream": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/acorn": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", diff --git a/package.json b/package.json index 586ff82..0a242c8 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,8 @@ }, "devDependencies": { "@types/node": "^25.5.0", + "@types/readable-stream": "^2.3.15", + "@types/sax": "^1.2.7", "eslint": "^6.8.0", "iconv-lite": "^0.5.1", "mocha": "^7.1.2", @@ -58,7 +60,8 @@ "scripts": { "lint": "eslint .", "typecheck": "node -e \"process.exit(process.release.lts < 'Gallium' ? 0 : 1)\" || tsc", - "pretest": "npm run lint && npm run typecheck", + "typecheck:src": "node -e \"process.exit(process.release.lts < 'Gallium' ? 0 : 1)\" || tsc --project tsconfig.src.json", + "pretest": "npm run lint && npm run typecheck && npm run typecheck:src", "test": "mocha", "version": "git changelog ; git add History.md" }, diff --git a/tsconfig.src.json b/tsconfig.src.json new file mode 100644 index 0000000..960cc43 --- /dev/null +++ b/tsconfig.src.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "noEmit": true, + "checkJs": true, + "allowJs": true, + "strict": false, + "noImplicitAny": false, + "noImplicitThis": false, + "strictNullChecks": false, + "module": "commonjs", + "lib": ["es6"], + "target": "es6" + }, + "include": ["index.js", "lib/**/*.js"], + "exclude": ["node_modules", "test", "bin"] +}