Skip to content

Commit da1d933

Browse files
authored
Helix codemod update with new patterns and util functions (#7842)
* WIP: helix codemod revisit, have yet to do edit flows and unit tests * Move all engineless capable cases to unit tests * Clean up ops * Fix test * Add missing unit tests and clean up args * Clean up for review * Missing case in refactor
1 parent 02c4331 commit da1d933

File tree

9 files changed

+737
-804
lines changed

9 files changed

+737
-804
lines changed

e2e/playwright/point-click.spec.ts

Lines changed: 21 additions & 306 deletions
Large diffs are not rendered by default.

src/lang/modifyAst.ts

Lines changed: 0 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -511,92 +511,6 @@ export function addModuleImport({
511511
}
512512
}
513513

514-
/**
515-
* Append a helix to the AST
516-
*/
517-
export function addHelix({
518-
node,
519-
axis,
520-
cylinder,
521-
revolutions,
522-
angleStart,
523-
radius,
524-
length,
525-
ccw,
526-
insertIndex,
527-
variableName,
528-
}: {
529-
node: Node<Program>
530-
axis?: Node<Literal> | Node<Name | CallExpressionKw>
531-
cylinder?: VariableDeclarator
532-
revolutions: Expr
533-
angleStart: Expr
534-
radius?: Expr
535-
length?: Expr
536-
ccw?: boolean
537-
insertIndex?: number
538-
variableName?: string
539-
}): { modifiedAst: Node<Program>; pathToNode: PathToNode } {
540-
const modifiedAst = structuredClone(node)
541-
const name =
542-
variableName ?? findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.HELIX)
543-
const modeArgs: CallExpressionKw['arguments'] = []
544-
if (axis && radius) {
545-
modeArgs.push(createLabeledArg('axis', axis))
546-
modeArgs.push(createLabeledArg('radius', radius))
547-
if (length) {
548-
modeArgs.push(createLabeledArg('length', length))
549-
}
550-
} else if (cylinder) {
551-
modeArgs.push(
552-
createLabeledArg('cylinder', createLocalName(cylinder.id.name))
553-
)
554-
}
555-
556-
// Extra labeled args expressions
557-
const ccwExpr = ccw ? [createLabeledArg('ccw', createLiteral(ccw))] : []
558-
559-
const variable = createVariableDeclaration(
560-
name,
561-
createCallExpressionStdLibKw(
562-
'helix',
563-
null, // Not in a pipeline
564-
[
565-
...modeArgs,
566-
createLabeledArg('revolutions', revolutions),
567-
createLabeledArg('angleStart', angleStart),
568-
...ccwExpr,
569-
]
570-
)
571-
)
572-
573-
const insertAt =
574-
insertIndex !== undefined
575-
? insertIndex
576-
: modifiedAst.body.length
577-
? modifiedAst.body.length
578-
: 0
579-
580-
modifiedAst.body.length
581-
? modifiedAst.body.splice(insertAt, 0, variable)
582-
: modifiedAst.body.push(variable)
583-
const argIndex = 0
584-
const pathToNode: PathToNode = [
585-
['body', ''],
586-
[insertAt, 'index'],
587-
['declaration', 'VariableDeclaration'],
588-
['init', 'VariableDeclarator'],
589-
['arguments', 'CallExpressionKw'],
590-
[argIndex, ARG_INDEX_FIELD],
591-
['arg', LABELED_ARG_FIELD],
592-
]
593-
594-
return {
595-
modifiedAst,
596-
pathToNode,
597-
}
598-
}
599-
600514
/**
601515
* Return a modified clone of an AST with a named constant inserted into the body
602516
*/

src/lang/modifyAst/geometry.test.ts

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import { addHelix } from '@src/lang/modifyAst/geometry'
2+
import { assertParse, recast } from '@src/lang/wasm'
3+
import { err } from '@src/lib/trap'
4+
import { enginelessExecutor } from '@src/lib/testHelpers'
5+
import type { Selections } from '@src/lib/selections'
6+
import { stringToKclExpression } from '@src/lib/kclHelpers'
7+
import type { KclCommandValue } from '@src/lib/commandTypes'
8+
import { createPathToNodeForLastVariable } from '@src/lang/modifyAst'
9+
10+
async function getAstAndArtifactGraph(code: string) {
11+
const ast = assertParse(code)
12+
if (err(ast)) throw ast
13+
14+
const { artifactGraph } = await enginelessExecutor(ast)
15+
return { ast, artifactGraph }
16+
}
17+
18+
describe('Testing addHelix', () => {
19+
it('should add a standalone call on default axis selection', async () => {
20+
const expectedNewLine = `helix001 = helix(
21+
axis = X,
22+
revolutions = 1,
23+
angleStart = 2,
24+
radius = 3,
25+
length = 4,
26+
)`
27+
const { ast, artifactGraph } = await getAstAndArtifactGraph('')
28+
const result = addHelix({
29+
ast,
30+
artifactGraph,
31+
axis: 'X',
32+
revolutions: (await stringToKclExpression('1')) as KclCommandValue,
33+
angleStart: (await stringToKclExpression('2')) as KclCommandValue,
34+
radius: (await stringToKclExpression('3')) as KclCommandValue,
35+
length: (await stringToKclExpression('4')) as KclCommandValue,
36+
})
37+
if (err(result)) throw result
38+
await enginelessExecutor(ast)
39+
const newCode = recast(result.modifiedAst)
40+
expect(newCode).toContain(expectedNewLine)
41+
})
42+
43+
it('should add a standalone call on default axis selection with ccw true', async () => {
44+
const expectedNewLine = `helix001 = helix(
45+
axis = X,
46+
revolutions = 1,
47+
angleStart = 2,
48+
radius = 3,
49+
length = 4,
50+
ccw = true,
51+
)`
52+
const { ast, artifactGraph } = await getAstAndArtifactGraph('')
53+
const result = addHelix({
54+
ast,
55+
artifactGraph,
56+
axis: 'X',
57+
revolutions: (await stringToKclExpression('1')) as KclCommandValue,
58+
angleStart: (await stringToKclExpression('2')) as KclCommandValue,
59+
radius: (await stringToKclExpression('3')) as KclCommandValue,
60+
length: (await stringToKclExpression('4')) as KclCommandValue,
61+
ccw: true,
62+
})
63+
if (err(result)) throw result
64+
await enginelessExecutor(ast)
65+
const newCode = recast(result.modifiedAst)
66+
expect(newCode).toContain(expectedNewLine)
67+
})
68+
69+
it('should edit a standalone call with default axis selection', async () => {
70+
const code = `helix001 = helix(
71+
axis = X,
72+
revolutions = 1,
73+
angleStart = 2,
74+
radius = 3,
75+
length = 4,
76+
)`
77+
const expectedNewLine = `helix001 = helix(
78+
axis = Y,
79+
revolutions = 11,
80+
angleStart = 12,
81+
radius = 13,
82+
length = 14,
83+
)`
84+
const { ast, artifactGraph } = await getAstAndArtifactGraph(code)
85+
const result = addHelix({
86+
ast,
87+
artifactGraph,
88+
axis: 'Y',
89+
revolutions: (await stringToKclExpression('11')) as KclCommandValue,
90+
angleStart: (await stringToKclExpression('12')) as KclCommandValue,
91+
radius: (await stringToKclExpression('13')) as KclCommandValue,
92+
length: (await stringToKclExpression('14')) as KclCommandValue,
93+
nodeToEdit: createPathToNodeForLastVariable(ast),
94+
})
95+
if (err(result)) throw result
96+
await enginelessExecutor(ast)
97+
const newCode = recast(result.modifiedAst)
98+
expect(newCode).not.toContain(code)
99+
expect(newCode).toContain(expectedNewLine)
100+
})
101+
102+
const segmentInPath = `sketch001 = startSketchOn(XZ)
103+
profile001 = startProfile(sketch001, at = [0, 0])
104+
|> yLine(length = 100)
105+
|> line(endAbsolute = [100, 0])
106+
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
107+
|> close()`
108+
109+
const helixFromSegmentInPath = `sketch001 = startSketchOn(XZ)
110+
profile001 = startProfile(sketch001, at = [0, 0])
111+
|> yLine(length = 100, tag = $seg01)
112+
|> line(endAbsolute = [100, 0])
113+
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
114+
|> close()
115+
helix001 = helix(
116+
axis = seg01,
117+
revolutions = 1,
118+
angleStart = 2,
119+
radius = 3,
120+
)
121+
`
122+
123+
it('should add a standalone call on segment selection', async () => {
124+
const { ast, artifactGraph } = await getAstAndArtifactGraph(segmentInPath)
125+
const segment = [...artifactGraph.values()].find(
126+
(n) => n.type === 'segment'
127+
)
128+
const edge: Selections = {
129+
graphSelections: [
130+
{
131+
artifact: segment,
132+
codeRef: segment!.codeRef,
133+
},
134+
],
135+
otherSelections: [],
136+
}
137+
const result = addHelix({
138+
ast,
139+
artifactGraph,
140+
edge,
141+
revolutions: (await stringToKclExpression('1')) as KclCommandValue,
142+
angleStart: (await stringToKclExpression('2')) as KclCommandValue,
143+
radius: (await stringToKclExpression('3')) as KclCommandValue,
144+
})
145+
if (err(result)) throw result
146+
await enginelessExecutor(ast)
147+
const newCode = recast(result.modifiedAst)
148+
expect(newCode).toBe(helixFromSegmentInPath)
149+
})
150+
151+
it('should edit a standalone call on segment selection', async () => {
152+
const { ast, artifactGraph } = await getAstAndArtifactGraph(
153+
helixFromSegmentInPath
154+
)
155+
const segment = [...artifactGraph.values()].find(
156+
(n) => n.type === 'segment'
157+
)
158+
const edge: Selections = {
159+
graphSelections: [
160+
{
161+
artifact: segment,
162+
codeRef: segment!.codeRef,
163+
},
164+
],
165+
otherSelections: [],
166+
}
167+
const result = addHelix({
168+
ast,
169+
artifactGraph,
170+
edge,
171+
revolutions: (await stringToKclExpression('4')) as KclCommandValue,
172+
angleStart: (await stringToKclExpression('5')) as KclCommandValue,
173+
radius: (await stringToKclExpression('6')) as KclCommandValue,
174+
ccw: true,
175+
nodeToEdit: createPathToNodeForLastVariable(ast),
176+
})
177+
if (err(result)) throw result
178+
await enginelessExecutor(ast)
179+
const newCode = recast(result.modifiedAst)
180+
expect(newCode).toContain(`helix001 = helix(
181+
axis = seg01,
182+
revolutions = 4,
183+
angleStart = 5,
184+
radius = 6,
185+
ccw = true,
186+
)`)
187+
})
188+
189+
// For now to avoid setting up an engine connection, the sweepEdge case is done in e2e (point-click.spec.ts)
190+
191+
const cylinderExtrude = `sketch001 = startSketchOn(XY)
192+
profile001 = circle(sketch001, center = [0, 0], radius = 100)
193+
extrude001 = extrude(profile001, length = 100)`
194+
195+
const helixFromCylinder = `${cylinderExtrude}
196+
helix001 = helix(
197+
cylinder = extrude001,
198+
revolutions = 1,
199+
angleStart = 2,
200+
ccw = true,
201+
)
202+
`
203+
204+
it('should add a standalone call on cylinder selection', async () => {
205+
const { ast, artifactGraph } = await getAstAndArtifactGraph(cylinderExtrude)
206+
const sweep = [...artifactGraph.values()].find((n) => n.type === 'sweep')
207+
const cylinder: Selections = {
208+
graphSelections: [
209+
{
210+
artifact: sweep,
211+
codeRef: sweep!.codeRef,
212+
},
213+
],
214+
otherSelections: [],
215+
}
216+
const result = addHelix({
217+
ast,
218+
artifactGraph,
219+
cylinder,
220+
revolutions: (await stringToKclExpression('1')) as KclCommandValue,
221+
angleStart: (await stringToKclExpression('2')) as KclCommandValue,
222+
ccw: true,
223+
})
224+
if (err(result)) throw result
225+
await enginelessExecutor(ast)
226+
const newCode = recast(result.modifiedAst)
227+
expect(newCode).toBe(helixFromCylinder)
228+
})
229+
230+
it('should edit a standalone call on cylinder selection', async () => {
231+
const { ast, artifactGraph } =
232+
await getAstAndArtifactGraph(helixFromCylinder)
233+
const sweep = [...artifactGraph.values()].find((n) => n.type === 'sweep')
234+
const cylinder: Selections = {
235+
graphSelections: [
236+
{
237+
artifact: sweep,
238+
codeRef: sweep!.codeRef,
239+
},
240+
],
241+
otherSelections: [],
242+
}
243+
const result = addHelix({
244+
ast,
245+
artifactGraph,
246+
cylinder,
247+
revolutions: (await stringToKclExpression('11')) as KclCommandValue,
248+
angleStart: (await stringToKclExpression('22')) as KclCommandValue,
249+
ccw: false,
250+
nodeToEdit: createPathToNodeForLastVariable(ast),
251+
})
252+
if (err(result)) throw result
253+
await enginelessExecutor(ast)
254+
const newCode = recast(result.modifiedAst)
255+
expect(newCode).toContain(
256+
`helix001 = helix(cylinder = extrude001, revolutions = 11, angleStart = 22`
257+
)
258+
})
259+
})

0 commit comments

Comments
 (0)