Skip to content

Commit 25dcb30

Browse files
authored
types(ses): move comments where typedoc can see them (#2205)
closes: #XXXX refs: #XXXX ## Description Before this PR, there was a lot of redundancy between `ses/src/error/types.js` and `ses/types.d.ts` in typing the `ses/src/error/assert.js` module. This cased several problems: - `yarn docs` was picking up only the doc-comments in the `ses/types.d.ts` file, dropping all the useful doc-comments in the `ses/src/error/types.js` file. - redundant expression of the same meaning in different syntaxes is a horrible maintenance burden. They will get out of sync. (This happened to me at first in a recent PR when I added the `sanitize` option.) - The types as exported by the `ses/src/error/types.js` are ambient, which we're trying to reduce and eventually eliminate. The types as exported by `ses/types.d.ts` are not ambient, requiring them to be explicitly imported. Which is finally pleasant with `@import`! - By importing types with `@import` rather than `@typedef {import('...). ...} ...`, the documentation attached to the types is visible in the IDE (at least vscode) and more usage locations. This PR should be a pure refactoring from a runtime semantics POV. No behavior of any code should be any different. All the changes are only to static information: types and doc-comments. ### Security Considerations As mentioned above, redundant expression of the same meaning in different syntaxes ... will get out of sync. This makes code more confusing to review reliably, so this PR helps security. ### Scaling Considerations none ### Documentation Considerations Most of the point! After this PR, `yarn docs` produces much better documentation for the types related to the assert.js module. It is thus also an attempt to get one's feet wet at making this kind of improvement. However, even after this PR, the output of `yarn docs` still leaves plenty of room for further improvement ;) ### Testing Considerations Probably none. Conceivably more type tests could be relevant, but if so it is not apparent to me. ### Compatibility Considerations Because this PR (allegedly) changes no runtime behavior, it cannot cause any incompat runtime behavior. However, client code is also a client of the exported types. Changing types from ambient to non-ambient risks breaking whether old clients continue to pass static type checking. Which brings us back to the oft-repeated question for reviewers: ***Do I need to mark this PR with a `!`?*** ### Upgrade Considerations none. - [ ] Includes `*BREAKING*:` in the commit message with migration instructions for any breaking change. - [ ] Updates `NEWS.md` for user-facing changes.
1 parent 5a979d3 commit 25dcb30

File tree

6 files changed

+298
-419
lines changed

6 files changed

+298
-419
lines changed

packages/ses/src/error/assert.js

+25-25
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ import './types.js';
4646
import './internal-types.js';
4747
import { makeNoteLogArgsArrayKit } from './note-log-args.js';
4848

49+
/**
50+
* @import {BaseAssert,
51+
* Assert,
52+
* AssertionFunctions,
53+
* AssertionUtilities,
54+
* StringablePayload,
55+
* DetailsToken,
56+
* MakeAssert,
57+
* } from '../../types.js'
58+
*/
59+
4960
// For our internal debugging purposes, uncomment
5061
// const internalDebugConsole = console;
5162

@@ -54,7 +65,7 @@ import { makeNoteLogArgsArrayKit } from './note-log-args.js';
5465
/** @type {WeakMap<StringablePayload, any>} */
5566
const declassifiers = new WeakMap();
5667

57-
/** @type {AssertQuote} */
68+
/** @type {AssertionUtilities['quote']} */
5869
const quote = (payload, spaces = undefined) => {
5970
const result = freeze({
6071
toString: freeze(() => bestEffortStringify(payload, spaces)),
@@ -67,19 +78,7 @@ freeze(quote);
6778
const canBeBare = freeze(/^[\w:-]( ?[\w:-])*$/);
6879

6980
/**
70-
* Embed a string directly into error details without wrapping punctuation.
71-
* To avoid injection attacks that exploit quoting confusion, this must NEVER
72-
* be used with data that is possibly attacker-controlled.
73-
* As a further safeguard, we fall back to quoting any input that is not a
74-
* string of sufficiently word-like parts separated by isolated spaces (rather
75-
* than throwing an exception, which could hide the original problem for which
76-
* explanatory details are being constructed---i.e., ``` assert.details`...` ```
77-
* should never be the source of a new exception, nor should an attempt to
78-
* render its output, although we _could_ instead decide to handle the latter
79-
* by inline replacement similar to that of `bestEffortStringify` for producing
80-
* rendered messages like `(an object) was tagged "[Unsafe bare string]"`).
81-
*
82-
* @type {AssertQuote}
81+
* @type {AssertionUtilities['bare']}
8382
*/
8483
const bare = (payload, spaces = undefined) => {
8584
if (typeof payload !== 'string' || !regexpTest(canBeBare, payload)) {
@@ -165,7 +164,7 @@ freeze(DetailsTokenProto.toString);
165164
* of them should be uses where the template literal has no redacted
166165
* substitution values. In those cases, the two are equivalent.
167166
*
168-
* @type {DetailsTag}
167+
* @type {AssertionUtilities['details']}
169168
*/
170169
const redactedDetails = (template, ...args) => {
171170
// Keep in mind that the vast majority of calls to `details` creates
@@ -174,7 +173,7 @@ const redactedDetails = (template, ...args) => {
174173
// all the work to happen only if needed, for example, if an assertion fails.
175174
const detailsToken = freeze({ __proto__: DetailsTokenProto });
176175
weakmapSet(hiddenDetailsMap, detailsToken, { template, args });
177-
return detailsToken;
176+
return /** @type {DetailsToken} */ (/** @type {unknown} */ (detailsToken));
178177
};
179178
freeze(redactedDetails);
180179

@@ -189,7 +188,7 @@ freeze(redactedDetails);
189188
* of safety. `unredactedDetails` also sacrifices the speed of `details`,
190189
* which is usually fine in debugging and testing.
191190
*
192-
* @type {DetailsTag}
191+
* @type {AssertionUtilities['details']}
193192
*/
194193
const unredactedDetails = (template, ...args) => {
195194
args = arrayMap(args, arg =>
@@ -286,7 +285,7 @@ const tagError = (err, optErrorName = err.name) => {
286285
*
287286
* @param {Error} error
288287
*/
289-
const sanitizeError = error => {
288+
export const sanitizeError = error => {
290289
const descs = getOwnPropertyDescriptors(error);
291290
const {
292291
name: _nameDesc,
@@ -322,7 +321,7 @@ const sanitizeError = error => {
322321
};
323322

324323
/**
325-
* @type {AssertMakeError}
324+
* @type {AssertionUtilities['error']}
326325
*/
327326
const makeError = (
328327
optDetails = redactedDetails`Assert failed`,
@@ -397,7 +396,7 @@ const { addLogArgs, takeLogArgsArray } = makeNoteLogArgsArrayKit();
397396
*/
398397
const hiddenNoteCallbackArrays = new WeakMap();
399398

400-
/** @type {AssertNote} */
399+
/** @type {AssertionUtilities['note']} */
401400
const note = (error, detailsNote) => {
402401
if (typeof detailsNote === 'string') {
403402
// If it is a string, use it as the literal part of the template so
@@ -478,21 +477,22 @@ const makeAssert = (optRaise = undefined, unredacted = false) => {
478477
const details = unredacted ? unredactedDetails : redactedDetails;
479478
const assertFailedDetails = details`Check failed`;
480479

481-
/** @type {AssertFail} */
480+
/** @type {AssertionFunctions['fail']} */
482481
const fail = (
483482
optDetails = assertFailedDetails,
484483
errConstructor = undefined,
485484
options = undefined,
486485
) => {
487486
const reason = makeError(optDetails, errConstructor, options);
488487
if (optRaise !== undefined) {
488+
// @ts-ignore returns `never` doesn't mean it isn't callable
489489
optRaise(reason);
490490
}
491491
throw reason;
492492
};
493493
freeze(fail);
494494

495-
/** @type {FailTag} */
495+
/** @type {AssertionUtilities['Fail']} */
496496
const Fail = (template, ...args) => fail(details(template, ...args));
497497

498498
// Don't freeze or export `baseAssert` until we add methods.
@@ -508,7 +508,7 @@ const makeAssert = (optRaise = undefined, unredacted = false) => {
508508
flag || fail(optDetails, errConstructor, options);
509509
}
510510

511-
/** @type {AssertEqual} */
511+
/** @type {AssertionFunctions['equal']} */
512512
const equal = (
513513
actual,
514514
expected,
@@ -525,7 +525,7 @@ const makeAssert = (optRaise = undefined, unredacted = false) => {
525525
};
526526
freeze(equal);
527527

528-
/** @type {AssertTypeof} */
528+
/** @type {AssertionFunctions['typeof']} */
529529
const assertTypeof = (specimen, typename, optDetails) => {
530530
// This will safely fall through if typename is not a string,
531531
// which is what we want.
@@ -544,7 +544,7 @@ const makeAssert = (optRaise = undefined, unredacted = false) => {
544544
};
545545
freeze(assertTypeof);
546546

547-
/** @type {AssertString} */
547+
/** @type {AssertionFunctions['string']} */
548548
const assertString = (specimen, optDetails = undefined) =>
549549
assertTypeof(specimen, 'string', optDetails);
550550

packages/ses/src/error/fatal-assert.js

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { freeze } from '../commons.js';
55
import { makeAssert } from './assert.js';
66
import './types.js';
77

8+
/** @import {Assert} from '../../types.js' */
9+
810
let abandon;
911
// Sniff for host-provided functions for terminating the enclosing UOPT (see
1012
// below). Currently it only checks for the `process.abort` or `process.exit`

packages/ses/src/error/stringify-utils.js

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
toStringTagSymbol,
2222
} from '../commons.js';
2323

24+
/** @import {StringablePayload} from '../../types.js' */
25+
2426
/**
2527
* Joins English terms with commas and an optional conjunction.
2628
*

0 commit comments

Comments
 (0)