Compare commits

...

5 Commits

Author SHA1 Message Date
cfde4e99f9 add more tests 2024-05-23 11:17:04 +10:00
1bcaaec807 Merge remote-tracking branch 'origin' into max-unused-variables 2024-05-23 10:51:11 +10:00
67e90f580d nicer query
added PipeExpression checker.

now query works with :

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], %)
2024-05-22 22:45:22 +02:00
78046eceb6 simple test
const xRel001 = -20
const xRel002 = xRel001-50
2024-05-22 21:18:55 +02:00
502cb08a10 findUnusedVariables
first draft
2024-05-21 22:40:30 +02:00
2 changed files with 148 additions and 0 deletions

View File

@ -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], %)

View File

@ -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(