From 23dfc297a22a2a32d34cdc6f05d9808725e60623 Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Tue, 16 Dec 2025 18:47:38 -0500 Subject: [PATCH 01/13] Added new properties to Test proto --- core/actions/index.ts | 7 +- core/actions/table.ts | 10 ++ core/actions/test.ts | 58 ++++++++- core/actions/test_test.ts | 263 +++++++++++++++++++++++++++++++++++++- core/actions/view.ts | 10 ++ core/session.ts | 37 ++++-- protos/core.proto | 7 + 7 files changed, 370 insertions(+), 22 deletions(-) diff --git a/core/actions/index.ts b/core/actions/index.ts index ac69ae87b..63f214ca3 100644 --- a/core/actions/index.ts +++ b/core/actions/index.ts @@ -5,6 +5,7 @@ import { IncrementalTable } from "df/core/actions/incremental_table"; import { Notebook } from "df/core/actions/notebook"; import { Operation } from "df/core/actions/operation"; import { Table } from "df/core/actions/table"; +import { Test } from "df/core/actions/test"; import { View } from "df/core/actions/view"; import { IColumnsDescriptor } from "df/core/column_descriptors"; import { Resolvable } from "df/core/contextables"; @@ -19,7 +20,8 @@ export type Action = | Assertion | Declaration | Notebook - | DataPreparation; + | DataPreparation + | Test; export type ActionProto = | dataform.Table // core.proto's Table represents the Table, View or IncrementalTable action type. @@ -27,7 +29,8 @@ export type ActionProto = | dataform.Assertion | dataform.Declaration | dataform.Notebook - | dataform.DataPreparation; + | dataform.DataPreparation + | dataform.Test; // In v4, consider making methods on inheritors of this private, forcing users to use constructors // in order to populate actions. diff --git a/core/actions/table.ts b/core/actions/table.ts index 47bdf594a..8fd1ff518 100644 --- a/core/actions/table.ts +++ b/core/actions/table.ts @@ -483,6 +483,16 @@ export class Table extends ActionBuilder { return dataform.Target.create(this.proto.target); } + /** @hidden */ + public getCanonicalTarget() { + return dataform.Target.create(this.proto.canonicalTarget); + } + + /** @hidden */ + public getDependencyTargets(): dataform.Target[] { + return this.proto.dependencyTargets.map(target => dataform.Target.create(target)); + } + /** @hidden */ public compile() { const context = new TableContext(this); diff --git a/core/actions/test.ts b/core/actions/test.ts index b6d69d204..b11e00510 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -77,8 +77,10 @@ export class Test extends ActionBuilder { /** @hidden We delay contextification until the final compile step, so hold these here for now. */ public contextableInputs = new Map>(); - private contextableQuery: Contextable; + private inputToTargets = new Map(); + private contextableQuery: Contextable; private datasetToTest: Resolvable; + private testTarget: dataform.ITarget; /** * @hidden Stores the generated proto for the compiled graph. @@ -93,6 +95,13 @@ export class Test extends ActionBuilder { if (config) { this.config(config); } + + // Initialize target, ensuring we can compile the dag and add this test as a dependency to the tested action. + this.proto.target = this.applySessionToTarget( + dataform.Target.create({ name: config.name }), + session.projectConfig, + config.filename + ); } /** @hidden */ @@ -127,10 +136,13 @@ export class Test extends ActionBuilder { * Sets the input query to unit test against. */ public input(refName: string | string[], contextableQuery: Contextable) { + const target = resolvableAsTarget(toResolvable(refName)); + const inputName = targetStringifier.stringify(target); this.contextableInputs.set( - targetStringifier.stringify(resolvableAsTarget(toResolvable(refName))), + inputName, contextableQuery ); + this.inputToTargets.set(inputName, target); return this; } @@ -143,17 +155,19 @@ export class Test extends ActionBuilder { } /** @hidden */ - public getFileName() { + public getFileName(): string { return this.proto.fileName; } /** @hidden */ - public getTarget(): undefined { - // The test action type has no target because it is not processed during regular execution. - return undefined; + public getTarget(): dataform.Target { + return dataform.Target.create(this.proto.target); + } + + public getTestTarget(): dataform.Target { + return dataform.Target.create(this.testTarget); } - /** @hidden */ public setFilename(filename: string) { this.proto.fileName = filename; } @@ -188,6 +202,28 @@ export class Test extends ActionBuilder { } else { const refReplacingContext = new RefReplacingContext(testContext); this.proto.testQuery = refReplacingContext.apply(dataset.contextableQuery); + this.testTarget = dataform.Target.create(dataset.getTarget()); + + // Set the test query with the fully qualified table references. + if (dataset instanceof Table) { + this.proto.target = this.replaceTestNameInTarget( + dataset.getTarget(), + this.proto.name + ); + this.proto.canonicalTarget = this.replaceTestNameInTarget( + dataset.getCanonicalTarget(), + this.proto.name + ); + } else { + this.proto.target = this.replaceTestNameInTarget( + dataset.getTarget(), + this.proto.name + ); + this.proto.canonicalTarget = this.replaceTestNameInTarget( + dataset.getCanonicalTarget(), + this.proto.name + ); + } } } this.proto.expectedOutputQuery = testContext.apply(this.contextableQuery); @@ -198,6 +234,14 @@ export class Test extends ActionBuilder { VerifyProtoErrorBehaviour.SUGGEST_REPORTING_TO_DATAFORM_TEAM ); } + + private replaceTestNameInTarget(target: dataform.Target, testName: string): dataform.Target { + return dataform.Target.create({ + database: target.database, + schema: target.schema, + name: testName + }); + } } /** @hidden */ diff --git a/core/actions/test_test.ts b/core/actions/test_test.ts index 9ddccf712..e45d6a19b 100644 --- a/core/actions/test_test.ts +++ b/core/actions/test_test.ts @@ -1,5 +1,262 @@ -import { suite } from "df/testing"; +// tslint:disable tsr-detect-non-literal-fs-filename +import { expect } from "chai"; +import * as fs from "fs-extra"; +import * as path from "path"; -suite("test", () => { - // This action currently has no unit tests! +import { asPlainObject, suite, test } from "df/testing"; +import { TmpDirFixture } from "df/testing/fixtures"; +import { + coreExecutionRequestFromPath, + runMainInVm, + VALID_WORKFLOW_SETTINGS_YAML +} from "df/testing/run_core"; + +suite("test", ({ afterEach }) => { + const tmpDirFixture = new TmpDirFixture(afterEach); + + test(`test with no inputs`, () => { + const projectDir = tmpDirFixture.createNewTmpDir(); + const workflowSettingsPath = path.join(projectDir, "workflow_settings.yaml"); + const definitionsDir = path.join(projectDir, "definitions"); + const actionsYamlPath = path.join(definitionsDir, "actions.yaml"); + const actionSqlPath = path.join(definitionsDir, "action.sql"); + const actionTestSqlxPath = path.join(definitionsDir, "action_test.sqlx"); + + fs.writeFileSync(workflowSettingsPath, VALID_WORKFLOW_SETTINGS_YAML); + fs.mkdirSync(definitionsDir); + fs.writeFileSync(actionsYamlPath, ` +actions: +- table: + filename: action.sql` + ); + fs.writeFileSync(actionSqlPath, "SELECT 1"); + fs.writeFileSync(actionTestSqlxPath, ` +config { + type: "test", + dataset: "action" +} +SELECT 1`); + + const result = runMainInVm(coreExecutionRequestFromPath(projectDir)); + + expect(result.compile.compiledGraph.graphErrors.compilationErrors).deep.equals([]); + expect(asPlainObject(result.compile.compiledGraph.tests)).deep.equals( + asPlainObject([ + { + // Original test properties + name: "action_test", + testQuery: "SELECT 1", + expectedOutputQuery: "\n\nSELECT 1", + fileName: "definitions/action_test.sqlx", + + // New properties + target: { + database: "defaultProject", + schema: "defaultDataset", + name: "action_test" + }, + canonicalTarget: { + database: "defaultProject", + schema: "defaultDataset", + name: "action_test" + }, + } + ]) + ); + expect(asPlainObject(result.compile.compiledGraph.tables)).deep.equals( + asPlainObject([ + { + "target": { + "database": "defaultProject", + "name": "action", + "schema": "defaultDataset" + }, + "canonicalTarget": { + "database": "defaultProject", + "name": "action", + "schema": "defaultDataset" + }, + "dependencyTargets": [ + { + "database": "defaultProject", + "name": "action_test", + "schema": "defaultDataset" + } + ], + "disabled": false, + "enumType": "TABLE", + "fileName": "definitions/action.sql", + "hermeticity": "NON_HERMETIC", + "query": "SELECT 1", + "type": "table" + } + ])); + }); + + test(`test with multiple_inputs input`, () => { + const projectDir = tmpDirFixture.createNewTmpDir(); + const workflowSettingsPath = path.join(projectDir, "workflow_settings.yaml"); + const definitionsDir = path.join(projectDir, "definitions"); + const actionsYamlPath = path.join(definitionsDir, "actions.yaml"); + const action1SqlxPath = path.join(definitionsDir, "action1.sqlx"); + const action1TestSqlxPath = path.join(definitionsDir, "action1_test.sqlx"); + const action2SqlxPath = path.join(definitionsDir, "action2.sqlx"); + const action2TestSqlxPath = path.join(definitionsDir, "action2_test.sqlx"); + + fs.writeFileSync(workflowSettingsPath, VALID_WORKFLOW_SETTINGS_YAML); + fs.mkdirSync(definitionsDir); + + // Add a declaration + fs.writeFileSync(actionsYamlPath, ` +actions: +- declaration: + name: a_declaration` + ); + + // Add an action with a test, reads from declaration + fs.writeFileSync(action1SqlxPath, ` +config { + type: "table", +} +SELECT a,b,c FROM \${ref("a_declaration")} + `); + fs.writeFileSync(action1TestSqlxPath, ` +config { + type: "test", + dataset: "action1" +} +input "a_declaration" { + SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d +} +SELECT 1 AS a, 2 AS b, 3 AS c`); + + + // Add an action with a test, reads from previous action + fs.writeFileSync(action2SqlxPath, ` +config { + type: "table", +} +SELECT a,b FROM \${ref("action1")} + `); + fs.writeFileSync(action2TestSqlxPath, ` +config { + type: "test", + dataset: "action2" +} +input "action1" { + SELECT 1 AS a, 2 AS b, 3 AS c +} +SELECT 1 AS a, 2 AS b`); + + const result = runMainInVm(coreExecutionRequestFromPath(projectDir)); + + expect(result.compile.compiledGraph.graphErrors.compilationErrors).deep.equals([]); + expect(asPlainObject(result.compile.compiledGraph.tests)).deep.equals( + asPlainObject([ + { + // Original test properties + name: "action1_test", + testQuery: "\n\nSELECT a,b,c FROM (\n SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d\n)\n ", + expectedOutputQuery: "\n\n\nSELECT 1 AS a, 2 AS b, 3 AS c", + fileName: "definitions/action1_test.sqlx", + + // New properties + target: { + database: "defaultProject", + schema: "defaultDataset", + name: "action1_test" + }, + canonicalTarget: { + database: "defaultProject", + schema: "defaultDataset", + name: "action1_test" + }, + }, + { + // Original test properties + name: "action2_test", + testQuery: "\n\nSELECT a,b FROM (\n SELECT 1 AS a, 2 AS b, 3 AS c\n)\n ", + expectedOutputQuery: "\n\n\nSELECT 1 AS a, 2 AS b", + fileName: "definitions/action2_test.sqlx", + + // New properties + target: { + database: "defaultProject", + schema: "defaultDataset", + name: "action2_test" + }, + canonicalTarget: { + database: "defaultProject", + schema: "defaultDataset", + name: "action2_test" + }, + } + ]) + ); + expect(asPlainObject(result.compile.compiledGraph.tables)).deep.equals( + asPlainObject([ + { + "target": { + "database": "defaultProject", + "name": "action1", + "schema": "defaultDataset" + }, + "canonicalTarget": { + "database": "defaultProject", + "name": "action1", + "schema": "defaultDataset" + }, + "dependencyTargets": [ + { + "database": "defaultProject", + "name": "a_declaration", + "schema": "defaultDataset" + }, + { + "database": "defaultProject", + "name": "action1_test", + "schema": "defaultDataset" + } + ], + "disabled": false, + "enumType": "TABLE", + "fileName": "definitions/action1.sqlx", + "hermeticity": "NON_HERMETIC", + "query": "\n\nSELECT a,b,c FROM `defaultProject.defaultDataset.a_declaration`\n ", + "type": "table" + }, + { + "target": { + "database": "defaultProject", + "name": "action2", + "schema": "defaultDataset" + }, + "canonicalTarget": { + "database": "defaultProject", + "name": "action2", + "schema": "defaultDataset" + }, + "dependencyTargets": [ + { + "database": "defaultProject", + "name": "action1", + "schema": "defaultDataset" + }, + { + "database": "defaultProject", + "name": "action2_test", + "schema": "defaultDataset" + } + ], + "disabled": false, + "enumType": "TABLE", + "fileName": "definitions/action2.sqlx", + "hermeticity": "NON_HERMETIC", + "query": "\n\nSELECT a,b FROM `defaultProject.defaultDataset.action1`\n ", + "type": "table" + } + ]) + ); + }); }); + diff --git a/core/actions/view.ts b/core/actions/view.ts index 92a551f94..1fbf82cdd 100644 --- a/core/actions/view.ts +++ b/core/actions/view.ts @@ -492,6 +492,16 @@ export class View extends ActionBuilder { return dataform.Target.create(this.proto.target); } + /** @hidden */ + public getCanonicalTarget() { + return dataform.Target.create(this.proto.canonicalTarget); + } + + /** @hidden */ + public getDependencyTargets(): dataform.Target[] { + return this.proto.dependencyTargets.map(target => dataform.Target.create(target)); + } + /** @hidden */ public compile() { const context = new ViewContext(this); diff --git a/core/session.ts b/core/session.ts index 8052de79f..89077e5d6 100644 --- a/core/session.ts +++ b/core/session.ts @@ -49,8 +49,8 @@ export class Session { public canonicalProjectConfig: dataform.ProjectConfig; public actions: Action[]; + public tests: Test[]; public indexedActions: ActionMap; - public tests: { [name: string]: Test }; // This map holds information about what assertions are dependent // upon a certain action in our actions list. We use this later to resolve dependencies. @@ -77,7 +77,7 @@ export class Session { dataform.ProjectConfig.create(originalProjectConfig || projectConfig || DEFAULT_CONFIG) ); this.actions = []; - this.tests = {}; + this.tests = []; this.graphErrors = { compilationErrors: [] }; } @@ -390,7 +390,8 @@ export class Session { newTest.session = this; newTest.setFilename(utils.getCallerFile(this.rootDir)); // Add it to global index. - this.tests[name] = newTest; + this.actions.push(newTest) + this.tests.push(newTest); return newTest; } @@ -429,7 +430,6 @@ export class Session { this.indexedActions = new ActionMap(this.actions); // defaultLocation is no longer a required parameter to support location auto-selection. - if ( !!this.projectConfig.vars && !Object.values(this.projectConfig.vars).every(value => typeof value === "string") @@ -454,7 +454,9 @@ export class Session { declarations: this.compileGraphChunk( this.actions.filter(action => action instanceof Declaration) ), - tests: this.compileGraphChunk(Object.values(this.tests)), + tests: this.compileGraphChunk( + this.actions.filter(action => action instanceof Test) + ), notebooks: this.compileGraphChunk(this.actions.filter(action => action instanceof Notebook)), dataPreparations: this.compileGraphChunk( this.actions.filter(action => action instanceof DataPreparation) @@ -464,13 +466,26 @@ export class Session { targets: this.actions.map(action => action.getTarget()) }); + // Add unit tests as dependencies to the parent actions + this.actions + .filter(action => action instanceof Test) + .map(test => test as Test) + .forEach(test => { + this.indexedActions + .find(test.getTestTarget()) + .filter(action => action instanceof Table || action instanceof View) + .map(action => action as Table | View) + .forEach(tableOrViewAction => tableOrViewAction.dependencies(utils.resolvableAsTarget(test.getTarget()))); + }); + this.fullyQualifyDependencies( [].concat( compiledGraph.tables, compiledGraph.assertions, compiledGraph.operations, compiledGraph.notebooks, - compiledGraph.dataPreparations + compiledGraph.dataPreparations, + compiledGraph.tests ) ); @@ -480,7 +495,8 @@ export class Session { compiledGraph.assertions, compiledGraph.operations, compiledGraph.notebooks, - compiledGraph.dataPreparations + compiledGraph.dataPreparations, + compiledGraph.tests ), [].concat(compiledGraph.declarations.map(declaration => declaration.target)) ); @@ -495,7 +511,8 @@ export class Session { compiledGraph.assertions, compiledGraph.operations, compiledGraph.notebooks, - compiledGraph.dataPreparations + compiledGraph.dataPreparations, + compiledGraph.tests ) ); verifyObjectMatchesProto( @@ -534,7 +551,7 @@ export class Session { return !!this.projectConfig.tablePrefix ? `${this.projectConfig.tablePrefix}_` : ""; } - private compileGraphChunk(actions: Array): T[] { + private compileGraphChunk(actions: Action[]): T[] { const compiledChunks: T[] = []; actions.forEach(action => { @@ -553,7 +570,7 @@ export class Session { actions.forEach(action => { const fullyQualifiedDependencies: { [name: string]: dataform.ITarget } = {}; if (action instanceof dataform.Declaration || !action.dependencyTargets) { - // Declarations cannot have dependencies. + // Declarations cannot have dependencies. return; } for (const dependency of action.dependencyTargets) { diff --git a/protos/core.proto b/protos/core.proto index ae9673205..fa5ff46d2 100644 --- a/protos/core.proto +++ b/protos/core.proto @@ -264,6 +264,13 @@ message Test { string test_query = 2; string expected_output_query = 3; + repeated string tags = 5; + + Target target = 6; + Target canonical_target = 7; + + repeated Target dependency_targets = 9; + // Generated. string file_name = 4; } From db9726bc6218be2c02bfdaa9020346db2f43faf9 Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Fri, 9 Jan 2026 11:23:39 -0500 Subject: [PATCH 02/13] Simplified code --- core/actions/test.ts | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/core/actions/test.ts b/core/actions/test.ts index b11e00510..ed8206387 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -202,28 +202,19 @@ export class Test extends ActionBuilder { } else { const refReplacingContext = new RefReplacingContext(testContext); this.proto.testQuery = refReplacingContext.apply(dataset.contextableQuery); - this.testTarget = dataform.Target.create(dataset.getTarget()); + + const testTarget = dataset as Table | View; + this.testTarget = dataform.Target.create(testTarget.getTarget()); // Set the test query with the fully qualified table references. - if (dataset instanceof Table) { - this.proto.target = this.replaceTestNameInTarget( - dataset.getTarget(), - this.proto.name - ); - this.proto.canonicalTarget = this.replaceTestNameInTarget( - dataset.getCanonicalTarget(), - this.proto.name - ); - } else { - this.proto.target = this.replaceTestNameInTarget( - dataset.getTarget(), - this.proto.name - ); - this.proto.canonicalTarget = this.replaceTestNameInTarget( - dataset.getCanonicalTarget(), - this.proto.name - ); - } + this.proto.target = this.replaceTestNameInTarget( + testTarget.getTarget(), + this.proto.name + ); + this.proto.canonicalTarget = this.replaceTestNameInTarget( + testTarget.getCanonicalTarget(), + this.proto.name + ); } } this.proto.expectedOutputQuery = testContext.apply(this.contextableQuery); From 8e12d84f19838660ec6a5df093c6c940cede61ee Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Fri, 9 Jan 2026 13:30:57 -0500 Subject: [PATCH 03/13] Simplified code for readability --- core/session.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/core/session.ts b/core/session.ts index 89077e5d6..aff0d2f92 100644 --- a/core/session.ts +++ b/core/session.ts @@ -467,16 +467,7 @@ export class Session { }); // Add unit tests as dependencies to the parent actions - this.actions - .filter(action => action instanceof Test) - .map(test => test as Test) - .forEach(test => { - this.indexedActions - .find(test.getTestTarget()) - .filter(action => action instanceof Table || action instanceof View) - .map(action => action as Table | View) - .forEach(tableOrViewAction => tableOrViewAction.dependencies(utils.resolvableAsTarget(test.getTarget()))); - }); + this.addTestsAsDependenciesToTestedActions(this.actions); this.fullyQualifyDependencies( [].concat( @@ -566,6 +557,19 @@ export class Session { return compiledChunks; } + private addTestsAsDependenciesToTestedActions(actions: Action[]) { + actions + .filter(action => action instanceof Test) + .map(test => test as Test) + .forEach(test => { + this.indexedActions + .find(test.getTestTarget()) + .filter(action => action instanceof Table || action instanceof View) + .map(action => action as Table | View) + .forEach(tableOrViewAction => tableOrViewAction.dependencies(utils.resolvableAsTarget(test.getTarget()))); + }); + } + private fullyQualifyDependencies(actions: ActionProto[]) { actions.forEach(action => { const fullyQualifiedDependencies: { [name: string]: dataform.ITarget } = {}; From 452c9622a1838b8420ba5a354f87c8e3c918ed79 Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Fri, 9 Jan 2026 13:34:46 -0500 Subject: [PATCH 04/13] More cleanup --- core/session.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/session.ts b/core/session.ts index aff0d2f92..16cc333cd 100644 --- a/core/session.ts +++ b/core/session.ts @@ -49,7 +49,6 @@ export class Session { public canonicalProjectConfig: dataform.ProjectConfig; public actions: Action[]; - public tests: Test[]; public indexedActions: ActionMap; // This map holds information about what assertions are dependent @@ -77,7 +76,6 @@ export class Session { dataform.ProjectConfig.create(originalProjectConfig || projectConfig || DEFAULT_CONFIG) ); this.actions = []; - this.tests = []; this.graphErrors = { compilationErrors: [] }; } @@ -391,7 +389,6 @@ export class Session { newTest.setFilename(utils.getCallerFile(this.rootDir)); // Add it to global index. this.actions.push(newTest) - this.tests.push(newTest); return newTest; } @@ -466,7 +463,6 @@ export class Session { targets: this.actions.map(action => action.getTarget()) }); - // Add unit tests as dependencies to the parent actions this.addTestsAsDependenciesToTestedActions(this.actions); this.fullyQualifyDependencies( From 72903beb3c0beeba8716b0b1a30df92716d65041 Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Fri, 9 Jan 2026 17:19:16 -0500 Subject: [PATCH 05/13] renamed function --- core/actions/test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/actions/test.ts b/core/actions/test.ts index ed8206387..53ff50b47 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -206,12 +206,12 @@ export class Test extends ActionBuilder { const testTarget = dataset as Table | View; this.testTarget = dataform.Target.create(testTarget.getTarget()); - // Set the test query with the fully qualified table references. - this.proto.target = this.replaceTestNameInTarget( + // Set the target and canonical target. This inherits the database and schema from the tested action, but uses the test's name. + this.proto.target = this.overrideTargetWithNewName( testTarget.getTarget(), this.proto.name ); - this.proto.canonicalTarget = this.replaceTestNameInTarget( + this.proto.canonicalTarget = this.overrideTargetWithNewName( testTarget.getCanonicalTarget(), this.proto.name ); @@ -226,7 +226,7 @@ export class Test extends ActionBuilder { ); } - private replaceTestNameInTarget(target: dataform.Target, testName: string): dataform.Target { + private overrideTargetWithNewName(target: dataform.Target, testName: string): dataform.Target { return dataform.Target.create({ database: target.database, schema: target.schema, From 23d988b74051f593a51f96bf5022a52d29e3beb7 Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Mon, 12 Jan 2026 15:22:35 -0500 Subject: [PATCH 06/13] Fixed issue with schema/database overrides and tests --- core/actions/test.ts | 44 ++++------- core/actions/test_test.ts | 146 +++++++++++++++++++++++++++++++++++++ core/session.ts | 7 +- tests/api/projects.spec.ts | 10 +++ 4 files changed, 178 insertions(+), 29 deletions(-) diff --git a/core/actions/test.ts b/core/actions/test.ts index 53ff50b47..6e9f57274 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -79,8 +79,8 @@ export class Test extends ActionBuilder { public contextableInputs = new Map>(); private inputToTargets = new Map(); private contextableQuery: Contextable; - private datasetToTest: Resolvable; private testTarget: dataform.ITarget; + private canonicalTestTarget: dataform.ITarget; /** * @hidden Stores the generated proto for the compiled graph. @@ -95,13 +95,6 @@ export class Test extends ActionBuilder { if (config) { this.config(config); } - - // Initialize target, ensuring we can compile the dag and add this test as a dependency to the tested action. - this.proto.target = this.applySessionToTarget( - dataform.Target.create({ name: config.name }), - session.projectConfig, - config.filename - ); } /** @hidden */ @@ -116,7 +109,15 @@ export class Test extends ActionBuilder { this.proto.name = config.name; } if (config.dataset) { - this.dataset(config.dataset); + // Determine target from the parent dataset name + const datasetToTest = this.applySessionToTarget(resolvableAsTarget(toResolvable(config.dataset)), this.session.projectConfig); + this.testTarget = dataform.Target.create(datasetToTest); + const canonicalDatasetToTest = this.applySessionToTarget(resolvableAsTarget(toResolvable(config.dataset)), this.session.canonicalProjectConfig); + this.canonicalTestTarget = dataform.Target.create(canonicalDatasetToTest); + + // Set the target as the test name, with the tested action database and schema. + this.proto.target = this.overrideTargetWithNewName(this.testTarget, config.name || "unnamed_test") + this.proto.canonicalTarget = this.overrideTargetWithNewName(this.canonicalTestTarget, config.name || "unnamed_test") } if (config.filename) { this.proto.fileName = config.filename; @@ -128,7 +129,7 @@ export class Test extends ActionBuilder { * Sets the schema (BigQuery dataset) in which to create the output of this action. */ public dataset(ref: Resolvable) { - this.datasetToTest = ref; + this.config({ dataset: ref }); return this; } @@ -175,23 +176,23 @@ export class Test extends ActionBuilder { /** @hidden */ public compile() { const testContext = new TestContext(this); - if (!this.datasetToTest) { + if (!this.testTarget) { this.session.compileError( new Error("Tests must operate upon a specified dataset."), this.proto.fileName ); } else { - const allResolved = this.session.indexedActions.find(resolvableAsTarget(this.datasetToTest)); + const allResolved = this.session.indexedActions.find(this.testTarget); if (allResolved.length > 1) { this.session.compileError( - new Error(ambiguousActionNameMsg(this.datasetToTest, allResolved)), + new Error(ambiguousActionNameMsg(this.testTarget, allResolved)), this.proto.fileName ); } const dataset = allResolved.length > 0 ? allResolved[0] : undefined; if (!(dataset && (dataset instanceof Table || dataset instanceof View))) { this.session.compileError( - new Error(`Dataset ${stringifyResolvable(this.datasetToTest)} could not be found.`), + new Error(`Dataset ${stringifyResolvable(this.testTarget)} could not be found.`), this.proto.fileName ); } else if (dataset instanceof IncrementalTable) { @@ -202,19 +203,6 @@ export class Test extends ActionBuilder { } else { const refReplacingContext = new RefReplacingContext(testContext); this.proto.testQuery = refReplacingContext.apply(dataset.contextableQuery); - - const testTarget = dataset as Table | View; - this.testTarget = dataform.Target.create(testTarget.getTarget()); - - // Set the target and canonical target. This inherits the database and schema from the tested action, but uses the test's name. - this.proto.target = this.overrideTargetWithNewName( - testTarget.getTarget(), - this.proto.name - ); - this.proto.canonicalTarget = this.overrideTargetWithNewName( - testTarget.getCanonicalTarget(), - this.proto.name - ); } } this.proto.expectedOutputQuery = testContext.apply(this.contextableQuery); @@ -226,7 +214,7 @@ export class Test extends ActionBuilder { ); } - private overrideTargetWithNewName(target: dataform.Target, testName: string): dataform.Target { + private overrideTargetWithNewName(target: dataform.ITarget, testName: string): dataform.Target { return dataform.Target.create({ database: target.database, schema: target.schema, diff --git a/core/actions/test_test.ts b/core/actions/test_test.ts index e45d6a19b..5b00078ba 100644 --- a/core/actions/test_test.ts +++ b/core/actions/test_test.ts @@ -258,5 +258,151 @@ SELECT 1 AS a, 2 AS b`); ]) ); }); + + test(`test with two actions with same name and different schema`, () => { + const projectDir = tmpDirFixture.createNewTmpDir(); + const workflowSettingsPath = path.join(projectDir, "workflow_settings.yaml"); + const definitionsDir = path.join(projectDir, "definitions"); + const schema1actionSqlxPath = path.join(definitionsDir, "schema1_action.sqlx"); + const schema2actionSqlxPath = path.join(definitionsDir, "schema2_action.sqlx"); + const schema1actionTestSqlxPath = path.join(definitionsDir, "schema1_action_test.sqlx"); + const schema2actionTestSqlxPath = path.join(definitionsDir, "schema2_action_test.sqlx"); + + fs.writeFileSync(workflowSettingsPath, VALID_WORKFLOW_SETTINGS_YAML); + fs.mkdirSync(definitionsDir); + fs.writeFileSync(schema1actionSqlxPath, ` +config { + schema: "schema1", + name: "action", + type: "table", +} +SELECT 1 + `); + fs.writeFileSync(schema2actionSqlxPath, ` +config { + schema: "schema2", + name: "action", + type: "table", +} +SELECT 2 + `); + fs.writeFileSync(schema1actionTestSqlxPath, ` +config { + type: "test", + dataset: { + schema: "schema1", + name: "action", + }, +} +SELECT 1`); + fs.writeFileSync(schema2actionTestSqlxPath, ` +config { + type: "test", + dataset: { + schema: "schema2", + name: "action", + }, +} +SELECT 2`); + + const result = runMainInVm(coreExecutionRequestFromPath(projectDir)); + + expect(result.compile.compiledGraph.graphErrors.compilationErrors).deep.equals([]); + expect(asPlainObject(result.compile.compiledGraph.tests)).deep.equals( + asPlainObject([ + { + // Original test properties + name: "schema1_action_test", + testQuery: "\n\nSELECT 1\n ", + expectedOutputQuery: "\n\nSELECT 1", + fileName: "definitions/schema1_action_test.sqlx", + + // New properties + target: { + database: "defaultProject", + schema: "schema1", + name: "schema1_action_test" + }, + canonicalTarget: { + database: "defaultProject", + schema: "schema1", + name: "schema1_action_test" + }, + }, + { + // Original test properties + name: "schema2_action_test", + testQuery: "\n\nSELECT 2\n ", + expectedOutputQuery: "\n\nSELECT 2", + fileName: "definitions/schema2_action_test.sqlx", + + // New properties + target: { + database: "defaultProject", + schema: "schema2", + name: "schema2_action_test" + }, + canonicalTarget: { + database: "defaultProject", + schema: "schema2", + name: "schema2_action_test" + }, + }, + ]) + ); + expect(asPlainObject(result.compile.compiledGraph.tables)).deep.equals( + asPlainObject([ + { + "target": { + "database": "defaultProject", + "name": "action", + "schema": "schema1" + }, + "canonicalTarget": { + "database": "defaultProject", + "name": "action", + "schema": "schema1" + }, + "dependencyTargets": [ + { + "database": "defaultProject", + "name": "schema1_action_test", + "schema": "schema1" + } + ], + "disabled": false, + "enumType": "TABLE", + "fileName": "definitions/schema1_action.sqlx", + "hermeticity": "NON_HERMETIC", + "query": "\n\nSELECT 1\n ", + "type": "table" + }, + { + "target": { + "database": "defaultProject", + "name": "action", + "schema": "schema2" + }, + "canonicalTarget": { + "database": "defaultProject", + "name": "action", + "schema": "schema2" + }, + "dependencyTargets": [ + { + "database": "defaultProject", + "name": "schema2_action_test", + "schema": "schema2" + } + ], + "disabled": false, + "enumType": "TABLE", + "fileName": "definitions/schema2_action.sqlx", + "hermeticity": "NON_HERMETIC", + "query": "\n\nSELECT 2\n ", + "type": "table" + } + ])); + }); }); diff --git a/core/session.ts b/core/session.ts index 16cc333cd..6f069e474 100644 --- a/core/session.ts +++ b/core/session.ts @@ -51,6 +51,9 @@ export class Session { public actions: Action[]; public indexedActions: ActionMap; + // Tests need to be resolved after config is applied, which is why we keep them separate from other actions. + public tests: Action[]; + // This map holds information about what assertions are dependent // upon a certain action in our actions list. We use this later to resolve dependencies. public actionAssertionMap = new ActionMap([]); @@ -76,6 +79,7 @@ export class Session { dataform.ProjectConfig.create(originalProjectConfig || projectConfig || DEFAULT_CONFIG) ); this.actions = []; + this.tests = []; this.graphErrors = { compilationErrors: [] }; } @@ -388,7 +392,7 @@ export class Session { newTest.session = this; newTest.setFilename(utils.getCallerFile(this.rootDir)); // Add it to global index. - this.actions.push(newTest) + this.tests.push(newTest) return newTest; } @@ -424,6 +428,7 @@ export class Session { } public compile(): dataform.CompiledGraph { + this.actions.push(...this.tests); this.indexedActions = new ActionMap(this.actions); // defaultLocation is no longer a required parameter to support location auto-selection. diff --git a/tests/api/projects.spec.ts b/tests/api/projects.spec.ts index 82c62f802..73549a248 100644 --- a/tests/api/projects.spec.ts +++ b/tests/api/projects.spec.ts @@ -328,6 +328,16 @@ suite("examples", () => { database: databaseWithSuffix("tada-analytics"), schema: schemaWithSuffix("df_integration_test"), name: "sample_data" + }), + dataform.Target.create({ + database: databaseWithSuffix("tada-analytics"), + schema: schemaWithSuffix("df_integration_test"), + name: "example_test_case" + }), + dataform.Target.create({ + database: databaseWithSuffix("tada-analytics"), + schema: schemaWithSuffix("df_integration_test"), + name: "example_test_case_fq_ref" }) ]); expect(exampleTable.preOps).to.eql([]); From b03d245e03e624a296e07d8b4ff7aec2796ab3e7 Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Mon, 12 Jan 2026 15:25:06 -0500 Subject: [PATCH 07/13] Removed unused code --- core/actions/table.ts | 10 ---------- core/actions/view.ts | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/core/actions/table.ts b/core/actions/table.ts index 8fd1ff518..47bdf594a 100644 --- a/core/actions/table.ts +++ b/core/actions/table.ts @@ -483,16 +483,6 @@ export class Table extends ActionBuilder { return dataform.Target.create(this.proto.target); } - /** @hidden */ - public getCanonicalTarget() { - return dataform.Target.create(this.proto.canonicalTarget); - } - - /** @hidden */ - public getDependencyTargets(): dataform.Target[] { - return this.proto.dependencyTargets.map(target => dataform.Target.create(target)); - } - /** @hidden */ public compile() { const context = new TableContext(this); diff --git a/core/actions/view.ts b/core/actions/view.ts index 1fbf82cdd..92a551f94 100644 --- a/core/actions/view.ts +++ b/core/actions/view.ts @@ -492,16 +492,6 @@ export class View extends ActionBuilder { return dataform.Target.create(this.proto.target); } - /** @hidden */ - public getCanonicalTarget() { - return dataform.Target.create(this.proto.canonicalTarget); - } - - /** @hidden */ - public getDependencyTargets(): dataform.Target[] { - return this.proto.dependencyTargets.map(target => dataform.Target.create(target)); - } - /** @hidden */ public compile() { const context = new ViewContext(this); From 8d97ba09c86b8858dba78af0aaa1932832cec61a Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Mon, 12 Jan 2026 15:29:08 -0500 Subject: [PATCH 08/13] Cleanup --- core/actions/test.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/core/actions/test.ts b/core/actions/test.ts index 6e9f57274..2c69e77e1 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -110,14 +110,26 @@ export class Test extends ActionBuilder { } if (config.dataset) { // Determine target from the parent dataset name - const datasetToTest = this.applySessionToTarget(resolvableAsTarget(toResolvable(config.dataset)), this.session.projectConfig); - this.testTarget = dataform.Target.create(datasetToTest); - const canonicalDatasetToTest = this.applySessionToTarget(resolvableAsTarget(toResolvable(config.dataset)), this.session.canonicalProjectConfig); - this.canonicalTestTarget = dataform.Target.create(canonicalDatasetToTest); + this.testTarget = dataform.Target.create( + this.applySessionToTarget( + resolvableAsTarget( + toResolvable(config.dataset) + ), + this.session.projectConfig + ) + ); + const canonicalTestTarget = dataform.Target.create( + this.applySessionToTarget( + resolvableAsTarget( + toResolvable(config.dataset) + ), + this.session.canonicalProjectConfig + ) + ); // Set the target as the test name, with the tested action database and schema. this.proto.target = this.overrideTargetWithNewName(this.testTarget, config.name || "unnamed_test") - this.proto.canonicalTarget = this.overrideTargetWithNewName(this.canonicalTestTarget, config.name || "unnamed_test") + this.proto.canonicalTarget = this.overrideTargetWithNewName(canonicalTestTarget, config.name || "unnamed_test") } if (config.filename) { this.proto.fileName = config.filename; From bb06f7434f7795f21ee457ecc1de58548b1496ce Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Mon, 12 Jan 2026 15:33:14 -0500 Subject: [PATCH 09/13] Further cleanup --- core/actions/test.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/core/actions/test.ts b/core/actions/test.ts index 2c69e77e1..4bb2a0e7d 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -77,10 +77,8 @@ export class Test extends ActionBuilder { /** @hidden We delay contextification until the final compile step, so hold these here for now. */ public contextableInputs = new Map>(); - private inputToTargets = new Map(); private contextableQuery: Contextable; private testTarget: dataform.ITarget; - private canonicalTestTarget: dataform.ITarget; /** * @hidden Stores the generated proto for the compiled graph. @@ -149,13 +147,10 @@ export class Test extends ActionBuilder { * Sets the input query to unit test against. */ public input(refName: string | string[], contextableQuery: Contextable) { - const target = resolvableAsTarget(toResolvable(refName)); - const inputName = targetStringifier.stringify(target); this.contextableInputs.set( - inputName, + targetStringifier.stringify(resolvableAsTarget(toResolvable(refName))), contextableQuery ); - this.inputToTargets.set(inputName, target); return this; } From b04bdd4113045e6f1c6fe755cceb830c1b45159f Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Mon, 12 Jan 2026 15:50:48 -0500 Subject: [PATCH 10/13] Fixed test naming --- core/actions/test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/actions/test.ts b/core/actions/test.ts index 4bb2a0e7d..db0506dc7 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -126,8 +126,8 @@ export class Test extends ActionBuilder { ); // Set the target as the test name, with the tested action database and schema. - this.proto.target = this.overrideTargetWithNewName(this.testTarget, config.name || "unnamed_test") - this.proto.canonicalTarget = this.overrideTargetWithNewName(canonicalTestTarget, config.name || "unnamed_test") + this.proto.target = this.overrideTargetWithNewName(this.testTarget, this.proto.name); + this.proto.canonicalTarget = this.overrideTargetWithNewName(canonicalTestTarget, this.proto.name); } if (config.filename) { this.proto.fileName = config.filename; From 983bde753b1e717619fb019be491f9409704e78a Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Mon, 12 Jan 2026 16:13:02 -0500 Subject: [PATCH 11/13] Added tags support --- core/actions/test.ts | 8 +++++++- core/actions/test_test.ts | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/actions/test.ts b/core/actions/test.ts index db0506dc7..bf4434d1b 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -32,10 +32,13 @@ export interface ITestConfig extends INamedConfig { /** Path to the source file that the contents of the action is loaded from. */ filename?: string; + + /** Tags for this test action. */ + tags?: string[]; } /** @hidden */ -const ITestConfigProperties = strictKeysOf()(["type", "dataset", "name", "filename"]); +const ITestConfigProperties = strictKeysOf()(["type", "dataset", "name", "filename", "tags"]); /** * Dataform test actions can be used to write unit tests for your generated SQL @@ -132,6 +135,9 @@ export class Test extends ActionBuilder { if (config.filename) { this.proto.fileName = config.filename; } + if (config.tags) { + this.proto.tags = config.tags; + } return this; } diff --git a/core/actions/test_test.ts b/core/actions/test_test.ts index 5b00078ba..92c260a22 100644 --- a/core/actions/test_test.ts +++ b/core/actions/test_test.ts @@ -33,7 +33,8 @@ actions: fs.writeFileSync(actionTestSqlxPath, ` config { type: "test", - dataset: "action" + dataset: "action", + tags: ["tag1", "tag2"] } SELECT 1`); @@ -60,6 +61,7 @@ SELECT 1`); schema: "defaultDataset", name: "action_test" }, + tags: ["tag1", "tag2"], } ]) ); From 5f07d65f80fbadbac3841ab61b39b97c06a1abea Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Mon, 12 Jan 2026 21:34:18 -0500 Subject: [PATCH 12/13] Removed adding test actions into the DAG until later --- core/actions/test_test.ts | 31 ------------------------------- core/session.ts | 15 --------------- tests/api/projects.spec.ts | 10 ---------- 3 files changed, 56 deletions(-) diff --git a/core/actions/test_test.ts b/core/actions/test_test.ts index 92c260a22..6d3e19e43 100644 --- a/core/actions/test_test.ts +++ b/core/actions/test_test.ts @@ -78,13 +78,6 @@ SELECT 1`); "name": "action", "schema": "defaultDataset" }, - "dependencyTargets": [ - { - "database": "defaultProject", - "name": "action_test", - "schema": "defaultDataset" - } - ], "disabled": false, "enumType": "TABLE", "fileName": "definitions/action.sql", @@ -213,11 +206,6 @@ SELECT 1 AS a, 2 AS b`); "database": "defaultProject", "name": "a_declaration", "schema": "defaultDataset" - }, - { - "database": "defaultProject", - "name": "action1_test", - "schema": "defaultDataset" } ], "disabled": false, @@ -243,11 +231,6 @@ SELECT 1 AS a, 2 AS b`); "database": "defaultProject", "name": "action1", "schema": "defaultDataset" - }, - { - "database": "defaultProject", - "name": "action2_test", - "schema": "defaultDataset" } ], "disabled": false, @@ -365,13 +348,6 @@ SELECT 2`); "name": "action", "schema": "schema1" }, - "dependencyTargets": [ - { - "database": "defaultProject", - "name": "schema1_action_test", - "schema": "schema1" - } - ], "disabled": false, "enumType": "TABLE", "fileName": "definitions/schema1_action.sqlx", @@ -390,13 +366,6 @@ SELECT 2`); "name": "action", "schema": "schema2" }, - "dependencyTargets": [ - { - "database": "defaultProject", - "name": "schema2_action_test", - "schema": "schema2" - } - ], "disabled": false, "enumType": "TABLE", "fileName": "definitions/schema2_action.sqlx", diff --git a/core/session.ts b/core/session.ts index 6f069e474..35a2f7650 100644 --- a/core/session.ts +++ b/core/session.ts @@ -468,8 +468,6 @@ export class Session { targets: this.actions.map(action => action.getTarget()) }); - this.addTestsAsDependenciesToTestedActions(this.actions); - this.fullyQualifyDependencies( [].concat( compiledGraph.tables, @@ -558,19 +556,6 @@ export class Session { return compiledChunks; } - private addTestsAsDependenciesToTestedActions(actions: Action[]) { - actions - .filter(action => action instanceof Test) - .map(test => test as Test) - .forEach(test => { - this.indexedActions - .find(test.getTestTarget()) - .filter(action => action instanceof Table || action instanceof View) - .map(action => action as Table | View) - .forEach(tableOrViewAction => tableOrViewAction.dependencies(utils.resolvableAsTarget(test.getTarget()))); - }); - } - private fullyQualifyDependencies(actions: ActionProto[]) { actions.forEach(action => { const fullyQualifiedDependencies: { [name: string]: dataform.ITarget } = {}; diff --git a/tests/api/projects.spec.ts b/tests/api/projects.spec.ts index 73549a248..82c62f802 100644 --- a/tests/api/projects.spec.ts +++ b/tests/api/projects.spec.ts @@ -328,16 +328,6 @@ suite("examples", () => { database: databaseWithSuffix("tada-analytics"), schema: schemaWithSuffix("df_integration_test"), name: "sample_data" - }), - dataform.Target.create({ - database: databaseWithSuffix("tada-analytics"), - schema: schemaWithSuffix("df_integration_test"), - name: "example_test_case" - }), - dataform.Target.create({ - database: databaseWithSuffix("tada-analytics"), - schema: schemaWithSuffix("df_integration_test"), - name: "example_test_case_fq_ref" }) ]); expect(exampleTable.preOps).to.eql([]); From ce2591fff14662a6f08cc8fc4bcec6b804292ff0 Mon Sep 17 00:00:00 2001 From: Fernando Velasquez Date: Mon, 12 Jan 2026 21:49:28 -0500 Subject: [PATCH 13/13] Addressed PR comments --- core/actions/test.ts | 20 ++++++++++---------- core/session.ts | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/actions/test.ts b/core/actions/test.ts index bf4434d1b..be19669e3 100644 --- a/core/actions/test.ts +++ b/core/actions/test.ts @@ -129,8 +129,8 @@ export class Test extends ActionBuilder { ); // Set the target as the test name, with the tested action database and schema. - this.proto.target = this.overrideTargetWithNewName(this.testTarget, this.proto.name); - this.proto.canonicalTarget = this.overrideTargetWithNewName(canonicalTestTarget, this.proto.name); + this.proto.target = overrideTargetWithNewName(this.testTarget, this.proto.name); + this.proto.canonicalTarget = overrideTargetWithNewName(canonicalTestTarget, this.proto.name); } if (config.filename) { this.proto.fileName = config.filename; @@ -226,14 +226,6 @@ export class Test extends ActionBuilder { VerifyProtoErrorBehaviour.SUGGEST_REPORTING_TO_DATAFORM_TEAM ); } - - private overrideTargetWithNewName(target: dataform.ITarget, testName: string): dataform.Target { - return dataform.Target.create({ - database: target.database, - schema: target.schema, - name: testName - }); - } } /** @hidden */ @@ -347,3 +339,11 @@ class RefReplacingContext implements ITableContext { return ""; } } + +function overrideTargetWithNewName(target: dataform.ITarget, testName: string): dataform.Target { + return dataform.Target.create({ + database: target.database, + schema: target.schema, + name: testName + }); +} diff --git a/core/session.ts b/core/session.ts index 35a2f7650..3881aa26a 100644 --- a/core/session.ts +++ b/core/session.ts @@ -560,7 +560,7 @@ export class Session { actions.forEach(action => { const fullyQualifiedDependencies: { [name: string]: dataform.ITarget } = {}; if (action instanceof dataform.Declaration || !action.dependencyTargets) { - // Declarations cannot have dependencies. + // Declarations cannot have dependencies. return; } for (const dependency of action.dependencyTargets) {