Skip to content

Commit

Permalink
refactor: restore @angular/cdk clone (#35)
Browse files Browse the repository at this point in the history
* refactor: restore CDK clone

* build: remove @angular/cdk

* refactor: update cdk sources

* fix: type mismatch
  • Loading branch information
nitayneeman authored Aug 7, 2020
1 parent 8b3a1ab commit 3a0bdc0
Show file tree
Hide file tree
Showing 20 changed files with 745 additions and 24 deletions.
9 changes: 0 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"dependencies": {
"@angular-devkit/core": "^8.3.8",
"@angular-devkit/schematics": "^8.3.8",
"@angular/cdk": "^8.2.3",
"@schematics/angular": "^8.3.8",
"@schematics/update": "^0.803.8",
"rxjs": "^6.4.0",
Expand Down
8 changes: 8 additions & 0 deletions src/cdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export * from './ng-add/package-config';
export * from './utils/ast';
export * from './utils/build-component';
export * from './utils/get-project';
export * from './utils/html-head-element';
export * from './utils/project-main-file';
export * from './utils/project-targets';
export * from './utils/version-agnostic-typescript';
40 changes: 40 additions & 0 deletions src/cdk/ng-add/package-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { Tree } from '@angular-devkit/schematics';

/**
* Sorts the keys of the given object.
* @returns A new object instance with sorted keys
*/
function sortObjectByKeys(obj: object) {
return Object.keys(obj)
.sort()
.reduce((result, key) => (result[key] = obj[key]) && result, {});
}

/** Adds a package to the package.json in the given host tree. */
export function addPackageToPackageJson(host: Tree, pkg: string, version: string): Tree {
if (host.exists('package.json')) {
const sourceText = host.read('package.json')!.toString('utf-8');
const json = JSON.parse(sourceText);

if (!json.dependencies) {
json.dependencies = {};
}

if (!json.dependencies[pkg]) {
json.dependencies[pkg] = version;
json.dependencies = sortObjectByKeys(json.dependencies);
}

host.overwrite('package.json', JSON.stringify(json, null, 2));
}

return host;
}
76 changes: 76 additions & 0 deletions src/cdk/utils/ast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { WorkspaceProject } from '@angular-devkit/core/src/experimental/workspace';
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { Schema as ComponentOptions } from '@schematics/angular/component/schema';
import { addImportToModule } from '@schematics/angular/utility/ast-utils';
import { InsertChange } from '@schematics/angular/utility/change';
import { getWorkspace } from '@schematics/angular/utility/config';
import { findModuleFromOptions as internalFindModule } from '@schematics/angular/utility/find-module';
import { getAppModulePath } from '@schematics/angular/utility/ng-ast-utils';
import { getProjectMainFile } from './project-main-file';
import { ts, typescript } from './version-agnostic-typescript';

/** Reads file given path and returns TypeScript source file. */
export function getSourceFile(host: Tree, path: string): typescript.SourceFile {
const buffer = host.read(path);
if (!buffer) {
throw new SchematicsException(`Could not find file for path: ${path}`);
}
return ts.createSourceFile(path, buffer.toString(), ts.ScriptTarget.Latest, true);
}

/** Import and add module to root app module. */
export function addModuleImportToRootModule(host: Tree, moduleName: string, src: string, project: WorkspaceProject) {
const modulePath = getAppModulePath(host, getProjectMainFile(project));
addModuleImportToModule(host, modulePath, moduleName, src);
}

/**
* Import and add module to specific module path.
* @param host the tree we are updating
* @param modulePath src location of the module to import
* @param moduleName name of module to import
* @param src src location to import
*/
export function addModuleImportToModule(host: Tree, modulePath: string, moduleName: string, src: string) {
const moduleSource: any = getSourceFile(host, modulePath);

if (!moduleSource) {
throw new SchematicsException(`Module not found: ${modulePath}`);
}

const changes = addImportToModule(moduleSource, modulePath, moduleName, src);
const recorder = host.beginUpdate(modulePath);

changes.forEach(change => {
if (change instanceof InsertChange) {
recorder.insertLeft(change.pos, change.toAdd);
}
});

host.commitUpdate(recorder);
}

/** Wraps the internal find module from options with undefined path handling */
export function findModuleFromOptions(host: Tree, options: ComponentOptions): string | undefined {
const workspace = getWorkspace(host);

if (!options.project) {
options.project = Object.keys(workspace.projects)[0];
}

const project = workspace.projects[options.project];

if (options.path === undefined) {
options.path = `/${project.root}/src/app`;
}

return internalFindModule(host, options);
}
89 changes: 89 additions & 0 deletions src/cdk/utils/ast/ng-module-imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { SchematicsException, Tree } from '@angular-devkit/schematics';
import * as ts from 'typescript';

/**
* Whether the Angular module in the given path imports the specified module class name.
*/
export function hasNgModuleImport(tree: Tree, modulePath: string, className: string): boolean {
const moduleFileContent = tree.read(modulePath);

if (!moduleFileContent) {
throw new SchematicsException(`Could not read Angular module file: ${modulePath}`);
}

const parsedFile = ts.createSourceFile(modulePath, moduleFileContent.toString(), ts.ScriptTarget.Latest, true);
const ngModuleMetadata = findNgModuleMetadata(parsedFile);

if (!ngModuleMetadata) {
throw new SchematicsException(`Could not find NgModule declaration inside: "${modulePath}"`);
}

for (let property of ngModuleMetadata!.properties) {
if (
!ts.isPropertyAssignment(property) ||
property.name.getText() !== 'imports' ||
!ts.isArrayLiteralExpression(property.initializer)
) {
continue;
}

if (property.initializer.elements.some(element => element.getText() === className)) {
return true;
}
}

return false;
}

/**
* Resolves the last identifier that is part of the given expression. This helps resolving
* identifiers of nested property access expressions (e.g. myNamespace.core.NgModule).
*/
function resolveIdentifierOfExpression(expression: ts.Expression): ts.Identifier | null {
if (ts.isIdentifier(expression)) {
return expression;
} else if (ts.isPropertyAccessExpression(expression)) {
return expression.name;
}
return null;
}

/**
* Finds a NgModule declaration within the specified TypeScript node and returns the
* corresponding metadata for it. This function searches breadth first because
* NgModule's are usually not nested within other expressions or declarations.
*/
function findNgModuleMetadata(rootNode: ts.Node): ts.ObjectLiteralExpression | null {
// Add immediate child nodes of the root node to the queue.
const nodeQueue: ts.Node[] = [...rootNode.getChildren()];

while (nodeQueue.length) {
const node = nodeQueue.shift()!;

if (ts.isDecorator(node) && ts.isCallExpression(node.expression) && isNgModuleCallExpression(node.expression)) {
return node.expression.arguments[0] as ts.ObjectLiteralExpression;
} else {
nodeQueue.push(...node.getChildren());
}
}

return null;
}

/** Whether the specified call expression is referring to a NgModule definition. */
function isNgModuleCallExpression(callExpression: ts.CallExpression): boolean {
if (!callExpression.arguments.length || !ts.isObjectLiteralExpression(callExpression.arguments[0])) {
return false;
}

const decoratorIdentifier = resolveIdentifierOfExpression(callExpression.expression);
return decoratorIdentifier ? decoratorIdentifier.text === 'NgModule' : false;
}
Loading

0 comments on commit 3a0bdc0

Please sign in to comment.