Skip to content

Commit

Permalink
Fix new tests
Browse files Browse the repository at this point in the history
  • Loading branch information
lorisleiva committed Nov 22, 2024
1 parent 3e8304c commit 72ec638
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 22 deletions.
22 changes: 19 additions & 3 deletions packages/visitors/src/getDefinedTypeHistogramVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { CamelCaseString } from '@codama/nodes';
import { extendVisitor, interceptVisitor, mergeVisitor, pipe, visit, Visitor } from '@codama/visitors-core';
import {
extendVisitor,
findProgramNodeFromPath,
interceptVisitor,
mergeVisitor,
NodeStack,
pipe,
recordNodeStackVisitor,
visit,
Visitor,
} from '@codama/visitors-core';

type DefinedTypeHistogramKey = CamelCaseString | `${CamelCaseString}.${CamelCaseString}`;

export type DefinedTypeHistogram = {
[key: CamelCaseString]: {
[key: DefinedTypeHistogramKey]: {
directlyAsInstructionArgs: number;
inAccounts: number;
inDefinedTypes: number;
Expand Down Expand Up @@ -33,6 +45,7 @@ function mergeHistograms(histograms: DefinedTypeHistogram[]): DefinedTypeHistogr
}

export function getDefinedTypeHistogramVisitor(): Visitor<DefinedTypeHistogram> {
const stack = new NodeStack();
let mode: 'account' | 'definedType' | 'instruction' | null = null;
let stackLevel = 0;

Expand Down Expand Up @@ -67,8 +80,10 @@ export function getDefinedTypeHistogramVisitor(): Visitor<DefinedTypeHistogram>
},

visitDefinedTypeLink(node) {
const program = findProgramNodeFromPath(stack.getPath());
const key = program ? `${program.name}.${node.name}` : node.name;
return {
[node.name]: {
[key]: {
directlyAsInstructionArgs: Number(mode === 'instruction' && stackLevel <= 1),
inAccounts: Number(mode === 'account'),
inDefinedTypes: Number(mode === 'definedType'),
Expand All @@ -88,5 +103,6 @@ export function getDefinedTypeHistogramVisitor(): Visitor<DefinedTypeHistogram>
return mergeHistograms([...dataHistograms, ...extraHistograms, ...subHistograms]);
},
}),
v => recordNodeStackVisitor(v, stack),
);
}
20 changes: 15 additions & 5 deletions packages/visitors/src/unwrapDefinedTypesVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { assertIsNodeFilter, camelCase, CamelCaseString, programNode } from '@codama/nodes';
import {
extendVisitor,
findProgramNodeFromPath,
getLastNodeFromPath,
LinkableDictionary,
NodeStack,
Expand All @@ -14,16 +15,25 @@ import {
export function unwrapDefinedTypesVisitor(typesToInline: string[] | '*' = '*') {
const linkables = new LinkableDictionary();
const stack = new NodeStack();
const typesToInlineMainCased = typesToInline === '*' ? '*' : typesToInline.map(camelCase);
const shouldInline = (definedType: CamelCaseString): boolean =>
typesToInlineMainCased === '*' || typesToInlineMainCased.includes(definedType);
const typesToInlineCamelCased = (typesToInline === '*' ? [] : typesToInline).map(fullPath => {
if (!fullPath.includes('.')) return camelCase(fullPath);
const [programName, typeName] = fullPath.split('.');
return `${camelCase(programName)}.${camelCase(typeName)}`;
});
const shouldInline = (typeName: CamelCaseString, programName: CamelCaseString | undefined): boolean => {
if (typesToInline === '*') return true;
const fullPath = `${programName}.${typeName}`;
if (!!programName && typesToInlineCamelCased.includes(fullPath)) return true;
return typesToInlineCamelCased.includes(typeName);
};

return pipe(
nonNullableIdentityVisitor(),
v =>
extendVisitor(v, {
visitDefinedTypeLink(linkType, { self }) {
if (!shouldInline(linkType.name)) {
const programName = linkType.program?.name ?? findProgramNodeFromPath(stack.getPath())?.name;
if (!shouldInline(linkType.name, programName)) {
return linkType;
}
const definedTypePath = linkables.getPathOrThrow(stack.getPath('definedTypeLinkNode'));
Expand All @@ -42,7 +52,7 @@ export function unwrapDefinedTypesVisitor(typesToInline: string[] | '*' = '*') {
.map(account => visit(account, self))
.filter(assertIsNodeFilter('accountNode')),
definedTypes: program.definedTypes
.filter(definedType => !shouldInline(definedType.name))
.filter(definedType => !shouldInline(definedType.name, program.name))
.map(type => visit(type, self))
.filter(assertIsNodeFilter('definedTypeNode')),
instructions: program.instructions
Expand Down
21 changes: 10 additions & 11 deletions packages/visitors/src/unwrapInstructionArgsDefinedTypesVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import { assertIsNode, CamelCaseString, getAllDefinedTypes, isNode } from '@codama/nodes';
import { rootNodeVisitor, visit } from '@codama/visitors-core';
import { assertIsNode, CamelCaseString, definedTypeLinkNode, isNode } from '@codama/nodes';
import { getRecordLinkablesVisitor, LinkableDictionary, rootNodeVisitor, visit } from '@codama/visitors-core';

import { getDefinedTypeHistogramVisitor } from './getDefinedTypeHistogramVisitor';
import { unwrapDefinedTypesVisitor } from './unwrapDefinedTypesVisitor';

export function unwrapInstructionArgsDefinedTypesVisitor() {
return rootNodeVisitor(root => {
const histogram = visit(root, getDefinedTypeHistogramVisitor());
const allDefinedTypes = getAllDefinedTypes(root);
const linkables = new LinkableDictionary();
visit(root, getRecordLinkablesVisitor(linkables));

const definedTypesToInline: string[] = Object.keys(histogram)
const definedTypesToInline = (Object.keys(histogram) as CamelCaseString[])
// Get all defined types used exactly once as an instruction argument.
.filter(
name =>
(histogram[name as CamelCaseString].total ?? 0) === 1 &&
(histogram[name as CamelCaseString].directlyAsInstructionArgs ?? 0) === 1,
)
.filter(key => (histogram[key].total ?? 0) === 1 && (histogram[key].directlyAsInstructionArgs ?? 0) === 1)
// Filter out enums which are better defined as external types.
.filter(name => {
const found = allDefinedTypes.find(type => type.name === name);
.filter(key => {
const names = key.split('.');
const link = names.length == 2 ? definedTypeLinkNode(names[1], names[0]) : definedTypeLinkNode(key);
const found = linkables.get([link]);
return found && !isNode(found.type, 'enumTypeNode');
});

Expand Down
50 changes: 48 additions & 2 deletions packages/visitors/test/getDefinedTypeHistogramVisitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
enumTypeNode,
instructionArgumentNode,
instructionNode,
numberTypeNode,
programNode,
rootNode,
structFieldTypeNode,
structTypeNode,
} from '@codama/nodes';
Expand Down Expand Up @@ -65,14 +67,14 @@ test('it counts the amount of times defined types are used within the tree', ()

// Then we expect the following histogram.
expect(histogram).toEqual({
myEnum: {
'customProgram.myEnum': {
directlyAsInstructionArgs: 0,
inAccounts: 1,
inDefinedTypes: 0,
inInstructionArgs: 0,
total: 1,
},
myStruct: {
'customProgram.myStruct': {
directlyAsInstructionArgs: 1,
inAccounts: 1,
inDefinedTypes: 0,
Expand All @@ -81,3 +83,47 @@ test('it counts the amount of times defined types are used within the tree', ()
},
});
});

test('it counts links from different programs separately', () => {
// Given a program node with a defined type used in another type.
const programA = programNode({
definedTypes: [
definedTypeNode({ name: 'myType', type: numberTypeNode('u8') }),
definedTypeNode({ name: 'myCopyType', type: definedTypeLinkNode('myType') }),
],
name: 'programA',
publicKey: '1111',
});

// And another program with a defined type sharing the same name.
const programB = programNode({
definedTypes: [
definedTypeNode({ name: 'myType', type: numberTypeNode('u16') }),
definedTypeNode({ name: 'myCopyType', type: definedTypeLinkNode('myType') }),
],
name: 'programB',
publicKey: '2222',
});

// When we unwrap the defined type from programA.
const node = rootNode(programA, [programB]);
const histogram = visit(node, getDefinedTypeHistogramVisitor());

// Then we expect programA to have been modified but not programB.
expect(histogram).toStrictEqual({
'programA.myType': {
directlyAsInstructionArgs: 0,
inAccounts: 0,
inDefinedTypes: 1,
inInstructionArgs: 0,
total: 1,
},
'programB.myType': {
directlyAsInstructionArgs: 0,
inAccounts: 0,
inDefinedTypes: 1,
inInstructionArgs: 0,
total: 1,
},
});
});
2 changes: 1 addition & 1 deletion packages/visitors/test/unwrapDefinedTypesVisitor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ test('it does not unwrap types from the wrong programs', () => {

// When we unwrap the defined type from programA.
const node = rootNode(programA, [programB]);
const result = visit(node, unwrapDefinedTypesVisitor(['myType']));
const result = visit(node, unwrapDefinedTypesVisitor(['programA.myType']));

// Then we expect programA to have been modified but not programB.
assertIsNode(result, 'rootNode');
Expand Down

0 comments on commit 72ec638

Please sign in to comment.