Skip to content

Commit

Permalink
Merge pull request #1211 from IgniteUI/dpetev/schamtics-add-provideAn…
Browse files Browse the repository at this point in the history
…imations

feat(ng-add): add setup config provideAnimations
  • Loading branch information
Lipata authored Feb 21, 2024
2 parents 3a94150 + 2f47676 commit 8f6a34b
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 7 deletions.
55 changes: 52 additions & 3 deletions packages/core/typescript/TypeScriptFileUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Util } from "../util/Util";
import { TypeScriptUtils as TsUtils } from "./TypeScriptUtils";

const DEFAULT_ROUTES_VARIABLE = "routes";
const DEFAULT_APPCONFIG_VARIABLE = "appConfig";
/**
* Apply various updates to typescript files using AST
*/
Expand Down Expand Up @@ -170,6 +171,54 @@ export class TypeScriptFileUpdate {
this.ngMetaEdits.imports.push(...imports);
}

/**
* Create a CallExpression for dep and add it to the `ApplicationConfig` providers array.
* @param dep The dependency to provide. TODO: Use different type to describe CallExpression, possible parameters, etc
* @param configVariable The name of the app config variable to edit
*/
public addAppConfigProvider(dep: Pick<TemplateDependency, 'provide' | 'from'>, configVariable = DEFAULT_APPCONFIG_VARIABLE) {
let providers = this.asArray(dep.provide, {});

const transformer: ts.TransformerFactory<ts.Node> = <T extends ts.Node>(context: ts.TransformationContext) =>
(rootNode: T) => {
const conditionalVisitor: ts.Visitor = (node: ts.Node): ts.Node => {
if (node.kind === ts.SyntaxKind.ArrayLiteralExpression &&
node.parent.kind === ts.SyntaxKind.PropertyAssignment &&
(node.parent as ts.PropertyAssignment).name.getText() === "providers") {
const array = (node as ts.ArrayLiteralExpression);
const nodes = ts.visitNodes(array.elements, visitor);
const alreadyProvided = nodes.map(x => TsUtils.getIdentifierName(x));

providers = providers.filter(x => alreadyProvided.indexOf(x) === -1);
this.requestImport(providers, dep.from);

const newProvides = providers
.map(x => ts.factory.createCallExpression(ts.factory.createIdentifier(x), undefined, undefined));
const elements = ts.factory.createNodeArray([
...nodes,
...newProvides
]);

return ts.factory.updateArrayLiteralExpression(array, elements);
} else {
return ts.visitEachChild(node, conditionalVisitor, context);
}
};

const visitCondition = (node: ts.Node): boolean => {
return node.kind === ts.SyntaxKind.VariableDeclaration &&
(node as ts.VariableDeclaration).name.getText() === configVariable &&
(node as ts.VariableDeclaration).type.getText() === "ApplicationConfig";
};
const visitor: ts.Visitor = this.createVisitor(conditionalVisitor, visitCondition, context);
context.enableSubstitution(ts.SyntaxKind.ArrayLiteralExpression);
return ts.visitNode(rootNode, visitor);
};
this.targetSource = ts.transform(this.targetSource, [transformer], {
pretty: true // oh well..
}).transformed[0] as ts.SourceFile;
}

//#region File state

/** Initializes existing imports info, [re]sets import and `NgModule` edits */
Expand Down Expand Up @@ -324,15 +373,15 @@ export class TypeScriptFileUpdate {
const index = existingProperties.indexOf(childrenProperty);
const childrenPropertyName = childrenProperty.name;
childrenProperty =
ts.updatePropertyAssignment(
ts.factory.updatePropertyAssignment(
childrenProperty,
childrenPropertyName,
ts.factory.createArrayLiteralExpression([...newArrayValues])
);
existingProperties
.splice(index, 1, childrenProperty);
}
return ts.updateObjectLiteral(currentNode, existingProperties) as ts.Node;
return ts.factory.updateObjectLiteralExpression(currentNode, existingProperties) as ts.Node;
} else {
return ts.visitEachChild(node, conditionalVisitor, context);
}
Expand Down Expand Up @@ -488,7 +537,7 @@ export class TypeScriptFileUpdate {
newProps.push(ts.factory.createPropertyAssignment(prop, arrayExpr));
}

return ts.updateObjectLiteral(obj, [
return ts.factory.updateObjectLiteralExpression(obj, [
...objProperties,
...newProps
]);
Expand Down
13 changes: 9 additions & 4 deletions packages/core/typescript/TypeScriptUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,15 @@ export class TypeScriptUtils {
* @param x Node to extract identifier text from.
*/
public static getIdentifierName(x: ts.Node): string {
if (x.kind === ts.SyntaxKind.CallExpression) {
const prop = ((x as ts.CallExpression).expression as ts.PropertyAccessExpression);
//pluck identifier from expression.name
x = prop.expression;
if (ts.isCallExpression(x)) {
const expression = x.expression;
if (ts.isPropertyAccessExpression(expression)) {
//pluck identifier from expression.name
x = expression.expression;
}
if (ts.isIdentifier(expression)) {
x = expression;
}
}
return (x as ts.Identifier).text;
}
Expand Down
7 changes: 7 additions & 0 deletions packages/ng-schematics/src/cli-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,19 @@ function importBrowserAnimations(): Rule {
return async (tree: Tree) => {
const projects = await getProjects(tree);
projects.forEach(project => {
// TODO: Resolve hardcoded paths instead
const moduleFile = `${project.sourceRoot}/app/app.module.ts`;
if (tree.exists(moduleFile)) {
const mainModule = new TypeScriptFileUpdate(moduleFile);
mainModule.addNgModuleMeta({ import: "BrowserAnimationsModule", from: "@angular/platform-browser/animations" });
mainModule.finalize();
}
const appConfigFile = `${project.sourceRoot}/app/app.config.ts`;
if (tree.exists(appConfigFile)) {
const appConfig = new TypeScriptFileUpdate(appConfigFile);
appConfig.addAppConfigProvider({ provide: "provideAnimations", from: "@angular/platform-browser/animations" });
appConfig.finalize();
}
});
};
}
Expand Down
48 changes: 48 additions & 0 deletions packages/ng-schematics/src/cli-config/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,54 @@ export class AppModule {
expect(content.replace(/\r\n/g, "\n")).toEqual(moduleContentAfterSchematic.replace(/\r\n/g, "\n"));
});

it("should add provideAnimations to app.config.ts", async () => {
const moduleContent =
`import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)]
};
`;

const moduleContentAfterSchematic =
`import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideAnimations } from "@angular/platform-browser/animations";
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideAnimations()]
};
`;
const targetFile = "./src/app/app.config.ts";
tree.create(targetFile, moduleContent);

await runner.runSchematicAsync("cli-config", {}, tree).toPromise();
let content = tree.readContent(targetFile);
expect(content.replace(/\r\n/g, "\n")).toEqual(moduleContentAfterSchematic.replace(/\r\n/g, "\n"));
});

it("should NOT add provideAnimations to app.config.ts if it already exists", async () => {
const moduleContent =
`import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideAnimations } from "@angular/platform-browser/animations";
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideAnimations()]
};
`;
const targetFile = "./src/app/app.config.ts";
tree.create(targetFile, moduleContent);

await runner.runSchematicAsync("cli-config", {}, tree).toPromise();
let content = tree.readContent(targetFile);
expect(content.replace(/\r\n/g, "\n")).toEqual(moduleContent.replace(/\r\n/g, "\n"));
});

it("should properly display the dependency mismatch warning", async () => {
const warns: string[] = [];
runner.logger.subscribe(entry => {
Expand Down

0 comments on commit 8f6a34b

Please sign in to comment.