diff --git a/.eslintrc.js b/.eslintrc.js index 1284aa91..728e1be6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -15,7 +15,8 @@ module.exports = { "prettier", ], plugins: [ - "@typescript-eslint", + "@typescript-eslint/eslint-plugin", + "eslint-plugin-tsdoc" ], parser: "@typescript-eslint/parser", parserOptions: { @@ -38,6 +39,8 @@ module.exports = { "import/no-cycle": "error", "import/no-default-export": "warn", + "tsdoc/syntax": "warn", + "@typescript-eslint/await-thenable": "warn", "@typescript-eslint/array-type": ["warn", { default: "generic" }], "@typescript-eslint/naming-convention": [ diff --git a/.vscode/settings.json b/.vscode/settings.json index 9a09c382..0bfd00ab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,8 @@ "editor.tabSize": 2, "editor.codeActionsOnSave": { "source.fixAll.eslint": true - } + }, + "cSpell.words": [ + "tsdoc" + ] } diff --git a/package-lock.json b/package-lock.json index d2850a52..603278c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "eslint": "latest", "eslint-config-prettier": "latest", "eslint-plugin-import": "latest", + "eslint-plugin-tsdoc": "latest", "ieee754": "latest", "karma": "latest", "karma-chrome-launcher": "latest", @@ -142,6 +143,37 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@microsoft/tsdoc": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz", + "integrity": "sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw==", + "dev": true + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.1.tgz", + "integrity": "sha512-2RqkwiD4uN6MLnHFljqBlZIXlt/SaUT6cuogU1w2ARw4nKuuppSmR0+s+NC+7kXBQykd9zzu0P4HtBpZT5zBpQ==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "0.14.1", + "ajv": "~6.12.6", + "jju": "~1.4.0", + "resolve": "~1.19.0" + } + }, + "node_modules/@microsoft/tsdoc-config/node_modules/resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1813,6 +1845,16 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/eslint-plugin-tsdoc": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.16.tgz", + "integrity": "sha512-F/RWMnyDQuGlg82vQEFHQtGyWi7++XJKdYNn0ulIbyMOFqYIjoJOUdE6olORxgwgLkpJxsCJpJbTHgxJ/ggfXw==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "0.14.1", + "@microsoft/tsdoc-config": "0.16.1" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -3063,6 +3105,12 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=", + "dev": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -5840,6 +5888,36 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@microsoft/tsdoc": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz", + "integrity": "sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw==", + "dev": true + }, + "@microsoft/tsdoc-config": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.1.tgz", + "integrity": "sha512-2RqkwiD4uN6MLnHFljqBlZIXlt/SaUT6cuogU1w2ARw4nKuuppSmR0+s+NC+7kXBQykd9zzu0P4HtBpZT5zBpQ==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.14.1", + "ajv": "~6.12.6", + "jju": "~1.4.0", + "resolve": "~1.19.0" + }, + "dependencies": { + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7137,6 +7215,16 @@ } } }, + "eslint-plugin-tsdoc": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.16.tgz", + "integrity": "sha512-F/RWMnyDQuGlg82vQEFHQtGyWi7++XJKdYNn0ulIbyMOFqYIjoJOUdE6olORxgwgLkpJxsCJpJbTHgxJ/ggfXw==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.14.1", + "@microsoft/tsdoc-config": "0.16.1" + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -8005,6 +8093,12 @@ } } }, + "jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=", + "dev": true + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", diff --git a/package.json b/package.json index 109661a0..d125a84f 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "eslint": "latest", "eslint-config-prettier": "latest", "eslint-plugin-import": "latest", + "eslint-plugin-tsdoc": "latest", "ieee754": "latest", "karma": "latest", "karma-chrome-launcher": "latest", diff --git a/src/Decoder.ts b/src/Decoder.ts index 4c02be3a..eb6fa157 100644 --- a/src/Decoder.ts +++ b/src/Decoder.ts @@ -118,8 +118,8 @@ export class Decoder { } /** - * @throws {DecodeError} - * @throws {RangeError} + * @throws {@link DecodeError} + * @throws {@link RangeError} */ public decode(buffer: ArrayLike | BufferSource): unknown { this.reinitializeState(); diff --git a/src/decode.ts b/src/decode.ts index 267b7d92..30a88ab3 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -47,6 +47,9 @@ export const defaultDecodeOptions: DecodeOptions = {}; * * This is a synchronous decoding function. * See other variants for asynchronous decoding: {@link decodeAsync()}, {@link decodeStream()}, or {@link decodeArrayStream()}. + * + * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty. + * @throws {@link DecodeError} if the buffer contains invalid data. */ export function decode( buffer: ArrayLike | BufferSource, @@ -67,6 +70,9 @@ export function decode( /** * It decodes multiple MessagePack objects in a buffer. * This is corresponding to {@link decodeMultiStream()}. + * + * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty. + * @throws {@link DecodeError} if the buffer contains invalid data. */ export function decodeMulti( buffer: ArrayLike | BufferSource, diff --git a/src/decodeAsync.ts b/src/decodeAsync.ts index 76377367..ee9922fa 100644 --- a/src/decodeAsync.ts +++ b/src/decodeAsync.ts @@ -5,7 +5,11 @@ import type { ReadableStreamLike } from "./utils/stream"; import type { DecodeOptions } from "./decode"; import type { SplitUndefined } from "./context"; -export async function decodeAsync( +/** + * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty. + * @throws {@link DecodeError} if the buffer contains invalid data. + */ + export async function decodeAsync( streamLike: ReadableStreamLike | BufferSource>, options: DecodeOptions> = defaultDecodeOptions as any, ): Promise { @@ -23,7 +27,11 @@ export async function decodeAsync( return decoder.decodeAsync(stream); } -export function decodeArrayStream( +/** + * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty. + * @throws {@link DecodeError} if the buffer contains invalid data. + */ + export function decodeArrayStream( streamLike: ReadableStreamLike | BufferSource>, options: DecodeOptions> = defaultDecodeOptions as any, ): AsyncGenerator { @@ -42,6 +50,10 @@ export function decodeArrayStream( return decoder.decodeArrayStream(stream); } +/** + * @throws {@link RangeError} if the buffer is incomplete, including the case where the buffer is empty. + * @throws {@link DecodeError} if the buffer contains invalid data. + */ export function decodeMultiStream( streamLike: ReadableStreamLike | BufferSource>, options: DecodeOptions> = defaultDecodeOptions as any, diff --git a/test/edge-cases.test.ts b/test/edge-cases.test.ts index 577aea98..96e17462 100644 --- a/test/edge-cases.test.ts +++ b/test/edge-cases.test.ts @@ -1,7 +1,7 @@ // kind of hand-written fuzzing data // any errors should not break Encoder/Decoder instance states import assert from "assert"; -import { encode, decode, Encoder, Decoder } from "../src"; +import { encode, decodeAsync, decode, Encoder, Decoder } from "../src"; import { DataViewIndexOutOfBoundsError } from "../src/Decoder"; function testEncoder(encoder: Encoder): void { @@ -147,4 +147,22 @@ describe("edge cases", () => { testDecoder(decoder); }); }); + + context("try to decode an empty input", () => { + it("throws RangeError (synchronous)", () => { + assert.throws(() => { + decode([]); + }, RangeError); + }); + + it("throws RangeError (asynchronous)", async () => { + const createStream = async function* () { + yield []; + }; + + assert.rejects(async () => { + await decodeAsync(createStream()); + }, RangeError); + }); + }); });