From 573b748cb08c5eb91a4e652862ec69a5b5f1a2a5 Mon Sep 17 00:00:00 2001 From: dsorajisto <103614897+dsorajisto@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:40:32 -0400 Subject: [PATCH] test(language/lint): add unit tests for unused-variable pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 5 unit tests for the unused-variable lint pass in packages/language/src/lint/unused-variable.ts, which flags variables declared in the `variables:` block that are never referenced via `@variables.` expressions elsewhere. Coverage: - Declared-but-unreferenced variable → flagged - Variable referenced in an expression → not flagged - Mixed used/unused → only the unused ones flagged - One diagnostic per unused variable when multiple are unused - No `variables:` block → no diagnostics Follows the same test pattern as required-fields.test.ts (#38) and empty-block.test.ts (#48). All tests pass via: pnpm --filter @agentscript/language test unused-variable --- .../language/src/lint/unused-variable.test.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 packages/language/src/lint/unused-variable.test.ts diff --git a/packages/language/src/lint/unused-variable.test.ts b/packages/language/src/lint/unused-variable.test.ts new file mode 100644 index 0000000..2f53ced --- /dev/null +++ b/packages/language/src/lint/unused-variable.test.ts @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2026, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + * For full license text, see the LICENSE file in the repo root or https://www.apache.org/licenses/LICENSE-2.0 + */ + +import { describe, it, expect } from 'vitest'; +import { parse } from '@agentscript/parser'; +import { Dialect } from '../core/dialect.js'; +import { NamedBlock, NamedCollectionBlock } from '../core/block.js'; +import { ExpressionValue } from '../core/primitives.js'; +import { LintEngine } from '../core/analysis/lint-engine.js'; +import { createSchemaContext } from '../core/analysis/scope.js'; +import { VariablesBlock } from '../blocks.js'; +import { unusedVariablePass } from './unused-variable.js'; + +const ValueBlock = NamedBlock('ValueBlock', { + expr: ExpressionValue.describe('An expression that may reference variables'), +}); + +const TestSchema = { + variables: VariablesBlock, + value: NamedCollectionBlock(ValueBlock), +}; + +const schemaCtx = createSchemaContext({ schema: TestSchema, aliases: {} }); + +function getDiagnostics(source: string) { + const { rootNode: root } = parse(source); + const mappingNode = + root.namedChildren.find(n => n.type === 'mapping') ?? root; + + const dialect = new Dialect(); + const result = dialect.parse(mappingNode, TestSchema); + + const engine = new LintEngine({ + passes: [unusedVariablePass()], + source: 'test', + }); + const { diagnostics } = engine.run(result.value, schemaCtx); + return diagnostics.filter(d => d.code === 'unused-variable'); +} + +describe('unused-variable lint pass', () => { + it('flags a variable that is declared but never referenced', () => { + const diags = getDiagnostics(` +variables: + unused: mutable string = "" +`); + expect(diags).toHaveLength(1); + expect(diags[0].message).toContain('unused'); + }); + + it('does not flag a variable that is referenced in an expression', () => { + const diags = getDiagnostics(` +variables: + used: mutable string = "" + +value v: + expr: @variables.used +`); + expect(diags).toHaveLength(0); + }); + + it('flags only the unused variables when multiple are declared', () => { + const diags = getDiagnostics(` +variables: + used: mutable string = "" + unused: mutable string = "" + +value v: + expr: @variables.used +`); + expect(diags).toHaveLength(1); + expect(diags[0].message).toContain('unused'); + }); + + it('reports one diagnostic per unused variable', () => { + const diags = getDiagnostics(` +variables: + a: mutable string = "" + b: mutable string = "" + c: mutable string = "" +`); + expect(diags).toHaveLength(3); + }); + + it('does not flag variables when no `variables:` block is declared', () => { + const diags = getDiagnostics(` +value v: + expr: @variables.foo +`); + expect(diags).toHaveLength(0); + }); +});