This commit is contained in:
Travis Prescott 2024-08-05 14:27:00 -07:00
Родитель 945c96b23f
Коммит 0e95c08bac
13 изменённых файлов: 341 добавлений и 385 удалений

Просмотреть файл

@ -1,30 +0,0 @@
import { ApiTreeNode } from "./api-tree-node.js";
import { ApiView } from "./apiview.js";
import { ApiViewDiagnostic } from "./diagnostic.js";
/**
* The serializable form of the ApiView. This is the tokenfile that gets sent to the APIView service.
*/
export class ApiViewDocument {
name: string;
packageName: string;
apiForest: ApiTreeNode[] | null;
diagnostics: ApiViewDiagnostic[];
versionString: string;
language: string;
crossLanguagePackageId: string | undefined;
constructor(apiview: ApiView) {
this.name = apiview.name;
this.packageName = apiview.packageName;
this.apiForest = apiview.nodes;
this.diagnostics = apiview.diagnostics;
this.versionString = apiview.versionString;
this.language = "TypeSpec";
this.crossLanguagePackageId = apiview.crossLanguagePackageId;
}
asString(): string {
return JSON.stringify(this, null, 2);
}
}

Просмотреть файл

@ -1,24 +1,25 @@
import { getNamespaceFullName, Namespace, navigateProgram, Program } from "@typespec/compiler";
import { ApiViewDiagnostic, ApiViewDiagnosticLevel } from "./diagnostic.js";
import { CodeDiagnostic, CodeDiagnosticLevel } from "./models/code-diagnostic.js";
import { NamespaceModel } from "./namespace-model.js";
import { LIB_VERSION } from "./version.js";
import { ApiTreeNode, NodeKind, NodeOptions, NodeTag } from "./api-tree-node.js";
import { ApiTreeNode, ApiTreeNodeKind, ApiTreeNodeOptions, NodeTag } from "./models/api-tree-node.js";
import { CodeFile } from "./models/code-file.js";
export class ApiView {
name: string;
packageName: string;
packageVersion: string;
crossLanguagePackageId: string | undefined;
nodes: ApiTreeNode[] = [];
diagnostics: ApiViewDiagnostic[] = [];
versionString: string;
diagnostics: CodeDiagnostic[] = [];
typeDeclarations = new Set<string>();
includeGlobalNamespace: boolean;
constructor(name: string, packageName: string, versionString?: string, includeGlobalNamespace?: boolean) {
constructor(name: string, packageName: string, packageVersion?: string, includeGlobalNamespace?: boolean) {
this.name = name;
this.packageName = packageName;
this.versionString = versionString ?? "";
this.packageVersion = packageVersion ?? "";
this.includeGlobalNamespace = includeGlobalNamespace ?? false;
this.crossLanguagePackageId = packageName;
}
@ -31,10 +32,11 @@ export class ApiView {
* @param options options for node creation
* @returns the created node
*/
node(parent: ApiTreeNode | ApiView, name: string, id: string, kind: NodeKind, options?: NodeOptions): ApiTreeNode {
node(parent: ApiTreeNode | ApiView, name: string, id: string, kind: ApiTreeNodeKind, options?: ApiTreeNodeOptions) {
const child = new ApiTreeNode(name, id, kind, {
tags: options?.tags,
properties: options?.properties,
iconName: options?.iconName,
crossLanguageDefinitionId: options?.crossLanguageDefinitionId,
});
if (parent instanceof ApiView) {
parent.nodes.push(child);
@ -43,7 +45,7 @@ export class ApiView {
}
}
child(parent: ApiTreeNode | ApiViewDocument, name: string, id: string, tags?: Tag[]) {
child(parent: ApiTreeNode | CodeFile, name: string, id: string, tags?: Tag[]) {
const child: ApiTreeNode = {
_kind: "ApiTreeNode",
name: name,
@ -217,8 +219,8 @@ export class ApiView {
});
}
diagnostic(message: string, targetId: string, level: ApiViewDiagnosticLevel) {
this.diagnostics.push(new ApiViewDiagnostic(message, targetId, level));
diagnostic(message: string, targetId: string, level: CodeDiagnosticLevel) {
this.diagnostics.push(new CodeDiagnostic(message, targetId, level));
}
emit(program: Program) {

Просмотреть файл

@ -1,23 +0,0 @@
export class ApiViewDiagnostic {
static idCounter: number = 1;
diagnosticNumber: number;
text: string;
targetId: string;
level: ApiViewDiagnosticLevel;
helpLinkUri?: string;
constructor(message: string, targetId: string, level: ApiViewDiagnosticLevel) {
this.diagnosticNumber = ApiViewDiagnostic.idCounter;
ApiViewDiagnostic.idCounter++;
this.text = message;
this.targetId = targetId;
this.level = level;
}
}
export enum ApiViewDiagnosticLevel {
Default = 0,
Info = 1,
Warning = 2,
Error = 3,
}

Просмотреть файл

@ -15,7 +15,7 @@ import { buildVersionProjections, getVersion } from "@typespec/versioning";
import path from "path";
import { ApiView } from "./apiview.js";
import { ApiViewEmitterOptions, reportDiagnostic } from "./lib.js";
import { ApiViewDocument } from "./apiview-document.js";
import { CodeFile } from "./models/code-file.js";
export interface ResolvedApiViewEmitterOptions {
emitterOutputDir: string;
@ -194,9 +194,10 @@ function createApiViewEmitter(program: Program, options: ResolvedApiViewEmitterO
await program.host.mkdirp(outputFolder);
const outputFile = options.outputFile ?? `${namespaceString}-apiview.json`;
const outputPath = resolvePath(outputFolder, outputFile);
const serialized = new CodeFile(apiview).serialize();
await emitFile(program, {
path: outputPath,
content: `${new ApiViewDocument(apiview).asString()}\n`,
content: JSON.stringify(serialized, null, 2),
});
}
}

Просмотреть файл

@ -0,0 +1,5 @@
/** Marks an object as being part of the ApiView server-side contract. */
export interface ApiViewSerializable {
/** Serializes the object to the format ApiView expects. */
serialize(): object;
}

Просмотреть файл

@ -36,73 +36,112 @@ import {
UnionVariantNode,
ValueOfExpressionNode,
} from "@typespec/compiler";
import { RenderClass, StructuredToken, TokenKind, TokenLocation, TokenOptions, TokenTag } from "./structured-token.js";
import { StructuredToken, StructuredTokenKind, StructuredTokenRenderClass, StructuredTokenTag, TokenLocation, TokenOptions } from "./structured-token.js";
import {
getFullyQualifiedIdentifier,
definitionIdFor,
getRawText,
generateId,
buildTemplateString,
} from "./helpers.js";
import { NamespaceModel } from "./namespace-model.js";
import { ApiView } from "./apiview.js";
} from "../helpers.js";
import { NamespaceModel } from "../namespace-model.js";
import { ApiView } from "../apiview.js";
import { ApiViewSerializable } from "../interface.js";
const WHITESPACE = " ";
/** Tags supported by APIView v2 */
export enum NodeTag {
/** Show item as deprecated. */
deprecated,
/** Hide item from APIView. */
hidden,
/** Hide item from APIView Navigation. */
hideFromNav,
/** Ignore differences in this item when calculating diffs. */
skipDiff,
}
/** The kind values APIView supports for ApiTreeNodes. */
export enum NodeKind {
assembly,
class,
delegate,
enum,
interface,
method,
namespace,
package,
struct,
type,
export enum ApiTreeNodeKind {
assembly = "assembly",
class = "class",
delegate = "delegate",
enum = "enum",
interface = "interface",
method = "method",
namespace = "namespace",
package = "package",
struct = "struct",
type = "type",
}
/** Options when creating a new ApiTreeNode. */
export interface NodeOptions {
tags?: NodeTag[];
properties?: Map<string, string>;
/**
* Options when creating a new ApiTreeNode.
*/
export interface ApiTreeNodeOptions {
tags?: ApiTreeNodeTag[];
iconName?: string;
crossLanguageDefinitionId?: string;
}
/**
* Describes optional properties settable on an ApiTreeNode.
*/
class ApiTreeNodeProperties implements ApiViewSerializable {
iconName?: string;
crossLanguageDefinitionId?: string;
constructor(iconName?: string, crossLanguageDefinitionId?: string) {
this.iconName = iconName;
this.crossLanguageDefinitionId = crossLanguageDefinitionId;
}
serialize(): object {
return {
IconName: this.iconName,
CrossLangDefId: this.crossLanguageDefinitionId,
}
}
}
/**
* Tags supported by APIView v2 ApiTreeNode.
*/
enum ApiTreeNodeTag {
/** Mark a token as deprecated. */
deprecated = "Deprecated",
/** Mark a node as hidden. */
hidden = "Hidden",
/** Indicate that a node should be omitted from the page navigation. */
hideFromNav = "HideFromNav",
/** Indicate that a node should not be used when computing a diff. */
skipDiff = "SkipDiff",
}
/** New-style structured APIView node. */
export class ApiTreeNode {
export class ApiTreeNode implements ApiViewSerializable {
name: string;
id: string;
kind: NodeKind;
tags?: Set<string>;
properties: Map<string, string>;
kind: ApiTreeNodeKind;
topTokens: StructuredToken[];
bottomTokens: StructuredToken[];
children: ApiTreeNode[];
properties?: ApiTreeNodeProperties;
tags?: ApiTreeNodeTag[];
constructor(name: string, id: string, kind: NodeKind, options?: NodeOptions) {
constructor(name: string, id: string, kind: ApiTreeNodeKind, options?: ApiTreeNodeOptions) {
this.name = name;
this.id = id;
this.kind = kind;
this.tags = new Set([...(options?.tags ?? []).toString()]);
this.properties = options?.properties ?? new Map<string, string>();
this.tags = options?.tags;
this.properties = new ApiTreeNodeProperties(options?.iconName, options?.crossLanguageDefinitionId);
this.topTokens = [];
this.bottomTokens = [];
this.children = [];
}
serialize(): object {
return {
Name: this.name,
Id: this.id,
Kind: this.kind,
TopTokens: this.topTokens?.map((t) => t.serialize()),
BottomTokens: this.bottomTokens?.map((t) => t.serialize()),
Children: this.children?.map((c) => c.serialize()),
Properties: this.properties?.serialize(),
Tags: this.tags
}
}
/**
* Creates a new node on the parent.
* @param parent the parent, which can be APIView itself for top-level nodes or another ApiTreeNode
@ -111,14 +150,18 @@ export class ApiTreeNode {
* @param options options for node creation
* @returns the created node
*/
node(parent: ApiTreeNode | ApiView, name: string, id: string, kind: NodeKind, options?: NodeOptions): ApiTreeNode {
node(parent: ApiTreeNode | ApiView, name: string, id: string, kind: ApiTreeNodeKind, options?: ApiTreeNodeOptions): ApiTreeNode {
const child = new ApiTreeNode(name, id, kind, {
tags: options?.tags,
properties: options?.properties,
iconName: options?.iconName,
crossLanguageDefinitionId: options?.crossLanguageDefinitionId,
});
if (parent instanceof ApiView) {
parent.nodes.push(child);
} else {
if (parent.children === undefined) {
parent.children = [];
}
parent.children.push(child);
}
return child;
@ -130,15 +173,8 @@ export class ApiTreeNode {
* @param lineId an optional line id
* @param options options you can set
*/
token(kind: TokenKind, options?: TokenOptions) {
const token: StructuredToken = {
kind: kind,
value: options?.value,
id: options?.lineId ?? "",
tags: new Set([...(options?.tags ?? []).toString()]),
properties: options?.properties ?? new Map<string, string>(),
renderClasses: new Set([...(options?.renderClasses ?? []).toString()]),
};
token(kind: StructuredTokenKind, options?: TokenOptions) {
const token = new StructuredToken(kind, options);
const location = options?.location ?? TokenLocation.top;
if (location === TokenLocation.top) {
this.topTokens.push(token);
@ -152,15 +188,15 @@ export class ApiTreeNode {
* @param count number of spaces to add
*/
whitespace(count: number = 1) {
this.topTokens.push(new StructuredToken(TokenKind.nonBreakingSpace, { value: WHITESPACE.repeat(count) }));
this.topTokens.push(new StructuredToken(StructuredTokenKind.nonBreakingSpace, { value: WHITESPACE.repeat(count) }));
}
/**
* Ensures exactly one space.
*/
space() {
if (this.topTokens[this.topTokens.length - 1]?.kind !== TokenKind.nonBreakingSpace) {
this.topTokens.push(new StructuredToken(TokenKind.nonBreakingSpace, { value: WHITESPACE }));
if (this.topTokens[this.topTokens.length - 1]?.kind !== StructuredTokenKind.nonBreakingSpace) {
this.topTokens.push(new StructuredToken(StructuredTokenKind.nonBreakingSpace, { value: WHITESPACE }));
}
}
@ -168,7 +204,7 @@ export class ApiTreeNode {
* Adds a newline token.
*/
newline() {
this.topTokens.push(new StructuredToken(TokenKind.lineBreak));
this.topTokens.push(new StructuredToken(StructuredTokenKind.lineBreak));
}
/**
@ -177,7 +213,7 @@ export class ApiTreeNode {
*/
blankLines(count: number) {
for (let i = 0; i < count; i++) {
this.token(TokenKind.lineBreak, { tags: [TokenTag.skipDiff], location: TokenLocation.bottom });
this.token(StructuredTokenKind.lineBreak, { tags: [StructuredTokenTag.skipDiff], location: TokenLocation.bottom });
}
// TODO: Erase this is no longer needed.
// // count the number of trailing newlines (ignoring indent whitespace)
@ -226,9 +262,9 @@ export class ApiTreeNode {
if (prefixSpace) {
this.space();
}
this.token(TokenKind.content, {
this.token(StructuredTokenKind.content, {
value: value,
renderClasses: [RenderClass.punctuation],
renderClasses: [StructuredTokenRenderClass.punctuation],
location: location,
});
if (postfixSpace) {
@ -241,9 +277,9 @@ export class ApiTreeNode {
* @param text to render
*/
text(text: string) {
this.token(TokenKind.content, {
this.token(StructuredTokenKind.content, {
value: text,
renderClasses: [RenderClass.text],
renderClasses: [StructuredTokenRenderClass.text],
});
}
@ -260,9 +296,9 @@ export class ApiTreeNode {
if (prefixSpace) {
this.space();
}
this.token(TokenKind.content, {
this.token(StructuredTokenKind.content, {
value: keyword,
renderClasses: [RenderClass.keyword],
renderClasses: [StructuredTokenRenderClass.keyword],
});
if (postfixSpace) {
this.space();
@ -282,9 +318,9 @@ export class ApiTreeNode {
}
this.typeDeclarations.add(typeId);
}
this.token(TokenKind.content, {
this.token(StructuredTokenKind.content, {
value: typeName,
renderClasses: [RenderClass.typeName],
renderClasses: [StructuredTokenRenderClass.typeName],
lineId: typeId,
});
}
@ -295,9 +331,9 @@ export class ApiTreeNode {
* @param targetId the id of the target type for clickable linking
*/
typeReference(typeName: string, targetId?: string) {
this.token(TokenKind.content, {
this.token(StructuredTokenKind.content, {
value: typeName,
renderClasses: [RenderClass.typeName],
renderClasses: [StructuredTokenRenderClass.typeName],
lineId: targetId,
});
}
@ -307,9 +343,9 @@ export class ApiTreeNode {
* @param name to render
*/
member(name: string) {
this.token(TokenKind.content, {
this.token(StructuredTokenKind.content, {
value: name,
renderClasses: [RenderClass.memberName],
renderClasses: [StructuredTokenRenderClass.memberName],
});
}
@ -320,9 +356,9 @@ export class ApiTreeNode {
stringLiteral(value: string) {
const lines = value.split("\n");
if (lines.length === 1) {
this.token(TokenKind.content, {
this.token(StructuredTokenKind.content, {
value: `\u0022${value}\u0022`,
renderClasses: [RenderClass.stringLiteral],
renderClasses: [StructuredTokenRenderClass.stringLiteral],
});
} else {
this.punctuation(`"""`);
@ -341,8 +377,8 @@ export class ApiTreeNode {
*/
literal(value: string) {
this.topTokens.push(
new StructuredToken(TokenKind.content, {
renderClasses: [RenderClass.literal],
new StructuredToken(StructuredTokenKind.content, {
renderClasses: [StructuredTokenRenderClass.literal],
value: value,
}),
);
@ -357,7 +393,7 @@ export class ApiTreeNode {
case SyntaxKind.AliasStatement:
obj = node as AliasStatementNode;
const aliasId = obj.id.sv;
const aliasNode = this.node(this, aliasId, aliasId, NodeKind.type);
const aliasNode = this.node(this, aliasId, aliasId, ApiTreeNodeKind.type);
aliasNode.keyword("alias", { postfixSpace: true });
aliasNode.typeDeclaration(aliasId, aliasId, true);
aliasNode.tokenizeTemplateParameters(obj.templateParameters);
@ -373,7 +409,7 @@ export class ApiTreeNode {
obj = node as AugmentDecoratorStatementNode;
const decoratorName = this.getNameForNode(obj.target);
const decoratorNode = this.node(this, decoratorName, decoratorName, "decorator", {
tags: [NodeTag.hideFromNav],
tags: [ApiTreeNodeTag.hideFromNav],
});
decoratorNode.punctuation("@@");
decoratorNode.tokenizeIdentifier(obj.target, "keyword");
@ -422,7 +458,7 @@ export class ApiTreeNode {
case SyntaxKind.DirectiveExpression:
obj = node as DirectiveExpressionNode;
const nodeId = generateId(node)!;
const directiveNode = this.node(this, nodeId, nodeId, "directive", { tags: [NodeTag.hideFromNav] });
const directiveNode = this.node(this, nodeId, nodeId, "directive", { tags: [ApiTreeNodeTag.hideFromNav] });
directiveNode.keyword(`#${obj.target.sv}`, { postfixSpace: true });
for (const arg of obj.arguments) {
switch (arg.kind) {
@ -734,7 +770,7 @@ export class ApiTreeNode {
private tokenizeModelStatement(node: ModelStatementNode) {
const nodeId = node.id.sv;
const modelNode = this.node(this, nodeId, nodeId, NodeKind.class);
const modelNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.class);
modelNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
modelNode.keyword("model", { postfixSpace: true });
modelNode.tokenizeIdentifier(node.id, "declaration");
@ -762,7 +798,7 @@ export class ApiTreeNode {
private tokenizeScalarStatement(node: ScalarStatementNode) {
const nodeId = node.id.sv;
const scalarNode = this.node(this, nodeId, nodeId, NodeKind.type);
const scalarNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.type);
scalarNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
scalarNode.keyword("scalar", { postfixSpace: true });
scalarNode.tokenizeIdentifier(node.id, "declaration");
@ -776,7 +812,7 @@ export class ApiTreeNode {
private tokenizeInterfaceStatement(node: InterfaceStatementNode) {
const nodeId = node.id.sv;
const interfaceNode = this.node(this, nodeId, nodeId, NodeKind.interface);
const interfaceNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.interface);
interfaceNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
interfaceNode.keyword("interface", { postfixSpace: true });
interfaceNode.tokenizeIdentifier(node.id, "declaration");
@ -784,7 +820,7 @@ export class ApiTreeNode {
for (let x = 0; x < node.operations.length; x++) {
const op = node.operations[x];
const opId = generateId(op)!;
const opNode = interfaceNode.node(interfaceNode, opId, opId, NodeKind.method);
const opNode = interfaceNode.node(interfaceNode, opId, opId, ApiTreeNodeKind.method);
opNode.tokenizeOperationStatement(op, true);
interfaceNode.blankLines(x !== node.operations.length - 1 ? 1 : 0);
}
@ -792,7 +828,7 @@ export class ApiTreeNode {
private tokenizeEnumStatement(node: EnumStatementNode) {
const nodeId = node.id.sv;
const enumNode = this.node(this, nodeId, nodeId, NodeKind.enum);
const enumNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.enum);
enumNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
enumNode.keyword("enum", { postfixSpace: true });
enumNode.tokenizeIdentifier(node.id, "declaration");
@ -807,7 +843,7 @@ export class ApiTreeNode {
private tokenizeUnionStatement(node: UnionStatementNode) {
const nodeId = node.id.sv;
const unionNode = this.node(this, nodeId, nodeId, NodeKind.enum);
const unionNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.enum);
unionNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
unionNode.keyword("union", { postfixSpace: true });
unionNode.tokenizeIdentifier(node.id, "declaration");
@ -877,7 +913,7 @@ export class ApiTreeNode {
isOperationSignature: boolean,
leadingNewline: boolean,
) {
const modelNode = this.node(this, "anonymous", "anonymous", NodeKind.class);
const modelNode = this.node(this, "anonymous", "anonymous", ApiTreeNodeKind.class);
if (node.properties.length) {
if (leadingNewline) {
modelNode.blankLines(0);
@ -928,7 +964,7 @@ export class ApiTreeNode {
private tokenizeOperationStatement(node: OperationStatementNode, suppressOpKeyword: boolean = false) {
const nodeId = node.id.sv;
const opNode = this.node(this, nodeId, nodeId, NodeKind.method);
const opNode = this.node(this, nodeId, nodeId, ApiTreeNodeKind.method);
opNode.tokenizeDecoratorsAndDirectives(node.decorators, node.directives, false);
if (!suppressOpKeyword) {
opNode.keyword("op", { postfixSpace: true });

Просмотреть файл

@ -0,0 +1,35 @@
import { ApiViewSerializable } from "../interface.js";
export class CodeDiagnostic implements ApiViewSerializable{
static idCounter: number = 1;
diagnosticId: number;
text: string;
targetId: string;
level: CodeDiagnosticLevel;
helpLinkUri?: string;
constructor(message: string, targetId: string, level: CodeDiagnosticLevel) {
this.diagnosticId = CodeDiagnostic.idCounter;
CodeDiagnostic.idCounter++;
this.text = message;
this.targetId = targetId;
this.level = level;
}
serialize(): object {
return {
DiagnosticId: this.diagnosticId,
Text: this.text,
HelpLinkUri: this.helpLinkUri,
TargetId: this.targetId,
Level: this.level,
}
}
}
export enum CodeDiagnosticLevel {
Info = 1,
Warning = 2,
Error = 3,
Fatal = 4,
}

Просмотреть файл

@ -0,0 +1,50 @@
import { ApiTreeNode } from "./api-tree-node.js";
import { ApiView } from "../apiview.js";
import { CodeDiagnostic } from "./code-diagnostic.js";
import { LIB_VERSION } from "../version.js";
import { ApiViewSerializable } from "../interface.js";
/**
* The serializable form of the ApiView. This is the tokenfile that gets sent to the APIView service.
*/
export class CodeFile implements ApiViewSerializable {
parserVersion: string;
name: string;
language: string;
packageName: string;
packageVersion: string;
crossLanguagePackageId: string | undefined;
apiForest: ApiTreeNode[] | null;
diagnostics: CodeDiagnostic[];
constructor(apiview: ApiView) {
this.parserVersion = LIB_VERSION;
this.name = apiview.name;
this.packageName = apiview.packageName;
this.packageVersion = apiview.packageVersion
this.apiForest = apiview.nodes;
this.diagnostics = apiview.diagnostics;
this.language = "TypeSpec";
this.crossLanguagePackageId = apiview.crossLanguagePackageId;
}
asString(): string {
return JSON.stringify(this, null, 2);
}
serialize(): object {
return {
VersionString: this.parserVersion,
Name: this.name,
Language: "TypeSpec",
LanguageVariant: null,
PackageName: this.packageName,
PackageVersion: this.packageVersion,
ServiceName: null,
CrossLanguagePackageId: this.crossLanguagePackageId,
APIForest: this.apiForest?.map(node => node.serialize()),
Diagnostics: this.diagnostics.map(diag => diag.serialize())
}
}
}

Просмотреть файл

@ -0,0 +1,110 @@
import { ApiViewSerializable } from "../interface.js";
/**
* Helper enum to declare where a token should be placed.
*/
export const enum TokenLocation {
/** Apithis.TopTokens. Most tokens will go here. */
top,
/** Apithis.BottomTokens. Useful for closing braces. */
bottom,
}
/**
* Describes the type of a structured token. Most tokens will be of type
* content.
*/
export const enum StructuredTokenKind {
content = 0,
lineBreak = 1,
nonBreakingSpace = 2,
tabSpace = 3,
parameterSeparator = 4,
}
/**
* Options when creating a new StructuredToken.
*/
export interface TokenOptions {
renderClasses?: (StructuredTokenRenderClass | string)[];
value?: string;
tags?: StructuredTokenTag[];
properties?: Map<string, string>;
location?: TokenLocation;
lineId?: string;
groupId?: "doc" | undefined;
navigateToId?: string;
}
/**
* New-style structured APIView token.
*/
export class StructuredToken implements ApiViewSerializable {
id?: string;
kind: StructuredTokenKind;
value?: string;
properties?: StructuredTokenProperties;
tags?: StructuredTokenTag[];
renderClasses?: (StructuredTokenRenderClass | string)[];
constructor(kind: StructuredTokenKind, options?: TokenOptions) {
this.id = options?.lineId;
this.kind = kind;
this.value = options?.value;
this.properties = new StructuredTokenProperties(options?.groupId, options?.navigateToId);
this.renderClasses = options?.renderClasses;
}
serialize(): object {
return {
Id: this.id,
Kind: this.kind,
Value: this.value,
Properties: this.properties?.serialize(),
Tags: this.tags,
RenderClasses: this.renderClasses,
}
}
}
/**
* Properties which can be set on a structured token.
*/
class StructuredTokenProperties implements ApiViewSerializable {
groupId?: "doc" | undefined;
navigateToId?: string;
constructor(groupId?: "doc" | undefined, navigateToId?: string) {
this.groupId = groupId;
this.navigateToId = navigateToId;
}
serialize(): object {
return {
GroupId: this.groupId,
NavigateToId: this.navigateToId,
}
}
}
/**
* Tags which can be set on a structured token.
*/
export enum StructuredTokenTag {
deprecated = "Deprecated",
skipDiff = "SkipDiff"
}
/**
* Render classes applicable to a structured token.
*/
export enum StructuredTokenRenderClass {
comment = "comment",
keyword = "keyword",
literal = "literal",
stringLiteral = "sliteral",
memberName = "mname",
typeName = "tname",
punctuation = "punc",
text = "text",
}

Просмотреть файл

@ -19,8 +19,8 @@ import {
} from "@typespec/compiler";
import { caseInsensitiveSort, findNodes, generateId } from "./helpers.js";
import { ApiView } from "./apiview.js";
import { TokenLocation } from "./structured-token.js";
import { NodeKind, NodeTag } from "./api-tree-node.js";
import { TokenLocation } from "./models/structured-token.js";
import { ApiTreeNodeKind } from "./models/api-tree-node.js";
export class NamespaceModel {
kind = SyntaxKind.NamespaceStatement;
@ -133,7 +133,7 @@ export class NamespaceModel {
}
tokenize(apiview: ApiView) {
const treeNode = apiview.node(apiview, this.name, this.name, NodeKind.namespace);
const treeNode = apiview.node(apiview, this.name, this.name, ApiTreeNodeKind.namespace);
if (this.node.kind === SyntaxKind.NamespaceStatement) {
treeNode.tokenizeDecoratorsAndDirectives(this.node.decorators, this.node.directives, false);
@ -148,25 +148,25 @@ export class NamespaceModel {
}
for (const node of this.operations.values()) {
const nodeId = generateId(node)!;
const subNode = apiview.node(treeNode, nodeId, nodeId, NodeKind.method);
const subNode = apiview.node(treeNode, nodeId, nodeId, ApiTreeNodeKind.method);
subNode.tokenize(node);
subNode.blankLines(1);
}
for (const node of this.resources.values()) {
const nodeId = generateId(node)!;
const subNode = apiview.node(treeNode, nodeId, nodeId, NodeKind.class);
const subNode = apiview.node(treeNode, nodeId, nodeId, ApiTreeNodeKind.class);
subNode.tokenize(node);
subNode.blankLines(1);
}
for (const node of this.models.values()) {
const nodeId = generateId(node)!;
const subNode = apiview.node(treeNode, nodeId, nodeId, NodeKind.class);
const subNode = apiview.node(treeNode, nodeId, nodeId, ApiTreeNodeKind.class);
subNode.tokenize(node);
subNode.blankLines(1);
}
for (const node of this.aliases.values()) {
const nodeId = generateId(node)!;
const subNode = apiview.node(treeNode, nodeId, nodeId, NodeKind.type);
const subNode = apiview.node(treeNode, nodeId, nodeId, ApiTreeNodeKind.type);
subNode.tokenize(node);
subNode.punctuation(";");
subNode.blankLines(1);

Просмотреть файл

@ -1,154 +0,0 @@
import {
AliasStatementNode,
EnumStatementNode,
InterfaceStatementNode,
IntersectionExpressionNode,
ModelExpressionNode,
ModelStatementNode,
ObjectLiteralNode,
OperationStatementNode,
ProjectionModelExpressionNode,
ScalarStatementNode,
SyntaxKind,
UnionExpressionNode,
UnionStatementNode,
} from "@typespec/compiler";
import { ApiView, NamespaceStack } from "./apiview.js";
import { NamespaceModel } from "./namespace-model.js";
export class ApiViewNavigation {
Text: string;
NavigationId: string | undefined;
ChildItems: ApiViewNavigation[];
Tags: ApiViewNavigationTag;
constructor(
objNode:
| AliasStatementNode
| NamespaceModel
| ModelStatementNode
| OperationStatementNode
| InterfaceStatementNode
| EnumStatementNode
| ModelExpressionNode
| IntersectionExpressionNode
| ProjectionModelExpressionNode
| ScalarStatementNode
| UnionStatementNode
| UnionExpressionNode
| ObjectLiteralNode,
stack: NamespaceStack
) {
let obj;
switch (objNode.kind) {
case SyntaxKind.NamespaceStatement:
stack.push(objNode.name);
this.Text = objNode.name;
this.Tags = { TypeKind: ApiViewNavigationKind.Module };
const operationItems = new Array<ApiViewNavigation>();
for (const node of objNode.operations.values()) {
operationItems.push(new ApiViewNavigation(node, stack));
}
const resourceItems = new Array<ApiViewNavigation>();
for (const node of objNode.resources.values()) {
resourceItems.push(new ApiViewNavigation(node, stack));
}
const modelItems = new Array<ApiViewNavigation>();
for (const node of objNode.models.values()) {
modelItems.push(new ApiViewNavigation(node, stack));
}
const aliasItems = new Array<ApiViewNavigation>();
for (const node of objNode.aliases.values()) {
aliasItems.push(new ApiViewNavigation(node, stack));
}
this.ChildItems = [];
if (operationItems.length) {
this.ChildItems.push({ Text: "Operations", ChildItems: operationItems, Tags: { TypeKind: ApiViewNavigationKind.Method }, NavigationId: "" });
}
if (resourceItems.length) {
this.ChildItems.push({ Text: "Resources", ChildItems: resourceItems, Tags: { TypeKind: ApiViewNavigationKind.Class }, NavigationId: "" });
}
if (modelItems.length) {
this.ChildItems.push({ Text: "Models", ChildItems: modelItems, Tags: { TypeKind: ApiViewNavigationKind.Class }, NavigationId: "" });
}
if (aliasItems.length) {
this.ChildItems.push({ Text: "Aliases", ChildItems: aliasItems, Tags: { TypeKind: ApiViewNavigationKind.Class }, NavigationId: "" });
}
break;
case SyntaxKind.ModelStatement:
obj = objNode as ModelStatementNode;
stack.push(obj.id.sv);
this.Text = obj.id.sv;
this.Tags = { TypeKind: ApiViewNavigationKind.Class };
this.ChildItems = [];
break;
case SyntaxKind.EnumStatement:
obj = objNode as EnumStatementNode;
stack.push(obj.id.sv);
this.Text = obj.id.sv;
this.Tags = { TypeKind: ApiViewNavigationKind.Enum };
this.ChildItems = [];
break;
case SyntaxKind.OperationStatement:
obj = objNode as OperationStatementNode;
stack.push(obj.id.sv);
this.Text = obj.id.sv;
this.Tags = { TypeKind: ApiViewNavigationKind.Method };
this.ChildItems = [];
break;
case SyntaxKind.InterfaceStatement:
obj = objNode as InterfaceStatementNode;
stack.push(obj.id.sv);
this.Text = obj.id.sv;
this.Tags = { TypeKind: ApiViewNavigationKind.Method };
this.ChildItems = [];
for (const child of obj.operations) {
this.ChildItems.push(new ApiViewNavigation(child, stack));
}
break;
case SyntaxKind.UnionStatement:
obj = objNode as UnionStatementNode;
stack.push(obj.id.sv);
this.Text = obj.id.sv;
this.Tags = { TypeKind: ApiViewNavigationKind.Enum };
this.ChildItems = [];
break;
case SyntaxKind.AliasStatement:
obj = objNode as AliasStatementNode;
stack.push(obj.id.sv);
this.Text = obj.id.sv;
this.Tags = { TypeKind: ApiViewNavigationKind.Class };
this.ChildItems = [];
break;
case SyntaxKind.ModelExpression:
throw new Error(`Navigation unsupported for "ModelExpression".`);
case SyntaxKind.IntersectionExpression:
throw new Error(`Navigation unsupported for "IntersectionExpression".`);
case SyntaxKind.ProjectionModelExpression:
throw new Error(`Navigation unsupported for "ProjectionModelExpression".`);
case SyntaxKind.ScalarStatement:
obj = objNode as ScalarStatementNode;
stack.push(obj.id.sv);
this.Text = obj.id.sv;
this.Tags = { TypeKind: ApiViewNavigationKind.Class };
this.ChildItems = [];
break;
default:
throw new Error(`Navigation unsupported for "${objNode.kind.toString()}".`);
}
this.NavigationId = stack.value();
stack.pop();
}
}
export interface ApiViewNavigationTag {
TypeKind: ApiViewNavigationKind;
}
export const enum ApiViewNavigationKind {
Class = "class",
Enum = "enum",
Method = "method",
Module = "namespace",
Package = "assembly",
}

Просмотреть файл

@ -1,76 +0,0 @@
/** Supported render classes for APIView v2.
* You can add custom ones but need to provide CSS to EngSys.
*/
export const enum RenderClass {
text,
keyword,
punctuation,
literal,
comment,
typeName = "type-name",
memberName = "member-name",
stringLiteral = "string-literal",
}
/** Tags supported by APIView v2 */
export enum TokenTag {
/** Show item as deprecated. */
deprecated,
/** Hide item from APIView. */
hidden,
/** Hide item from APIView Navigation. */
hideFromNav,
/** Ignore differences in this item when calculating diffs. */
skipDiff,
}
export const enum TokenLocation {
/** Apithis.TopTokens. Most tokens will go here. */
top,
/** Apithis.BottomTokens. Useful for closing braces. */
bottom,
}
/**
* Describes the type of structured token.
*/
export const enum TokenKind {
content = 0,
lineBreak = 1,
nonBreakingSpace = 2,
tabSpace = 3,
parameterSeparator = 4,
url = 5,
}
/**
* Options when creating a new StructuredToken.
*/
export interface TokenOptions {
renderClasses?: RenderClass[];
value?: string;
tags?: TokenTag[];
properties?: Map<string, string>;
location?: TokenLocation;
lineId?: string;
}
/**
* New-style structured APIView token.
*/
export class StructuredToken {
value?: string;
id?: string;
kind: TokenKind;
tags?: Set<string>;
properties: Map<string, string>;
renderClasses: Set<string>;
constructor(kind: TokenKind, options?: TokenOptions) {
this.id = options?.lineId;
this.kind = kind;
this.value = options?.value;
this.properties = options?.properties ?? new Map<string, string>();
this.renderClasses = new Set([...(options?.renderClasses ?? []).toString()]);
}
}

Просмотреть файл

@ -1 +1 @@
export const LIB_VERSION = "0.4.9";
export const LIB_VERSION = "0.5.0";