Skip to content

Commit 8708143

Browse files
committed
Merge branch 'library-mode'
2 parents b03a13b + 262a89c commit 8708143

18 files changed

+233
-350
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666
"postbuild": "node scripts/replace_application_version.js",
6767
"build_and_test": "npm run build && npm run test",
6868
"lint": "tslint --project .",
69-
"prepublishOnly": "npm run lint && npm run build_and_test",
69+
"prepublishOnly": "node scripts/set_strict.js false && npm run lint && npm run build_and_test",
70+
"postpublish": "node scripts/set_strict.js true",
7071
"prepare": "npm run build",
7172
"clean": "rm -rf node_modules package-lock.json lib coverage"
7273
},

scripts/set_strict.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @ts-check
2+
// Sets the Strict type that TypeDoc uses to enable overloads for consumers only.
3+
// See the rationale in src/lib/utils/index.ts
4+
5+
const fs = require('fs-extra');
6+
const { join } = require('path');
7+
8+
const file = join(__dirname, '../src/lib/utils/index.ts');
9+
10+
const isStrict = process.argv[2] === 'true'
11+
12+
fs.readFile(file, { encoding: 'utf-8'})
13+
.then(text => fs.writeFile(file, text.replace(/type Strict =.*/, `type Strict = ${isStrict};`)))
14+
.catch(reason => {
15+
console.error(reason);
16+
process.exit(1);
17+
});
18+

src/lib/application.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
DUMMY_APPLICATION_OWNER
2525
} from './utils/component';
2626
import { Options, BindOption } from './utils';
27-
import { TypeDocAndTSOptions } from './utils/options/declaration';
27+
import { TypeDocAndTSOptions, TypeDocOptions } from './utils/options/declaration';
2828

2929
/**
3030
* The default TypeDoc main application class.
@@ -115,7 +115,13 @@ export class Application extends ChildableComponent<
115115
* @param options The desired options to set.
116116
*/
117117
bootstrap(options: Partial<TypeDocAndTSOptions> = {}): { hasErrors: boolean, inputFiles: string[] } {
118-
this.options.setValues(options); // Ignore result, plugins might declare an option
118+
for (const [key, val] of Object.entries(options)) {
119+
try {
120+
this.options.setValue(key as keyof TypeDocOptions, val);
121+
} catch {
122+
// Ignore errors, plugins haven't been loaded yet and may declare an option.
123+
}
124+
}
119125
this.options.read(new Logger());
120126

121127
const logger = this.loggerType;
@@ -130,11 +136,13 @@ export class Application extends ChildableComponent<
130136
this.plugins.load();
131137

132138
this.options.reset();
133-
this.options.setValues(options).mapErr(errors => {
134-
for (const error of errors) {
139+
for (const [key, val] of Object.entries(options)) {
140+
try {
141+
this.options.setValue(key as keyof TypeDocOptions, val);
142+
} catch (error) {
135143
this.logger.error(error.message);
136144
}
137-
});
145+
}
138146
this.options.read(this.logger);
139147

140148
return {

src/lib/utils/index.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
/**
2+
* This type provides a flag that can be used to turn off more lax overloads intended for
3+
* plugin use only to catch type errors in the TypeDoc codebase. The prepublishOnly npm
4+
* script will be used to switch this flag to false when publishing, then immediately back
5+
* to true after a successful publish.
6+
*/
7+
type InternalOnly = true;
8+
9+
/**
10+
* Helper type to convert `T` to `F` if strict mode is on.
11+
*
12+
* Can be used in overloads to map a parameter type to `never`. For example, the
13+
* following function will work with any string argument, but to improve the type safety
14+
* of internal code, we only ever want to pass 'a' or 'b' to it. Plugins on the other
15+
* hand need to be able to pass any string to it. Overloads similar to this are used
16+
* in the {@link Options} class.
17+
*
18+
* ```ts
19+
* function over(flag: 'a' | 'b'): string
20+
* function over(flag: IfStrict<string, never>): string
21+
* function over(flag: string): string { return flag }
22+
* ```
23+
*/
24+
export type IfInternal<T, F> = InternalOnly extends true ? T : F;
25+
26+
/**
27+
* Helper type to convert `T` to `never` if strict mode is on.
28+
*
29+
* See {@link IfInternal} for the rationale.
30+
*/
31+
export type NeverIfInternal<T> = IfInternal<never, T>;
32+
133
export {
234
Options,
335
ParameterType,
@@ -18,4 +50,3 @@ export {
1850
} from './fs';
1951
export { Logger, LogLevel, ConsoleLogger, CallbackLogger } from './loggers';
2052
export { PluginHost } from './plugins';
21-
export { Result } from './result';

src/lib/utils/options/declaration.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as _ from 'lodash';
22
import { CompilerOptions } from 'typescript';
3-
import { Result } from '../result';
43
import { IgnoredTsOptionKeys } from './sources/typescript';
54

65
/**
@@ -52,6 +51,7 @@ export interface TypeDocOptionMap {
5251
excludeNotExported: boolean;
5352
excludePrivate: boolean;
5453
excludeProtected: boolean;
54+
excludeNotDocumented: boolean;
5555
ignoreCompilerErrors: boolean;
5656
disableSources: boolean;
5757
includes: string;
@@ -249,49 +249,49 @@ export type DeclarationOptionToOptionType<T extends DeclarationOption> =
249249
* @param option The option for which the value should be converted.
250250
* @returns The result of the conversion. Might be the value or an error.
251251
*/
252-
export function convert<T extends DeclarationOption>(value: unknown, option: T): Result<DeclarationOptionToOptionType<T>, string>;
253-
export function convert<T extends DeclarationOption>(value: unknown, option: T): Result<unknown, string> {
252+
export function convert<T extends DeclarationOption>(value: unknown, option: T): DeclarationOptionToOptionType<T>;
253+
export function convert<T extends DeclarationOption>(value: unknown, option: T): unknown {
254254
switch (option.type) {
255255
case undefined:
256256
case ParameterType.String:
257-
return Result.Ok(value == null ? '' : String(value));
257+
return value == null ? '' : String(value);
258258
case ParameterType.Number:
259259
const numberOption = option as NumberDeclarationOption;
260260
const numValue = parseInt(String(value), 10) || 0;
261261
if (!valueIsWithinBounds(numValue, numberOption.minValue, numberOption.maxValue)) {
262-
return Result.Err(getBoundsError(numberOption.name, numberOption.minValue, numberOption.maxValue));
262+
throw new Error(getBoundsError(numberOption.name, numberOption.minValue, numberOption.maxValue));
263263
}
264-
return Result.Ok(numValue);
264+
return numValue;
265265
case ParameterType.Boolean:
266-
return Result.Ok(Boolean(value));
266+
return Boolean(value);
267267
case ParameterType.Array:
268268
if (Array.isArray(value)) {
269-
return Result.Ok(value.map(String));
269+
return value.map(String);
270270
} else if (typeof value === 'string') {
271-
return Result.Ok(value.split(','));
271+
return value.split(',');
272272
}
273-
return Result.Ok([]);
273+
return [];
274274
case ParameterType.Map:
275275
const optionMap = option as MapDeclarationOption<unknown>;
276276
const key = String(value).toLowerCase();
277277
if (optionMap.map instanceof Map) {
278278
if (optionMap.map.has(key)) {
279-
return Result.Ok(optionMap.map.get(key));
279+
return optionMap.map.get(key);
280280
}
281281
if ([...optionMap.map.values()].includes(value)) {
282-
return Result.Ok(value);
282+
return value;
283283
}
284284
} else {
285285
if (optionMap.map.hasOwnProperty(key)) {
286-
return Result.Ok(optionMap.map[key]);
286+
return optionMap.map[key];
287287
}
288288
if (Object.values(optionMap.map).includes(value)) {
289-
return Result.Ok(value);
289+
return value;
290290
}
291291
}
292-
return Result.Err(optionMap.mapError ?? getMapError(optionMap.map, optionMap.name));
292+
throw new Error(optionMap.mapError ?? getMapError(optionMap.map, optionMap.name));
293293
case ParameterType.Mixed:
294-
return Result.Ok(value);
294+
return value;
295295
}
296296
}
297297

src/lib/utils/options/options.ts

Lines changed: 34 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import * as ts from 'typescript';
33

44
import { DeclarationOption, ParameterScope, ParameterType, convert, TypeDocOptions, KeyToDeclaration, TypeDocAndTSOptions, TypeDocOptionMap } from './declaration';
55
import { Logger } from '../loggers';
6-
import { Result, Ok, Err } from '../result';
76
import { insertPrioritySorted } from '../array';
87
import { addTSOptions, addTypeDocOptions } from './sources';
98
import { Application } from '../../..';
9+
import { NeverIfInternal } from '..';
1010

1111
/**
1212
* Describes an option reader that discovers user configuration and converts it to the
@@ -148,7 +148,7 @@ export class Options {
148148
* Adds an option declaration to the container.
149149
* @param declaration
150150
*/
151-
addDeclaration(declaration: Readonly<DeclarationOption>): void;
151+
addDeclaration(declaration: NeverIfInternal<Readonly<DeclarationOption>>): void;
152152

153153
addDeclaration(declaration: Readonly<DeclarationOption>): void {
154154
const names = [declaration.name];
@@ -172,10 +172,15 @@ export class Options {
172172
/**
173173
* Adds the given declarations to the container
174174
* @param declarations
175+
*
176+
* @privateRemarks
177+
* This function explicitly provides a way out of the strict typing enforced
178+
* by {@link addDeclaration}. It should only be used with careful validation
179+
* of the declaration map. Internally, it is only used for adding TS options
175180
*/
176181
addDeclarations(declarations: readonly DeclarationOption[]): void {
177182
for (const decl of declarations) {
178-
this.addDeclaration(decl);
183+
this.addDeclaration(decl as any);
179184
}
180185
}
181186

@@ -217,11 +222,11 @@ export class Options {
217222
* Checks if the given option has a set value or if the value is the default value.
218223
* @param name
219224
*/
220-
isDefault(name: keyof TypeDocAndTSOptions): boolean;
221-
isDefault(name: string): boolean;
225+
isDefault(name: keyof TypeDocOptions): boolean;
226+
isDefault(name: NeverIfInternal<string>): boolean;
222227
isDefault(name: string): boolean {
223228
// getValue will throw if the declaration does not exist.
224-
return this.getValue(name) === this.getDeclaration(name)!.defaultValue;
229+
return this.getValue(name as keyof TypeDocOptions) === this.getDeclaration(name)!.defaultValue;
225230
}
226231

227232
/**
@@ -236,32 +241,18 @@ export class Options {
236241
* @param name
237242
*/
238243
getValue<K extends keyof TypeDocOptions>(name: K): TypeDocOptions[K];
239-
getValue(name: string): unknown;
244+
getValue(name: NeverIfInternal<string>): unknown;
240245
getValue(name: string): unknown {
241-
return this.tryGetValue(name).match({
242-
ok: v => v,
243-
err(err) { throw err; }
244-
});
245-
}
246-
247-
/**
248-
* Tries to get the given option key, returns an [[Ok]] result if the option has been
249-
* declared with a TypeDoc scope, or an [[Err]] result otherwise.
250-
* @param name
251-
*/
252-
tryGetValue<K extends keyof TypeDocOptions>(name: K): Result<TypeDocOptions[K], Error>;
253-
tryGetValue(name: string): Result<unknown, Error>;
254-
tryGetValue(name: string): Result<unknown, Error> {
255246
const declaration = this.getDeclaration(name);
256247
if (!declaration) {
257-
return Err(new Error(`Unknown option '${name}'`));
248+
throw new Error(`Unknown option '${name}'`);
258249
}
259250

260251
if (declaration.scope === ParameterScope.TypeScript) {
261-
return Err(new Error('TypeScript options must be fetched with getCompilerOptions.'));
252+
throw new Error('TypeScript options must be fetched with getCompilerOptions.');
262253
}
263254

264-
return Ok(this._values[declaration.name]);
255+
return this._values[declaration.name];
265256
}
266257

267258
/**
@@ -272,47 +263,35 @@ export class Options {
272263
}
273264

274265
/**
275-
* Sets the given declared option. Returns a result with the Err set if the option fails,
276-
* otherwise Ok(void).
266+
* Sets the given declared option. Throws if setting the option fails.
277267
* @param name
278268
* @param value
279269
*/
280-
setValue<K extends keyof TypeDocAndTSOptions>(name: K, value: TypeDocAndTSOptions[K]): Result<void, Error>;
281-
setValue(name: string, value: unknown): Result<void, Error>;
282-
setValue(name: string, value: unknown): Result<void, Error> {
270+
setValue<K extends keyof TypeDocAndTSOptions>(name: K, value: TypeDocAndTSOptions[K]): void;
271+
setValue(name: NeverIfInternal<string>, value: NeverIfInternal<unknown>): void;
272+
setValue(name: string, value: unknown): void {
283273
const declaration = this.getDeclaration(name);
284274
if (!declaration) {
285-
return Err(Error(`Tried to set an option (${name}) that was not declared.`));
275+
throw new Error(`Tried to set an option (${name}) that was not declared.`);
286276
}
287277

288-
return convert(value, declaration).match({
289-
ok: value => {
290-
const bag = declaration.scope === ParameterScope.TypeScript
291-
? this._compilerOptions
292-
: this._values;
293-
bag[declaration.name] = value;
294-
return Ok(void 0);
295-
},
296-
err: err => Err(Error(err))
297-
});
278+
const converted = convert(value, declaration);
279+
const bag = declaration.scope === ParameterScope.TypeScript
280+
? this._compilerOptions
281+
: this._values;
282+
bag[declaration.name] = converted;
298283
}
299284

300285
/**
301-
* Sets all the given option values, returns a result with an array of errors for
302-
* keys which failed to be set.
286+
* Sets all the given option values, throws if any value fails to be set.
303287
* @param obj
288+
* @deprecated will be removed in 0.19. Use setValue in a loop instead.
304289
*/
305-
setValues(obj: Partial<TypeDocAndTSOptions>): Result<void, Error[]> {
306-
const errors: Error[] = [];
290+
setValues(obj: NeverIfInternal<Partial<TypeDocAndTSOptions>>): void {
291+
this._logger.warn('Options.setValues is deprecated and will be removed in 0.19.');
307292
for (const [name, value] of Object.entries(obj)) {
308-
this.setValue(name, value).match({
309-
ok() {},
310-
err(error) {
311-
errors.push(error);
312-
}
313-
});
293+
this.setValue(name as keyof TypeDocOptions, value);
314294
}
315-
return errors.length ? Err(errors) : Ok(void 0);
316295
}
317296

318297
/**
@@ -325,8 +304,7 @@ export class Options {
325304
if (declaration.type === ParameterType.Map) {
326305
this._values[declaration.name] = declaration.defaultValue;
327306
} else {
328-
this._values[declaration.name] = convert(declaration.defaultValue, declaration)
329-
.expect(`Failed to validate default value for ${declaration.name}`);
307+
this._values[declaration.name] = convert(declaration.defaultValue, declaration);
330308
}
331309
}
332310
}
@@ -350,17 +328,17 @@ export function BindOption<K extends keyof TypeDocOptionMap>(name: K):
350328
* @privateRemarks
351329
* This overload is intended for plugin use only with looser type checks. Do not use internally.
352330
*/
353-
export function BindOption(name: string):
331+
export function BindOption(name: NeverIfInternal<string>):
354332
(target: { application: Application } | { options: Options }, key: PropertyKey) => void;
355333

356334
export function BindOption(name: string) {
357335
return function(target: { application: Application } | { options: Options }, key: PropertyKey) {
358336
Object.defineProperty(target, key, {
359337
get(this: { application: Application } | { options: Options }) {
360338
if ('options' in this) {
361-
return this.options.getValue(name);
339+
return this.options.getValue(name as keyof TypeDocOptions);
362340
} else {
363-
return this.application.options.getValue(name);
341+
return this.application.options.getValue(name as keyof TypeDocOptions);
364342
}
365343
},
366344
enumerable: true,

0 commit comments

Comments
 (0)