tsa - Typescript source code analysis and documentation tool
This library extracts information from typescript source code that can be used for documentation generation. It produces result compatible with x/deno_doc@0.62.0, but uses Typescript Compiler to detect variable types.
It's similar to npm:typedoc, but works with Deno standards:
- uses
.ts
filename extension in module specifiers - allows to import remote modules
- allows to use
lib.deno.ns.d.ts
library to supportDeno
built-in objects - (current support for
npm:
schema in module specifiers is not stable)
Example:
// To download and run this example:
// curl 'https://raw.githubusercontent.com/jeremiah-shaulov/tsa/v0.0.10/README.md' | perl -ne '$y=$1 if /^```(ts\\b)?/; print $_ if $y&&$m; $m=$y&&($m||m~^// deno .*?/example1.ts~)' > /tmp/example1.ts
// deno run --allow-env --allow-net --allow-read --allow-write /tmp/example1.ts
import {tsa, printDiagnostics, LoadOptions, EmitDocOptions} from 'https://deno.land/x/tsa@v0.0.10/mod.ts';
/** Options for typescript compiler.
**/
export const compilerOptions: tsa.CompilerOptions =
{ declaration: true,
emitDeclarationOnly: true,
allowSyntheticDefaultImports: true,
lib: ['lib.esnext.d.ts', 'lib.deno.ns.d.ts'],
};
/** Configures how to resolve module specifiers, and how to load module contents.
**/
export const loadOptions: LoadOptions =
{
};
/** Configures what symbols to include in the result.
By default it works like `doc` from `x/deno_doc`.
**/
export const emitDocOptions: EmitDocOptions =
{
};
/** Generate doc for the current module, and write it to the provided filename.
@param filename Where to save the doc in JSON format.
**/
export async function writeSelfDocToFile(filename: string)
{ // Create typescript program from the source code of this file
const program = await tsa.createDenoProgram([import.meta.url], compilerOptions, loadOptions);
// Print errors and warnings (if any)
printDiagnostics(tsa.getPreEmitDiagnostics(program));
// Generate the docs
const docNodes = program.emitDoc(emitDocOptions);
// Save the docs to file
await Deno.writeTextFile(filename, JSON.stringify(docNodes, undefined, '\t'));
// Print the number of `docNodes` written
console.log('%c%d doc-nodes %cwritten to %s', 'color:green', docNodes.length, '', filename);
}
// Save to `/tmp/doc.json`
await writeSelfDocToFile('/tmp/doc.json');
How to use from command line
First install the tool:
deno install --allow-env --allow-net --allow-read --allow-write https://deno.land/x/tsa@v0.0.10/tsa.ts
You can use tsa
as you use tsc
for generating JavaScript or DTS (other usage patterns are not supported).
And it will do the same operations as tsc
would, but on project that follows Deno
standards.
Plus tsa
can generate source code AST. To do this specify --outFile
to a file with .json
extension.
tsa --declaration --emitDeclarationOnly --outFile /tmp/ast.json 'https://deno.land/x/mysql@v2.11.0/mod.ts'
How to use from Deno projects
This library exports the following symbols:
tsa
- Namespace that contains everything from the underlying Typescript Compiler. It's the same namespace thatnpm:typescript
exports, with 2 extensions:- type
DenoProgram
, that is extension ofProgram
, that addsemitDoc()
method - function
createDenoProgram()
, that is similar tocreateProgram()
, but returnsDenoProgram
instead ofProgram
- type
LoadOptions
- Configures how to resolve module specifiers, and how to load module contentsdefaultResolve()
- Function that is used by default ifLoadOptions.resolve()
is not setdefaultLoad()
- Function that is used by default ifLoadOptions.load()
is not set. It loads from files or external URLs, and caches external resources.EmitDocOptions
- Options forDenoProgram.emitDoc()
formatDiagnostics()
- Calls one oftsa.formatDiagnostics()
ortsa.formatDiagnosticsWithColorAndContext()
depending on the value ofDeno.noColor
printDiagnostics()
- Prints the result offormatDiagnostics()
to stderr- Type
DocNode
, array of whichDenoProgram.emitDoc()
returns. It's assignable to theDocNode
from x/deno_doc@0.62.0, but contains more information - Types for
DocNode
fields:DocNodeKind
,DocNodeFunction
,DocNodeVariable
,DocNodeClass
, and many more They are assignable to the same types from x/deno_doc@0.62.0.
function DenoProgram.createDenoProgram(entryPoints: readonly string[], compilerOptions?: tsa.CompilerOptions, loadOptions?: LoadOptions): Promise<tsa.DenoProgram>;
interface DenoProgram extends tsa.Program
{ emitDoc(options?: EmitDocOptions): DocNode[];
}
How the result is different from deno_doc?
DenoProgram.emitDoc()
returns array of DocNode
objects, like doc()
from x/deno_doc does.
To understand the information each DocNode
contains, you need to start from learning x/deno_doc.
This library adds additional information to DocNode
:
Location
has additionalentryPointNumber
field. You can pass relative paths tocreateDenoProgram()
as entry points, butLocation.filename
always contains corresponding absolute URL. If this filename is one of the entry points, theentryPointNumber
field will contain the index inentryPoints
array.DocNode
has additionalexports
field. If a symbol is reexported from several places, those places will be recorded here (including current location).ClassPropertyDef
has additionalinit
field that contains property initializer (if any).EnumDef
has additionalisConst
field forconst enum
s.- Doc-comments are returned not only as
doc
string, but alsodocTokens
, that have separate parts for comment text and@link
tags. JsDocTagTyped
(for@enum
,@extends
,@this
and@type
tags) andJsDocTagParam
(for@param
) have additionaltsType
field for the object type.JsDocTagNamed
(for@callback
and@template
tags) has additionaltsType
andtypeParams
fields.DecoratorDef
has additionalnodeIndex
field that contains node index in the results where this decorator function is returned, if it's returned. To include referenced symbols in the result, useEmitDocOptions.includeReferenced
option.- References to another named types are returned as
TsTypeRefDef
objects that contain not onlytypeName
, but also additionalnodeIndex
field, that contains index in the results for this type. If the type is an enum member, alsonodeSubIndex
will be set to member number. SeeEmitDocOptions.includeReferenced
. ClassDef
has additionalsuperNodeIndex
field, that contains node index in the results for the super class. SeeEmitDocOptions.includeReferenced
.TsTypeDef.repr
field for string literals (kind == 'string'
) contains quotes, for string template literals (kind == 'template'
) contains backticks, and for bigint literals (kind == 'bigInt'
) has trailingn
.
Configuration options for the Typescript Compiler (tsa.CompilerOptions)
You can pass tsa.CompilerOptions
to tsa.createDenoProgram()
. It works in the same fashion as typescript.CompilerOptions
for typescript.createProgram()
, with the following differences:
lib
has 2 additional options that you can provide:lib.deno.ns.d.ts
andlib.deno.unstable.d.ts
. If you don't specifylib
explicitly, the default islib.deno.ns.d.ts
.- default value for
allowJs
istrue
. - default value for
target
istsa.ScriptTarget.ESNext
. - default value for
module
istsa.ModuleKind.ESNext
. - regardless of
allowImportingTsExtensions
value, module specifiers must include.ts
(or different) extension.
Module resolution and loading options (LoadOptions)
You can pass LoadOptions
to tsa.createDenoProgram()
that allow to configure the way modules are resolved and loaded.
type LoadOptions =
{ importMap?: string|URL;
resolve?(specifier: string, referrer: string): string | Promise<string>;
load?(specifier: string, isDynamic: boolean): Promise<LoadResponse|undefined>;
};
importMap
- An optional URL or path to an import map to be loaded and used to resolve module specifiers. If bothimportMap
andresolve()
are specified, theimportMap
will be preferred.resolve()
- An optional callback that allows the default resolution logic of the module graph to be "overridden". This is intended to allow items like an import map to be used with the module graph. The callback takes the string of the module specifier, as it appears inimport from
orexport from
, and the string URL of the module where this import is found. The callback then returns a resolved URL to the module file.load()
- An optional callback that is called with the URL string of the resource to be loaded. The callback should return aLoadResponse
orundefined
if the module is not found. If there are other errors encountered, a rejected promise should be returned.
For example LoadOptions
allow to substitute source code of a module during loading.
// To download and run this example:
// curl 'https://raw.githubusercontent.com/jeremiah-shaulov/tsa/v0.0.10/README.md' | perl -ne '$y=$1 if /^```(ts\\b)?/; print $_ if $y&&$m; $m=$y&&($m||m~^// deno .*?/example2.ts~)' > /tmp/example2.ts
// deno run --allow-env --allow-net --allow-read --allow-write /tmp/example2.ts
import {tsa, defaultLoad, printDiagnostics} from 'https://deno.land/x/tsa@v0.0.10/mod.ts';
/** Generate doc for the current module, and write it to the provided filename.
@param filename Where to save the doc in JSON format.
**/
export async function writeSelfDocToFile(filename: string)
{ // Create typescript program from the source code of this file
const program = await tsa.createDenoProgram
( [import.meta.url],
{ declaration: true,
emitDeclarationOnly: true,
allowSyntheticDefaultImports: true,
},
{ async load(specifier, isDynamic)
{ // Load the module contents
const result = await defaultLoad(specifier, isDynamic);
// If the module was found, substitute it's contents
if (result?.kind == 'module')
{ result.content =
` /** Example module.
@module
**/
${result.content}
`;
}
// Return the result
return result;
}
}
);
// Print errors and warnings (if any)
printDiagnostics(tsa.getPreEmitDiagnostics(program));
// Generate the docs
const docNodes = program.emitDoc();
// Save the docs to file
await Deno.writeTextFile(filename, JSON.stringify(docNodes, undefined, '\t'));
// Print the number of `docNodes` written
console.log('%c%d doc-nodes %cwritten to %s', 'color:green', docNodes.length, '', filename);
}
// Save to `/tmp/doc.json`
await writeSelfDocToFile('/tmp/doc.json');
EmitDocOptions
You can pass options to DenoProgram.emitDoc()
that affect what files to traverse, and what symbols to include in the result.
type EmitDocOptions =
{ followModuleImports?: boolean;
includeReferenced?: boolean;
includeBuiltIn?: boolean;
ignoreIgnoreTag?: boolean;
noImportNodes?: boolean;
includeSymbol?(symbol: tsa.Symbol, isExported: boolean, checker: tsa.TypeChecker): boolean;
};
followModuleImports
- Work not only on entry points, but on every module that appears inimport from
orexport from
statements.includeReferenced
-DocNode
s in the result can reference another symbols. Symbol names can appear in type aliases, type parameters, etc. Also decorators refer to functions defined somewhere, not necessarily in entry point modules. If this flag is set totrue
, referenced symbols will be included in the result, and their indices in the resulting array will be recorded in the nodes that refer to them. The referrers include:DecoratorDef.nodeIndex
,TsTypeRefDef.nodeIndex
andTsTypeRefDef.nodeSubIndex
(for enum members), andClassDef.superNodeIndex
.includeBuiltIn
- Also generate docs for referenced built-in objects, likeMap
,HTMLElement
, etc.ignoreIgnoreTag
- By default symbols marked with@ignore
tag in their doc-comments, will not be included in the result. Set this totrue
to ignore the ignore.noImportNodes
- By default, for every symbol that appears inimport from
statement there will be correspondingDocNode
withkind == 'import'
. Set this totrue
to exclude such nodes from the result.includeSymbol()
- Callback function that will be called to ask you whether you want to include every occured symbol in the source files to the result. By default only exported symbols are processed, and if you setEmitDocOptions.includeReferenced
, also not exported but referenced ones. Specify this callback to potentially include other symbols.
I want to use different tsc version
This library contains typescript compiler inside, and it's version is predefined when the library is packaged.
// To download and run this example:
// curl 'https://raw.githubusercontent.com/jeremiah-shaulov/tsa/v0.0.10/README.md' | perl -ne '$y=$1 if /^```(ts\\b)?/; print $_ if $y&&$m; $m=$y&&($m||m~^// deno .*?/example3.ts~)' > /tmp/example3.ts
// deno run --allow-env --allow-net --allow-read --allow-write /tmp/example3.ts
import {tsa} from 'https://deno.land/x/tsa@v0.0.10/mod.ts';
console.log(tsa.version);
There's no guarantee that it can work with different tsc
version, but i'll show you one hack that allows to substitute the tsc
:
// To download and run this example:
// curl 'https://raw.githubusercontent.com/jeremiah-shaulov/tsa/v0.0.10/README.md' | perl -ne '$y=$1 if /^```(ts\\b)?/; print $_ if $y&&$m; $m=$y&&($m||m~^// deno .*?/example4.ts~)' > /tmp/example4.ts
// deno run --allow-env --allow-net --allow-read --allow-write /tmp/example4.ts
import {tsa} from 'https://deno.land/x/tsa@v0.0.10/mod.ts';
// Different version of typescript
import tsaSubstitute from 'npm:typescript@3.9.3';
const entryPoint = 'https://deno.land/x/case@2.1.1/mod.ts';
// Use `call()` to substitute the typescript namespace
const program = await tsa.createDenoProgram.call(tsaSubstitute, [entryPoint]);
const docNodes = program.emitDoc();
await Deno.writeTextFile('/tmp/doc.json', JSON.stringify(docNodes, undefined, '\t'));