Skip to content

Commit

Permalink
Linked template templateLink object does not create a new scope (#801)
Browse files Browse the repository at this point in the history
* Regression from 0.10.0: top-level parameters not recognized in nested template properties

* PR fix
  • Loading branch information
StephenWeatherford authored Jun 19, 2020
1 parent c9895e4 commit aebabc0
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 20 deletions.
24 changes: 14 additions & 10 deletions src/templateScopes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,8 @@ export class NestedTemplateInnerScope extends TemplateScopeFromObject {
*/
export class NestedTemplateOuterScope extends TemplateScope {
public constructor(
public readonly parentScope: TemplateScope,
public readonly nestedTemplateObject: Json.ObjectValue | undefined,
private readonly parentScope: TemplateScope,
private readonly nestedTemplateObject: Json.ObjectValue | undefined,
// tslint:disable-next-line: variable-name
__debugDisplay: string
) {
Expand Down Expand Up @@ -287,7 +287,8 @@ export class NestedTemplateOuterScope extends TemplateScope {

export class LinkedTemplate extends TemplateScope {
public constructor(
public readonly templateLinkObject: Json.ObjectValue | undefined,
private readonly parentScope: TemplateScope,
templateLinkObject: Json.ObjectValue | undefined,
// tslint:disable-next-line: variable-name
__debugDisplay: string
) {
Expand All @@ -299,19 +300,22 @@ export class LinkedTemplate extends TemplateScope {

public readonly scopeKind: TemplateScopeKind = TemplateScopeKind.LinkedDeployment;

// Shares its members with its parent (i.e., if the expressions inside the
// templateLink object reference parameters and variables, those are referring to
// the parent's parameters/variables - a linked template does create a new scope, but
// only inside the template contents themselves, not the templateLink object)
public readonly hasUniqueParamsVarsAndFunctions: boolean = false;

protected getParameterDefinitions(): IParameterDefinition[] | undefined {
// Not supported yet
return undefined;
return this.parentScope.parameterDefinitions;
}

protected getVariableDefinitions(): IVariableDefinition[] | undefined {
// Not supported yet
return undefined;
return this.parentScope.variableDefinitions;
}

protected getNamespaceDefinitions(): UserFunctionNamespaceDefinition[] | undefined {
// Not supported yet
return undefined;
return this.parentScope.namespaceDefinitions;
}

protected getResources(): IResource[] | undefined {
Expand Down Expand Up @@ -369,7 +373,7 @@ export function getChildTemplateForResourceObject(
propertiesObject
?.getPropertyValue(templateKeys.linkedDeploymentTemplateLink)?.asObjectValue;
if (templateLinkObject) {
return new LinkedTemplate(templateLinkObject, `Linked template "${templateName}"`);
return new LinkedTemplate(parentScope, templateLinkObject, `Linked template "${templateName}"`);
}
}

Expand Down
22 changes: 22 additions & 0 deletions test/LinkedTemplates.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// ----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ----------------------------------------------------------------------------

// tslint:disable:no-unused-expression max-func-body-length promise-function-async max-line-length no-unnecessary-class
// tslint:disable:no-non-null-assertion object-literal-key-quotes variable-name no-constant-condition

import { testDiagnosticsFromFile } from "./support/diagnostics";

suite("Linked templates", () => {
suite("variables and parameters inside templateLink object refer to the parent's scope", () => {
test('Regress #792: Regression from 0.10.0: top-level parameters not recognized in nested template properties', async () => {
await testDiagnosticsFromFile(
'templates/linked-templates-scope.json',
{},
[
// Should be no errors
]
);
});
});
});
63 changes: 62 additions & 1 deletion test/functional/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ----------------------------------------------------------------------------

// tslint:disable:no-suspicious-comment
// tslint:disable:no-suspicious-comment max-func-body-length

import { testDiagnostics } from "../support/diagnostics";
import { testWithLanguageServerAndRealFunctionMetadata } from "../support/testWithLanguageServer";
Expand Down Expand Up @@ -69,5 +69,66 @@ suite("General validation tests (all diagnotic sources)", () => {
},
[]);
});

testWithLanguageServerAndRealFunctionMetadata("linked template scope", async () => {
await testDiagnostics(
{
$schema: "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
contentVersion: "1.0.0.0",
parameters: {
linkedTemplatesLocation: {
type: "string"
}
},
variables: {
firstTemplate: "../linkedTemplate1.json",
secondTemplate: "linkedTemplate2.json"
},
resources: [
{
name: "nestedDeployment2",
type: "Microsoft.Resources/deployments",
apiVersion: "2017-05-10",
properties: {
mode: "Incremental",
templateLink: {
uri: "[concat(parameters('linkedTemplatesLocation'), '/', variables('secondTemplate'))]",
contentVersion: "1.0.0.0"
},
parameters: {
linked2param1: {
value: "abc"
}
}
}
},
{
name: "nestedDeployment1",
type: "Microsoft.Resources/deployments",
apiVersion: "2017-05-10",
properties: {
mode: "Incremental",
templateLink: {
uri: "[concat(parameters('linkedTemplatesLocation'), '/', variables('firstTemplate'))]"
},
parameters: {
linked1param1: {
value: "my value" // (error)
}
}
}
}
],
outputs: {
},
functions: [
]
},
{
},
[
// expecting no errors
]);
});
});
});
45 changes: 36 additions & 9 deletions test/support/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,23 +193,50 @@ export interface ITestDiagnosticsOptions extends IGetDiagnosticsOptions {
}

export interface IGetDiagnosticsOptions {
/**
* Parameters that will be placed into a temp file and associated with the template file
*/
parameters?: string | Partial<IDeploymentParametersFile>;
/**
* A parameter file that will be associated with the template file
*/
parametersFile?: string;
includeSources?: DiagnosticSource[]; // Error sources to include in the comparison - defaults to all
ignoreSources?: DiagnosticSource[]; // Error sources to ignore in the comparison - defaults to ignoring none
includeRange?: boolean; // defaults to false - whether to include the error range in the results for comparison (if true, ignored when expected messages don't have ranges)
search?: RegExp; // Run a replacement using this regex and replacement on the file/contents before testing for errors
replace?: string; // Run a replacement using this regex and replacement on the file/contents before testing for errors
doNotAddSchema?: boolean; // Don't add schema (testDiagnostics only) automatically
waitForChange?: boolean; // Wait until diagnostics change before retrieving themed
/**
* Error sources to include in the comparison - defaults to all
*/
includeSources?: DiagnosticSource[];
/**
* Error sources to ignore in the comparison - defaults to ignoring none
*/
ignoreSources?: DiagnosticSource[];
/**
* defaults to false - whether to include the error range in the results for comparison (if true, ignored when expected messages don't have ranges)
*/
includeRange?: boolean;
/**
* Run a replacement using this regex and replacement on the file/contents before testing for errors
*/
search?: RegExp;
/**
* Run a replacement using this regex and replacement on the file/contents before testing for errors
*/
replace?: string;
/**
* Don't add schema (testDiagnostics only) automatically
*/
doNotAddSchema?: boolean;
/**
* Wait until diagnostics change before retrieving themed
*/
waitForChange?: boolean;
}

export async function testDiagnosticsFromFile(filePath: string | Partial<IDeploymentTemplate>, options: ITestDiagnosticsOptions, expected: string[]): Promise<void> {
await testDiagnosticsCore(filePath, options, expected);
}

export async function testDiagnostics(json: string | Partial<IDeploymentTemplate>, options: ITestDiagnosticsOptions, expected: string[]): Promise<void> {
await testDiagnosticsCore(json, options, expected);
export async function testDiagnostics(templateContentsOrFileName: string | Partial<IDeploymentTemplate>, options: ITestDiagnosticsOptions, expected: string[]): Promise<void> {
await testDiagnosticsCore(templateContentsOrFileName, options, expected);
}

async function testDiagnosticsCore(templateContentsOrFileName: string | Partial<IDeploymentTemplate>, options: ITestDiagnosticsOptions, expected: string[]): Promise<void> {
Expand Down
64 changes: 64 additions & 0 deletions test/templates/linked-templates-scope.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"linkedTemplatesLocation": {
"type": "string",
"defaultValue": "https://raw.githubusercontent.com/StephenWeatherford/template-examples/master/linkedTemplates/twoLinked/templates/library"
},
"intvalue": {
"type": "int",
"defaultValue": 123
}
},
"variables": {
"firstTemplate": "../linkedTemplate1.json",
"secondTemplate": "linkedTemplate2.json",
"stringValue": "abc",
"nestedDeployment2Name": "nestedDeployment2",
"mode": "Incremental",
"contentVersion": "1.2.3.4"
},
"resources": [
{
"name": "[variables('nestedDeployment2Name')]",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2017-05-10",
"properties": {
"mode": "[variables('mode')]",
"templateLink": {
// THESE REFER TO THE TOP-LEVEL PARAMS/VARS
"uri": "[concat(parameters('linkedTemplatesLocation'), '/', variables('secondTemplate'))]",
"contentVersion": "[variables('contentVersion')]"
},
"parameters": {
"linked2param1": {
"value": "[variables('stringValue')]"
}
}
}
},
{
"name": "nestedDeployment1",
"type": "Microsoft.Resources/deployments",
"apiVersion": "2017-05-10",
"properties": {
"mode": "[variables('mode')]",
"templateLink": {
// THESE REFER TO THE TOP-LEVEL PARAMS/VARS
"uri": "[concat(parameters('linkedTemplatesLocation'), '/', variables('firstTemplate'))]"
},
"parameters": {
"linked1param1": {
"value": "[parameters('intvalue')]"
}
},
"debugSetting": {
"detailLevel": "requestContent,responseContent"
}
}
}
],
"outputs": {},
"functions": []
}

0 comments on commit aebabc0

Please sign in to comment.