Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export default defineConfig(
'jsdoc/check-tag-names': ['error', {
// NOTE: Not all Typedoc supported tags are present here. Feel free to add any other
// Typedoc supported tags to this list
definedTags: ['category', 'categoryDescription', 'hidden', 'title'],
definedTags: ['category', 'categoryDescription', 'hidden', 'title', 'publicType', 'publicReturnType'],
inlineTags: ['link', 'see'],
}],
'jsdoc/empty-tags': ['error', {
Expand Down
110 changes: 101 additions & 9 deletions lib/buildtools/src/build/docs/__tests__/conductor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,23 @@ function createParameter(
return parameter;
}

function addExportedNames(
project: td.ProjectReflection,
plugin: td.DeclarationReflection,
names: string[]
) {
const exportedNames = register(
project,
new td.DeclarationReflection('exportedNames', td.ReflectionKind.Property, plugin)
);
plugin.addChild(exportedNames);
exportedNames.type = new td.TypeOperatorType(
new td.TupleType(names.map(name => new td.LiteralType(name))),
'readonly'
);
return exportedNames;
}

describe(normalizeConductorType, () => {
it('maps TypedValue numbers to native numbers', () => {
const project = createProject();
Expand Down Expand Up @@ -126,15 +143,7 @@ describe(normalizeConductorDocs, () => {
conductorReference(project, 'RenamedModulePluginBase')
];

const exportedNames = register(
project,
new td.DeclarationReflection('exportedNames', td.ReflectionKind.Property, plugin)
);
plugin.addChild(exportedNames);
exportedNames.type = new td.TypeOperatorType(
new td.TupleType([new td.LiteralType('repeat')]),
'readonly'
);
addExportedNames(project, plugin, ['repeat']);

const method = register(
project,
Expand Down Expand Up @@ -179,6 +188,61 @@ describe(normalizeConductorDocs, () => {
expect(signature.type?.stringify(td.TypeContext.none)).toEqual('Function');
});

it('uses @functionDeclaration decorator types for promoted Conductor methods', () => {
const project = createProject();
const fixturePath = pathlib.resolve(import.meta.dirname, 'fixtures/conductorDeclarations.ts');

const plugin = register(
project,
new td.DeclarationReflection('default', td.ReflectionKind.Class, project)
);
project.addChild(plugin);
addExportedNames(project, plugin, ['make_widget']);

const method = register(
project,
new td.DeclarationReflection('make_widget', td.ReflectionKind.Method, plugin)
);
plugin.addChild(method);
method.sources = [new td.SourceReference(fixturePath, 22, 10)];

const property = register(
project,
new td.DeclarationReflection('sample_widget', td.ReflectionKind.Property, plugin)
);
plugin.addChild(property);
property.sources = [new td.SourceReference(fixturePath, 19, 3)];
property.type = typedValue(project, 'OPAQUE');

const methodSignature = register(
project,
new td.SignatureReflection('make_widget', td.ReflectionKind.CallSignature, method)
);
method.signatures = [methodSignature];
methodSignature.parameters = [
createParameter(project, methodSignature, 'count', typedValue(project, 'NUMBER')),
createParameter(project, methodSignature, 'mapper', typedValue(project, 'CLOSURE'))
];
methodSignature.type = asyncGenerator(project, typedValue(project, 'OPAQUE'));

normalizeConductorDocs(project);

const publicFunction = project.children?.find(child => child.name === 'make_widget');
const [signature] = publicFunction?.signatures ?? [];
const publicVariable = project.children?.find(child => child.name === 'sample_widget');

expect(signature.parameters?.map(parameter => [
parameter.name,
parameter.type?.stringify(td.TypeContext.none)
])).toEqual([
['count', 'number'],
['mapper', '(widget: Widget) => Widget']
]);
expect(signature.type?.stringify(td.TypeContext.none)).toEqual('Widget');
expect(publicVariable?.kind).toEqual(td.ReflectionKind.Variable);
expect(publicVariable?.type?.stringify(td.TypeContext.none)).toEqual('Widget');
});

it('normalizes the migrated repeat bundle docs', async () => {
const repeatBundle: ResolvedBundle = {
type: 'bundle',
Expand Down Expand Up @@ -213,4 +277,32 @@ describe(normalizeConductorDocs, () => {
expect(publicFunctions.find(func => func.name === 'repeat')?.signatures?.[0].parameters?.map(parameter => parameter.name))
.toEqual(['func', 'n']);
});

it('uses declaration decorators in the migrated rune bundle docs', async () => {
const runeBundle: ResolvedBundle = {
type: 'bundle',
name: 'rune',
manifest: {},
directory: pathlib.resolve(import.meta.dirname, '../../../../../../src/bundles/rune')
};
const app = await initTypedocForJson(runeBundle, td.LogLevel.None);
const project = await app.convert();
expect(project).toBeDefined();

normalizeConductorDocs(project!);

const show = project!.children?.find(child => child.name === 'show');
const [signature] = show?.signatures ?? [];
const blank = project!.children?.find(child => child.name === 'blank');

expect(signature.parameters?.map(parameter => [
parameter.name,
parameter.type?.stringify(td.TypeContext.none)
])).toEqual([
['rune', 'Rune']
]);
expect(signature.type?.stringify(td.TypeContext.none)).toEqual('Rune');
expect(blank?.kind).toEqual(td.ReflectionKind.Variable);
expect(blank?.type?.stringify(td.TypeContext.none)).toEqual('Rune');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
type Decorator = (...args: any[]) => any;

const functionDeclaration = (_paramTypes: string, _returnType: string): Decorator => {
return () => undefined;
};

const variableDeclaration = (_type: string): Decorator => {
return () => undefined;
};

const classDeclaration = (_name: string): Decorator => {
return () => undefined;
};

@classDeclaration('Widget')
export class Widget {}

export class WidgetPlugin {
exportedNames = ['make_widget'] as const;

@variableDeclaration('Widget')
sample_widget!: Widget;

@functionDeclaration('count: number, mapper: (widget: Widget) => Widget', 'Widget')
async* make_widget(_count: unknown, _mapper: unknown) {
await Promise.resolve();
return undefined;
}
}
44 changes: 39 additions & 5 deletions lib/buildtools/src/build/docs/conductor/normalisation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,40 @@ import { dataTypeToNativeType, getDataTypeName, isConductorPluginClass, isConduc
* Rewrites one call signature from Conductor-facing types to public module-facing types.
*/
function normalizeSignature(signature: td.SignatureReflection, project: td.ProjectReflection) {
if (signature.type) {

const devDefinedTypeDeclaration: DevDefinedTypeDeclaration = { arguments: {}, return: undefined };
if (signature.comment?.blockTags) {
signature.comment.blockTags.forEach(tag => {
if (tag.tag == '@publicType' && tag.content) {
const [argName, argType] = tag.content[0].text.split(/:\s(.+)/).map(s => s.trim());
devDefinedTypeDeclaration.arguments[argName] = argType;
}
if (tag.tag == '@publicReturnType' && tag.content) {
devDefinedTypeDeclaration.return = tag.content[0].text;
}
});
signature.comment.blockTags = signature.comment.blockTags.filter(tag => tag.tag !== '@publicType' && tag.tag !== '@publicReturnType');
}

if (devDefinedTypeDeclaration.return) {
signature.type = new td.UnknownType(devDefinedTypeDeclaration.return);
} else if (signature.type) {
project.removeTypeReflections(signature.type);
signature.type = normalizeConductorType(signature.type, project);
}

signature.parameters = signature.parameters
?.filter(parameter => !isServiceParameter(parameter))
.map(parameter => {
if (parameter.type) {
project.removeTypeReflections(parameter.type);
project.removeTypeReflections(parameter.type);
if (devDefinedTypeDeclaration.arguments[parameter.name]) {
parameter.type = new td.UnknownType(devDefinedTypeDeclaration.arguments[parameter.name]);
} else if (parameter.type) {
parameter.type = normalizeConductorType(parameter.type, project);
}
return parameter;
});

}

/**
Expand Down Expand Up @@ -122,15 +142,29 @@ export function normalizeConductorType(type: td.SomeType, project: td.ProjectRef
return type;
}

type DevDefinedTypeDeclaration = {
arguments: {
[name: string]: string;
};
return: string | undefined;
};

/**
* Applies signature/type normalization to a declaration and its accessors.
*/
function normalizeDeclaration(declaration: td.DeclarationReflection, project: td.ProjectReflection) {
if (declaration.type) {
const publicData = declaration.comment?.getTag('@publicType')?.content?.[0]?.text;
if (declaration.comment) {
declaration.comment.blockTags = declaration.comment.blockTags.filter(tag => tag.tag !== '@publicType' && tag.tag !== '@publicReturnType');
}
project.removeTypeReflections(declaration.type);
declaration.type = normalizeConductorType(declaration.type, project);
if (publicData) {
declaration.type = td.ReferenceType.createBrokenReference(publicData, project, undefined);
} else {
declaration.type = normalizeConductorType(declaration.type, project);
}
}

declaration.signatures?.forEach(signature => normalizeSignature(signature, project));
declaration.indexSignatures?.forEach(signature => normalizeSignature(signature, project));

Expand Down
Loading