Compare commits
5 Commits
kcl-0.2.26
...
max-unused
Author | SHA1 | Date | |
---|---|---|---|
cfde4e99f9 | |||
1bcaaec807 | |||
67e90f580d | |||
78046eceb6 | |||
502cb08a10 |
@ -1,6 +1,7 @@
|
|||||||
import { parse, recast, initPromise } from './wasm'
|
import { parse, recast, initPromise } from './wasm'
|
||||||
import {
|
import {
|
||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
|
findUnusedVariables,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
isTypeInValue,
|
isTypeInValue,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
@ -60,6 +61,91 @@ const variableBelowShouldNotBeIncluded = 3
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Test findUnusedVariables', () => {
|
||||||
|
it('should find unused variable in common kcl code', () => {
|
||||||
|
// example code
|
||||||
|
const code = `
|
||||||
|
const xRel001 = -20
|
||||||
|
const xRel002 = -50
|
||||||
|
const part001 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([175.73, 109.38], %)
|
||||||
|
|> line([xRel001, 178.25], %)
|
||||||
|
|> line([-265.39, -87.86], %)
|
||||||
|
|> tangentialArcTo([543.32, -355.04], %)
|
||||||
|
`
|
||||||
|
// parse into ast
|
||||||
|
const ast = parse(code)
|
||||||
|
// find unused variables
|
||||||
|
const unusedVariables = findUnusedVariables(ast)
|
||||||
|
// check wether unused variables match the expected result
|
||||||
|
expect(
|
||||||
|
unusedVariables
|
||||||
|
.map((node) => node.declarations.map((decl) => decl.id.name))
|
||||||
|
.flat()
|
||||||
|
).toEqual(['xRel002'])
|
||||||
|
})
|
||||||
|
it("should not find used variable, even if it's heavy nested", () => {
|
||||||
|
// example code
|
||||||
|
const code = `
|
||||||
|
const deepWithin = 1
|
||||||
|
const veryNested = [
|
||||||
|
{ key: [{ key2: max(5, deepWithin) }] }
|
||||||
|
]
|
||||||
|
`
|
||||||
|
// parse into ast
|
||||||
|
const ast = parse(code)
|
||||||
|
// find unused variables
|
||||||
|
const unusedVariables = findUnusedVariables(ast)
|
||||||
|
// check wether unused variables match the expected result
|
||||||
|
expect(
|
||||||
|
unusedVariables.find((node) =>
|
||||||
|
node.declarations.find((decl) => decl.id.name === 'deepWithin')
|
||||||
|
)
|
||||||
|
).toBeFalsy()
|
||||||
|
})
|
||||||
|
it('should not find used variable, even if used in a closure', () => {
|
||||||
|
// example code
|
||||||
|
const code = `const usedInClosure = 1
|
||||||
|
fn myFunction = () => {
|
||||||
|
return usedInClosure
|
||||||
|
}
|
||||||
|
`
|
||||||
|
// parse into ast
|
||||||
|
const ast = parse(code)
|
||||||
|
// find unused variables
|
||||||
|
const unusedVariables = findUnusedVariables(ast)
|
||||||
|
// check wether unused variables match the expected result
|
||||||
|
expect(
|
||||||
|
unusedVariables.find((node) =>
|
||||||
|
node.declarations.find((decl) => decl.id.name === 'usedInClosure')
|
||||||
|
)
|
||||||
|
).toBeFalsy()
|
||||||
|
})
|
||||||
|
// TODO: The commented code in the below does not even parse due to a KCL bug
|
||||||
|
// When it does parse correctly we'de expect 'a' to be defined but unused
|
||||||
|
// as it's shadowed by the 'a' in the inner scope
|
||||||
|
// it('should find unused variable when the same identifier is used in deeper scope', () => {
|
||||||
|
// const code = `const a = 1
|
||||||
|
// const b = 2
|
||||||
|
// fn (a) => {
|
||||||
|
// return a + 1
|
||||||
|
// }
|
||||||
|
// const myVar = b + 5`
|
||||||
|
// // parse into ast
|
||||||
|
// const ast = parse(code)
|
||||||
|
// console.log('ast', ast)
|
||||||
|
// // find unused variables
|
||||||
|
// const unusedVariables = findUnusedVariables(ast)
|
||||||
|
// console.log('unusedVariables', unusedVariables)
|
||||||
|
// // check wether unused variables match the expected result
|
||||||
|
// expect(
|
||||||
|
// unusedVariables
|
||||||
|
// .map((node) => node.declarations.map((decl) => decl.id.name))
|
||||||
|
// .flat()
|
||||||
|
// ).toEqual(['a'])
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
|
||||||
describe('testing argIsNotIdentifier', () => {
|
describe('testing argIsNotIdentifier', () => {
|
||||||
const code = `const part001 = startSketchOn('XY')
|
const code = `const part001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-1.2, 4.83], %)
|
|> startProfileAt([-1.2, 4.83], %)
|
||||||
|
@ -392,6 +392,68 @@ export function findAllPreviousVariables(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findUnusedVariables(ast: Program): Array<VariableDeclaration> {
|
||||||
|
const declaredVariables = new Map<string, VariableDeclarator>() // Map to store declared variables
|
||||||
|
const usedVariables = new Set<string>() // Set to track used variables
|
||||||
|
|
||||||
|
// 1. Traverse and populate
|
||||||
|
ast.body.forEach((node) => {
|
||||||
|
traverse(node, {
|
||||||
|
enter(node) {
|
||||||
|
if (node.type === 'VariableDeclarator') {
|
||||||
|
// if node is a VariableDeclarator,
|
||||||
|
// add it to declaredVariables
|
||||||
|
declaredVariables.set(node.id.name, node)
|
||||||
|
} else if (node.type === 'Identifier') {
|
||||||
|
// if the node is Identifier, (use of a variable)
|
||||||
|
// check if it is a declared value,
|
||||||
|
// just in case...
|
||||||
|
// to be sure it's a part of the declared variables
|
||||||
|
if (declaredVariables.has(node.name)) {
|
||||||
|
// if yes - mark it as used
|
||||||
|
usedVariables.add(node.name)
|
||||||
|
}
|
||||||
|
} else if (node.type === 'VariableDeclaration') {
|
||||||
|
// check if the declaration is model-defining (contains PipeExpression)
|
||||||
|
const isModelDefining = node.declarations.some(
|
||||||
|
(decl) => decl.init?.type === 'PipeExpression'
|
||||||
|
)
|
||||||
|
if (isModelDefining) {
|
||||||
|
// If it is, mark all contained variables as used
|
||||||
|
node.declarations.forEach((decl) => {
|
||||||
|
usedVariables.add(decl.id.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. Remove used variables from declaredVariables
|
||||||
|
usedVariables.forEach((name) => {
|
||||||
|
declaredVariables.delete(name)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. collect unused VariableDeclarations
|
||||||
|
const unusedVariableDeclarations: Array<VariableDeclaration> = []
|
||||||
|
ast.body.forEach((node) => {
|
||||||
|
if (node.type === 'VariableDeclaration') {
|
||||||
|
const unusedDeclarators = node.declarations.filter((declarator) =>
|
||||||
|
declaredVariables.has(declarator.id.name)
|
||||||
|
)
|
||||||
|
if (unusedDeclarators.length > 0) {
|
||||||
|
unusedVariableDeclarations.push({
|
||||||
|
...node,
|
||||||
|
declarations: unusedDeclarators,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. Return the unused variables
|
||||||
|
return unusedVariableDeclarations
|
||||||
|
}
|
||||||
|
|
||||||
type ReplacerFn = (_ast: Program, varName: string) => { modifiedAst: Program }
|
type ReplacerFn = (_ast: Program, varName: string) => { modifiedAst: Program }
|
||||||
|
|
||||||
export function isNodeSafeToReplace(
|
export function isNodeSafeToReplace(
|
||||||
|
Reference in New Issue
Block a user