import { assertParse, recast, initPromise, PathToNode, Identifier, topLevelRange, PipeExpression, CallExpression, VariableDeclarator, } from './wasm' import { findAllPreviousVariables, isNodeSafeToReplace, isTypeInValue, hasExtrudeSketch, findUsesOfTagInPipe, hasSketchPipeBeenExtruded, doesSceneHaveSweepableSketch, traverse, getNodeFromPath, doesSceneHaveExtrudedSketch, } from './queryAst' import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils' import { enginelessExecutor } from '../lib/testHelpers' import { createArrayExpression, createCallExpression, createLiteral, createPipeSubstitution, createCallExpressionStdLib, } from './modifyAst' import { err } from 'lib/trap' import { codeRefFromRange } from './std/artifactGraph' import { addCallExpressionsToPipe, addCloseToPipe } from 'lang/std/sketch' beforeAll(async () => { await initPromise }) describe('findAllPreviousVariables', () => { it('should find all previous variables', async () => { const code = `baseThick = 1 armAngle = 60 baseThickHalf = baseThick / 2 halfArmAngle = armAngle / 2 arrExpShouldNotBeIncluded = [1, 2, 3] objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 } part001 = startSketchOn('XY') |> startProfileAt([0, 0], %) |> yLineTo(1, %) |> xLine(3.84, %) // selection-range-7ish-before-this variableBelowShouldNotBeIncluded = 3 ` const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7 const ast = assertParse(code) const execState = await enginelessExecutor(ast) const { variables, bodyPath, insertIndex } = findAllPreviousVariables( ast, execState.variables, topLevelRange(rangeStart, rangeStart) ) expect(variables).toEqual([ { key: 'baseThick', value: 1 }, { key: 'armAngle', value: 60 }, { key: 'baseThickHalf', value: 0.5 }, { key: 'halfArmAngle', value: 30 }, // no arrExpShouldNotBeIncluded, variableBelowShouldNotBeIncluded etc ]) // there are 4 number variables and 2 non-number variables before the sketch var // ∴ the insert index should be 6 expect(insertIndex).toEqual(6) expect(bodyPath).toEqual([['body', '']]) }) }) describe('testing argIsNotIdentifier', () => { const code = `part001 = startSketchOn('XY') |> startProfileAt([-1.2, 4.83], %) |> line(end = [2.8, 0]) |> angledLine([100 + 100, 3.09], %) |> angledLine([abc, 3.09], %) |> angledLine([def('yo'), 3.09], %) |> angledLine([ghi(%), 3.09], %) |> angledLine([jkl('yo') + 2, 3.09], %) yo = 5 + 6 yo2 = hmm([identifierGuy + 5])` it('find a safe binaryExpression', () => { const ast = assertParse(code) const rangeStart = code.indexOf('100 + 100') + 2 const result = isNodeSafeToReplace( ast, topLevelRange(rangeStart, rangeStart) ) if (err(result)) throw result expect(result.isSafe).toBe(true) expect(result.value?.type).toBe('BinaryExpression') expect(code.slice(result.value.start, result.value.end)).toBe('100 + 100') const replaced = result.replacer(structuredClone(ast), 'replaceName') if (err(replaced)) throw replaced const outCode = recast(replaced.modifiedAst) expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`) }) it('find a safe Identifier', () => { const ast = assertParse(code) const rangeStart = code.indexOf('abc') const result = isNodeSafeToReplace( ast, topLevelRange(rangeStart, rangeStart) ) if (err(result)) throw result expect(result.isSafe).toBe(true) expect(result.value?.type).toBe('Identifier') expect(code.slice(result.value.start, result.value.end)).toBe('abc') }) it('find a safe CallExpression', () => { const ast = assertParse(code) const rangeStart = code.indexOf('def') const result = isNodeSafeToReplace( ast, topLevelRange(rangeStart, rangeStart) ) if (err(result)) throw result expect(result.isSafe).toBe(true) expect(result.value?.type).toBe('CallExpression') expect(code.slice(result.value.start, result.value.end)).toBe("def('yo')") const replaced = result.replacer(structuredClone(ast), 'replaceName') if (err(replaced)) throw replaced const outCode = recast(replaced.modifiedAst) expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`) }) it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => { const ast = assertParse(code) const rangeStart = code.indexOf('ghi') const range = topLevelRange(rangeStart, rangeStart) const result = isNodeSafeToReplace(ast, range) if (err(result)) throw result expect(result.isSafe).toBe(false) expect(result.value?.type).toBe('CallExpression') expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)') }) it('find an UNsafe Identifier, as it is a callee', () => { const ast = assertParse(code) const rangeStart = code.indexOf('ine(end = [2.8,') const result = isNodeSafeToReplace( ast, topLevelRange(rangeStart, rangeStart) ) if (err(result)) throw result expect(result.isSafe).toBe(false) expect(result.value?.type).toBe('CallExpressionKw') expect(code.slice(result.value.start, result.value.end)).toBe( 'line(end = [2.8, 0])' ) }) it("find a safe BinaryExpression that's assigned to a variable", () => { const ast = assertParse(code) const rangeStart = code.indexOf('5 + 6') + 1 const result = isNodeSafeToReplace( ast, topLevelRange(rangeStart, rangeStart) ) if (err(result)) throw result expect(result.isSafe).toBe(true) expect(result.value?.type).toBe('BinaryExpression') expect(code.slice(result.value.start, result.value.end)).toBe('5 + 6') const replaced = result.replacer(structuredClone(ast), 'replaceName') if (err(replaced)) throw replaced const outCode = recast(replaced.modifiedAst) expect(outCode).toContain(`yo = replaceName`) }) it('find a safe BinaryExpression that has a CallExpression within', () => { const ast = assertParse(code) const rangeStart = code.indexOf('jkl') + 1 const result = isNodeSafeToReplace( ast, topLevelRange(rangeStart, rangeStart) ) if (err(result)) throw result expect(result.isSafe).toBe(true) expect(result.value?.type).toBe('BinaryExpression') expect(code.slice(result.value.start, result.value.end)).toBe( "jkl('yo') + 2" ) const replaced = result.replacer(structuredClone(ast), 'replaceName') if (err(replaced)) throw replaced const { modifiedAst } = replaced const outCode = recast(modifiedAst) expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`) }) it('find a safe BinaryExpression within a CallExpression', () => { const ast = assertParse(code) const rangeStart = code.indexOf('identifierGuy') + 1 const result = isNodeSafeToReplace( ast, topLevelRange(rangeStart, rangeStart) ) if (err(result)) throw result expect(result.isSafe).toBe(true) expect(result.value?.type).toBe('BinaryExpression') expect(code.slice(result.value.start, result.value.end)).toBe( 'identifierGuy + 5' ) const replaced = result.replacer(structuredClone(ast), 'replaceName') if (err(replaced)) throw replaced const { modifiedAst } = replaced const outCode = recast(modifiedAst) expect(outCode).toContain(`yo2 = hmm([replaceName])`) }) describe('testing isTypeInValue', () => { it('finds the pipeSubstituion', () => { const val = createCallExpression('yoyo', [ createArrayExpression([ createLiteral(1), createCallExpression('yoyo2', [createPipeSubstitution()]), createLiteral('hey'), ]), ]) expect(isTypeInValue(val, 'PipeSubstitution')).toBe(true) }) it('There is no pipeSubstituion', () => { const val = createCallExpression('yoyo', [ createArrayExpression([ createLiteral(1), createCallExpression('yoyo2', [createLiteral(5)]), createLiteral('hey'), ]), ]) expect(isTypeInValue(val, 'PipeSubstitution')).toBe(false) }) }) }) describe('testing getNodePathFromSourceRange', () => { const code = `part001 = startSketchOn('XY') |> startProfileAt([0.39, -0.05], %) |> line(end = [0.94, 2.61]) |> line(end = [-0.21, -1.4])` it('finds the second line when cursor is put at the end', () => { const searchLn = `line(end = [0.94, 2.61])` const sourceIndex = code.indexOf(searchLn) + searchLn.length const ast = assertParse(code) const result = getNodePathFromSourceRange( ast, topLevelRange(sourceIndex, sourceIndex) ) expect(result).toEqual([ ['body', ''], [0, 'index'], ['declaration', 'VariableDeclaration'], ['init', ''], ['body', 'PipeExpression'], [2, 'index'], ]) }) it('finds the last line when cursor is put at the end', () => { const searchLn = `line(end = [-0.21, -1.4])` const sourceIndex = code.indexOf(searchLn) + searchLn.length const ast = assertParse(code) const result = getNodePathFromSourceRange( ast, topLevelRange(sourceIndex, sourceIndex) ) const expected = [ ['body', ''], [0, 'index'], ['declaration', 'VariableDeclaration'], ['init', ''], ['body', 'PipeExpression'], [3, 'index'], ] expect(result).toEqual(expected) // expect similar result for start of line const startSourceIndex = code.indexOf(searchLn) const startResult = getNodePathFromSourceRange( ast, topLevelRange(startSourceIndex, startSourceIndex) ) expect(startResult).toEqual([...expected, ['callee', 'CallExpressionKw']]) // expect similar result when whole line is selected const selectWholeThing = getNodePathFromSourceRange( ast, topLevelRange(startSourceIndex, sourceIndex) ) expect(selectWholeThing).toEqual(expected) }) it('finds the node in if-else condition', () => { const code = `y = 0 x = if x > y { x + 1 } else { y }` const searchLn = `x > y` const sourceIndex = code.indexOf(searchLn) const ast = assertParse(code) const result = getNodePathFromSourceRange( ast, topLevelRange(sourceIndex, sourceIndex) ) expect(result).toEqual([ ['body', ''], [1, 'index'], ['declaration', 'VariableDeclaration'], ['init', ''], ['cond', 'IfExpression'], ['left', 'BinaryExpression'], ]) const _node = getNodeFromPath(ast, result) if (err(_node)) throw _node expect(_node.node.type).toEqual('Identifier') expect(_node.node.name).toEqual('x') }) it('finds the node in if-else then', () => { const code = `y = 0 x = if x > y { x + 1 } else { y }` const searchLn = `x + 1` const sourceIndex = code.indexOf(searchLn) const ast = assertParse(code) const result = getNodePathFromSourceRange( ast, topLevelRange(sourceIndex, sourceIndex) ) expect(result).toEqual([ ['body', ''], [1, 'index'], ['declaration', 'VariableDeclaration'], ['init', ''], ['then_val', 'IfExpression'], ['body', 'IfExpression'], [0, 'index'], ['expression', 'ExpressionStatement'], ['left', 'BinaryExpression'], ]) const _node = getNodeFromPath(ast, result) if (err(_node)) throw _node expect(_node.node.type).toEqual('Identifier') expect(_node.node.name).toEqual('x') }) it('finds the node in import statement item', () => { const code = `import foo, bar as baz from 'thing.kcl'` const searchLn = `bar` const sourceIndex = code.indexOf(searchLn) const ast = assertParse(code) const result = getNodePathFromSourceRange( ast, topLevelRange(sourceIndex, sourceIndex) ) expect(result).toEqual([ ['body', ''], [0, 'index'], ['selector', 'ImportStatement'], ['items', 'ImportSelector'], [1, 'index'], ['name', 'ImportItem'], ]) const _node = getNodeFromPath(ast, result) if (err(_node)) throw _node expect(_node.node.type).toEqual('Identifier') expect(_node.node.name).toEqual('bar') }) }) describe('testing hasExtrudeSketch', () => { it('find sketch', async () => { const exampleCode = `length001 = 2 part001 = startSketchAt([-1.41, 3.46]) |> line(end = [19.49, 1.16], tag = $seg01) |> angledLine([-35, length001], %) |> line(end = [-3.22, -7.36]) |> angledLine([-175, segLen(seg01)], %)` const ast = assertParse(exampleCode) const execState = await enginelessExecutor(ast) const result = hasExtrudeSketch({ ast, selection: { codeRef: codeRefFromRange(topLevelRange(100, 101), ast), }, memVars: execState.variables, }) expect(result).toEqual(true) }) it('find solid', async () => { const exampleCode = `length001 = 2 part001 = startSketchAt([-1.41, 3.46]) |> line(end = [19.49, 1.16], tag = $seg01) |> angledLine([-35, length001], %) |> line(end = [-3.22, -7.36]) |> angledLine([-175, segLen(seg01)], %) |> extrude(length = 1)` const ast = assertParse(exampleCode) const execState = await enginelessExecutor(ast) const result = hasExtrudeSketch({ ast, selection: { codeRef: codeRefFromRange(topLevelRange(100, 101), ast), }, memVars: execState.variables, }) expect(result).toEqual(true) }) it('finds nothing', async () => { const exampleCode = `length001 = 2` const ast = assertParse(exampleCode) const execState = await enginelessExecutor(ast) const result = hasExtrudeSketch({ ast, selection: { codeRef: codeRefFromRange(topLevelRange(10, 11), ast), }, memVars: execState.variables, }) expect(result).toEqual(false) }) }) describe('Testing findUsesOfTagInPipe', () => { const exampleCode = `part001 = startSketchOn('-XZ') |> startProfileAt([68.12, 156.65], %) |> line(end = [306.21, 198.82]) |> line(end = [306.21, 198.85], tag = $seg01) |> angledLine([-65, segLen(seg01)], %) |> line(end = [306.21, 198.87]) |> angledLine([65, segLen(seg01)], %)` it('finds the current segment', async () => { const ast = assertParse(exampleCode) const lineOfInterest = `198.85], tag = $seg01` const characterIndex = exampleCode.indexOf(lineOfInterest) + lineOfInterest.length const pathToNode = getNodePathFromSourceRange( ast, topLevelRange(characterIndex, characterIndex) ) const result = findUsesOfTagInPipe(ast, pathToNode) expect(result).toHaveLength(2) result.forEach((range) => { expect(exampleCode.slice(range[0], range[1])).toContain('segLen') }) }) it('find no tag if line has no tag', () => { const ast = assertParse(exampleCode) const lineOfInterest = `line(end = [306.21, 198.82])` const characterIndex = exampleCode.indexOf(lineOfInterest) + lineOfInterest.length const pathToNode = getNodePathFromSourceRange( ast, topLevelRange(characterIndex, characterIndex) ) const result = findUsesOfTagInPipe(ast, pathToNode) expect(result).toHaveLength(0) }) }) describe('Testing hasSketchPipeBeenExtruded', () => { const exampleCode = `sketch001 = startSketchOn('XZ') |> startProfileAt([3.29, 7.86], %) |> line(end = [2.48, 2.44]) |> line(end = [2.66, 1.17]) |> line(end = [3.75, 0.46]) |> line(end = [4.99, -0.46], tag = $seg01) |> line(end = [3.3, -2.12]) |> line(end = [2.16, -3.33]) |> line(end = [0.85, -3.08]) |> line(end = [-0.18, -3.36]) |> line(end = [-3.86, -2.73]) |> line(end = [-17.67, 0.85]) |> close() extrude001 = extrude(sketch001, length = 10) sketch002 = startSketchOn(extrude001, seg01) |> startProfileAt([-12.94, 6.6], %) |> line(end = [2.45, -0.2]) |> line(end = [-2, -1.25]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() sketch003 = startSketchOn(extrude001, 'END') |> startProfileAt([8.14, 2.8], %) |> line(end = [-1.24, 4.39]) |> line(end = [3.79, 1.91]) |> line(end = [1.77, -2.95]) |> line(end = [3.12, 1.74]) |> line(end = [1.91, -4.09]) |> line(end = [-5.6, -2.75]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() |> extrude(length = 3.14) ` it('identifies sketch001 pipe as extruded (extrusion after pipe)', async () => { const ast = assertParse(exampleCode) const lineOfInterest = `line(end = [4.99, -0.46], tag = $seg01)` const characterIndex = exampleCode.indexOf(lineOfInterest) + lineOfInterest.length const extruded = hasSketchPipeBeenExtruded( { codeRef: codeRefFromRange( topLevelRange(characterIndex, characterIndex), ast ), }, ast ) expect(extruded).toBeTruthy() }) it('identifies sketch002 pipe as not extruded', async () => { const ast = assertParse(exampleCode) const lineOfInterest = `line(end = [2.45, -0.2])` const characterIndex = exampleCode.indexOf(lineOfInterest) + lineOfInterest.length const extruded = hasSketchPipeBeenExtruded( { codeRef: codeRefFromRange( topLevelRange(characterIndex, characterIndex), ast ), }, ast ) expect(extruded).toBeFalsy() }) it('identifies sketch003 pipe as extruded (extrusion within pipe)', async () => { const ast = assertParse(exampleCode) const lineOfInterest = `|> line(end = [3.12, 1.74])` const characterIndex = exampleCode.indexOf(lineOfInterest) + lineOfInterest.length const extruded = hasSketchPipeBeenExtruded( { codeRef: codeRefFromRange( topLevelRange(characterIndex, characterIndex), ast ), }, ast ) expect(extruded).toBeTruthy() }) }) describe('Testing doesSceneHaveSweepableSketch', () => { it('finds sketch001 pipe to be extruded', async () => { const exampleCode = `sketch001 = startSketchOn('XZ') |> startProfileAt([3.29, 7.86], %) |> line(end = [2.48, 2.44]) |> line(end = [-3.86, -2.73]) |> line(end = [-17.67, 0.85]) |> close() extrude001 = extrude(sketch001, length = 10) sketch002 = startSketchOn(extrude001, $seg01) |> startProfileAt([-12.94, 6.6], %) |> line(end = [2.45, -0.2]) |> line(end = [-2, -1.25]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close() ` const ast = assertParse(exampleCode) const extrudable = doesSceneHaveSweepableSketch(ast) expect(extrudable).toBeTruthy() }) it('finds sketch001 and sketch002 pipes to be lofted', async () => { const exampleCode = `sketch001 = startSketchOn('XZ') |> circle({ center = [0, 0], radius = 1 }, %) plane001 = offsetPlane('XZ', 2) sketch002 = startSketchOn(plane001) |> circle({ center = [0, 0], radius = 3 }, %) ` const ast = assertParse(exampleCode) const extrudable = doesSceneHaveSweepableSketch(ast, 2) expect(extrudable).toBeTruthy() }) it('should recognize that sketch001 has been extruded', async () => { const exampleCode = `sketch001 = startSketchOn('XZ') |> startProfileAt([3.29, 7.86], %) |> line(end = [2.48, 2.44]) |> line(end = [-3.86, -2.73]) |> line(end = [-17.67, 0.85]) |> close() extrude001 = extrude(sketch001, length = 10) ` const ast = assertParse(exampleCode) const extrudable = doesSceneHaveSweepableSketch(ast) expect(extrudable).toBeFalsy() }) }) describe('Testing doesSceneHaveExtrudedSketch', () => { it('finds extruded sketch as variable', async () => { const exampleCode = `sketch001 = startSketchOn('XZ') |> circle({ center = [0, 0], radius = 1 }, %) extrude001 = extrude(sketch001, length = 1) ` const ast = assertParse(exampleCode) if (err(ast)) throw ast const extrudable = doesSceneHaveExtrudedSketch(ast) expect(extrudable).toBeTruthy() }) it('finds extruded sketch in pipe', async () => { const exampleCode = `extrude001 = startSketchOn('XZ') |> circle({ center = [0, 0], radius = 1 }, %) |> extrude(length = 1) ` const ast = assertParse(exampleCode) if (err(ast)) throw ast const extrudable = doesSceneHaveExtrudedSketch(ast) expect(extrudable).toBeTruthy() }) it('finds no extrusion with sketch only', async () => { const exampleCode = `extrude001 = startSketchOn('XZ') |> circle({ center = [0, 0], radius = 1 }, %) ` const ast = assertParse(exampleCode) if (err(ast)) throw ast const extrudable = doesSceneHaveExtrudedSketch(ast) expect(extrudable).toBeFalsy() }) }) describe('Testing traverse and pathToNode', () => { it.each([ ['basic', '2.73'], [ 'very nested, array, object, callExpression, array, memberExpression', '.yo', ], ])('testing %s', async (testName, literalOfInterest) => { const code = `myVar = 5 sketch001 = startSketchOn('XZ') |> startProfileAt([3.29, 7.86], %) |> line(end = [2.48, 2.44]) |> line(end = [-3.86, -2.73]) |> line(end = [-17.67, 0.85]) |> close() bing = { yo: 55 } myNestedVar = [ { prop: line(end = [bing.yo, 21], tag = sketch001) } ] ` const ast = assertParse(code) let pathToNode: PathToNode = [] traverse(ast, { enter: (node, path) => { if ( node.type === 'Literal' && String((node as any).value.value) === literalOfInterest ) { pathToNode = path } else if ( node.type === 'Identifier' && literalOfInterest.includes(node.name) ) { pathToNode = path } }, }) const literalIndex = code.indexOf(literalOfInterest) const pathToNode2 = getNodePathFromSourceRange( ast, topLevelRange(literalIndex + 2, literalIndex + 2) ) expect(pathToNode).toEqual(pathToNode2) }) }) describe('Testing specific sketch getNodeFromPath workflow', () => { it('should parse the code', () => { const openSketch = `sketch001 = startSketchOn('XZ') |> startProfileAt([0.02, 0.22], %) |> xLine(0.39, %) |> line([0.02, -0.17], %) |> yLine(-0.15, %) |> line([-0.21, -0.02], %) |> xLine(-0.15, %) |> line([-0.02, 0.21], %) |> line([-0.08, 0.05], %)` const ast = assertParse(openSketch) expect(ast.start).toEqual(0) expect(ast.end).toEqual(227) }) it('should find the location to add new lineTo', () => { const openSketch = `sketch001 = startSketchOn('XZ') |> startProfileAt([0.02, 0.22], %) |> xLine(0.39, %) |> line([0.02, -0.17], %) |> yLine(-0.15, %) |> line([-0.21, -0.02], %) |> xLine(-0.15, %) |> line([-0.02, 0.21], %) |> line([-0.08, 0.05], %)` const ast = assertParse(openSketch) const sketchSnippet = `startProfileAt([0.02, 0.22], %)` const sketchRange = topLevelRange( openSketch.indexOf(sketchSnippet), openSketch.indexOf(sketchSnippet) + sketchSnippet.length ) const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange) const modifiedAst = addCallExpressionsToPipe({ node: ast, variables: {}, pathToNode: sketchPathToNode, expressions: [ createCallExpressionStdLib( 'lineTo', // We are forcing lineTo! [ createArrayExpression([ createCallExpressionStdLib('profileStartX', [ createPipeSubstitution(), ]), createCallExpressionStdLib('profileStartY', [ createPipeSubstitution(), ]), ]), createPipeSubstitution(), ] ), ], }) if (err(modifiedAst)) throw modifiedAst const recasted = recast(modifiedAst) const expectedCode = `sketch001 = startSketchOn('XZ') |> startProfileAt([0.02, 0.22], %) |> xLine(0.39, %) |> line([0.02, -0.17], %) |> yLine(-0.15, %) |> line([-0.21, -0.02], %) |> xLine(-0.15, %) |> line([-0.02, 0.21], %) |> line([-0.08, 0.05], %) |> lineTo([profileStartX(%), profileStartY(%)], %) ` expect(recasted).toEqual(expectedCode) }) it('it should find the location to add close', () => { const openSketch = `sketch001 = startSketchOn('XZ') |> startProfileAt([0.02, 0.22], %) |> xLine(0.39, %) |> line([0.02, -0.17], %) |> yLine(-0.15, %) |> line([-0.21, -0.02], %) |> xLine(-0.15, %) |> line([-0.02, 0.21], %) |> line([-0.08, 0.05], %) |> lineTo([profileStartX(%), profileStartY(%)], %) ` const ast = assertParse(openSketch) const sketchSnippet = `startProfileAt([0.02, 0.22], %)` const sketchRange = topLevelRange( openSketch.indexOf(sketchSnippet), openSketch.indexOf(sketchSnippet) + sketchSnippet.length ) const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange) const modifiedAst = addCloseToPipe({ node: ast, variables: {}, pathToNode: sketchPathToNode, }) if (err(modifiedAst)) throw modifiedAst const recasted = recast(modifiedAst) const expectedCode = `sketch001 = startSketchOn('XZ') |> startProfileAt([0.02, 0.22], %) |> xLine(0.39, %) |> line([0.02, -0.17], %) |> yLine(-0.15, %) |> line([-0.21, -0.02], %) |> xLine(-0.15, %) |> line([-0.02, 0.21], %) |> line([-0.08, 0.05], %) |> lineTo([profileStartX(%), profileStartY(%)], %) |> close() ` expect(recasted).toEqual(expectedCode) }) })