Files
modeling-app/src/lang/queryAst.test.ts
Nick Cameron 322f398049 ProgramMemory refactor - eliminate copies of memory (#5273)
* refactor program memory for encapsulation and remove special treatment of return values

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactor ProgramMemory to isolate mutation

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactor ProgramMemory

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Generated output

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Cache the result of executing modules for their items

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Compress envs when popped

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Remove the last traces of geometry from the memory module

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* docs

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fixups

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Frontend

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Improve Environment GC

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fixup mock execution frontend and interpreter, misc bug fixes

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-11 21:22:56 +00:00

800 lines
25 KiB
TypeScript

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<Identifier>(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<Identifier>(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<Identifier>(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)
})
})