Skip to content
This repository has been archived by the owner on May 1, 2020. It is now read-only.

Commit

Permalink
fix(deep-linking): remove IonicPage import statement in transform/non…
Browse files Browse the repository at this point in the history
…-transform approachs to work better with strict TS settings
  • Loading branch information
danbucholtz committed Sep 20, 2017
1 parent a16deee commit 84d9ec7
Show file tree
Hide file tree
Showing 3 changed files with 283 additions and 47 deletions.
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@
"xml2js": "0.4.17"
},
"devDependencies": {
"@angular/animations": "4.3.6",
"@angular/common": "4.3.6",
"@angular/compiler": "4.3.6",
"@angular/compiler-cli": "4.3.6",
"@angular/core": "4.3.6",
"@angular/forms": "4.3.6",
"@angular/http": "4.3.6",
"@angular/platform-browser": "4.3.6",
"@angular/platform-browser-dynamic": "4.3.6",
"@angular/platform-server": "4.3.6",
"@angular/tsc-wrapped": "4.3.6",
"@angular/animations": "4.4.3",
"@angular/common": "4.4.3",
"@angular/compiler": "4.4.3",
"@angular/compiler-cli": "4.4.3",
"@angular/core": "4.4.3",
"@angular/forms": "4.4.3",
"@angular/http": "4.4.3",
"@angular/platform-browser": "4.4.3",
"@angular/platform-browser-dynamic": "4.4.3",
"@angular/platform-server": "4.4.3",
"@angular/tsc-wrapped": "4.4.3",
"@types/chalk": "^0.4.30",
"@types/chokidar": "1.4.29",
"@types/clean-css": "^3.4.29",
Expand Down
155 changes: 154 additions & 1 deletion src/deep-linking/util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2039,7 +2039,7 @@ export class AboutPage {
const expectedContent = `
import { Component } from '@angular/core';
import { IonicPage, PopoverController } from 'ionic-angular';
import { PopoverController } from 'ionic-angular';
@Component({
Expand All @@ -2061,4 +2061,157 @@ export class AboutPage {
expect(result).toEqual(expectedContent);
});
});

describe('purgeDeepLinkImport', () => {
it('should remove the IonicPage decorator but preserve others', () => {
const input = `
import { Component } from '@angular/core';
import { IonicPage, PopoverController } from 'ionic-angular';
@IonicPage()
@Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
conferenceDate = '2047-05-17';
constructor(public popoverCtrl: PopoverController) { }
presentPopover(event: Event) {
let popover = this.popoverCtrl.create('PopoverPage');
popover.present({ ev: event });
}
}
`;
const expectedText = `
import { Component } from '@angular/core';
import { PopoverController } from 'ionic-angular';
@IonicPage()
@Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
conferenceDate = '2047-05-17';
constructor(public popoverCtrl: PopoverController) { }
presentPopover(event: Event) {
let popover = this.popoverCtrl.create('PopoverPage');
popover.present({ ev: event });
}
}
`;
const result = util.purgeDeepLinkImport(input);
expect(result).toEqual(expectedText);
});

it('should remove the entire import statement', () => {
const input = `
import { Component } from '@angular/core';
import { IonicPage } from 'ionic-angular';
@IonicPage()
@Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
conferenceDate = '2047-05-17';
constructor(public popoverCtrl: PopoverController) { }
presentPopover(event: Event) {
let popover = this.popoverCtrl.create('PopoverPage');
popover.present({ ev: event });
}
}
`;
const expectedText = `
import { Component } from '@angular/core';
@IonicPage()
@Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
conferenceDate = '2047-05-17';
constructor(public popoverCtrl: PopoverController) { }
presentPopover(event: Event) {
let popover = this.popoverCtrl.create('PopoverPage');
popover.present({ ev: event });
}
}
`;
const result = util.purgeDeepLinkImport(input);
expect(result).toEqual(expectedText);
});
});

describe('purgeDeepLinkDecoratorTSTransform', () => {
it('should do something', () => {
const input = `
import { Component } from '@angular/core';
import { IonicPage } from 'ionic-angular';
@IonicPage()
@Component({
selector: 'page-about',
templateUrl: 'about.html'
})
export class AboutPage {
conferenceDate = '2047-05-17';
constructor(public popoverCtrl: PopoverController) { }
presentPopover(event: Event) {
let popover = this.popoverCtrl.create('PopoverPage');
popover.present({ ev: event });
}
}
`;

const expected = `import { Component } from "@angular/core";
import { } from "ionic-angular";
@Component({
selector: "page-about",
templateUrl: "about.html"
})
export class AboutPage {
conferenceDate = "2047-05-17";
constructor(public popoverCtrl: PopoverController) { }
presentPopover(event: Event) {
let popover = this.popoverCtrl.create("PopoverPage");
popover.present({ ev: event });
}
}
`;
const result = transformSourceFile(input, [util.purgeDeepLinkDecoratorTSTransformImpl]);
expect(result).toEqual(expected);
});
});
});



export function transformSourceFile(sourceText: string, transformers: ts.TransformerFactory<ts.SourceFile>[]) {
const transformed = ts.transform(ts.createSourceFile('source.ts', sourceText, ts.ScriptTarget.ES2015), transformers);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }, {
onEmitNode: transformed.emitNodeWithNotification,
substituteNode: transformed.substituteNode
});
const result = printer.printBundle(ts.createBundle(transformed.transformed));
transformed.dispose();
return result;
}
153 changes: 118 additions & 35 deletions src/deep-linking/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ClassDeclaration,
createClassDeclaration,
createIdentifier,
createNamedImports,
Decorator,
Expression,
Identifier,
Expand All @@ -24,6 +25,8 @@ import {
TransformerFactory,
updateCall,
updateClassDeclaration,
updateImportClause,
updateImportDeclaration,
updateSourceFile,
visitEachChild,
VisitResult
Expand Down Expand Up @@ -378,51 +381,87 @@ export class ${className}Module {}
}

export function purgeDeepLinkDecoratorTSTransform(): TransformerFactory<SourceFile> {
return (transformContext: TransformationContext) => {
return purgeDeepLinkDecoratorTSTransformImpl;
}

function visitClassDeclaration(classDeclaration: ClassDeclaration) {
let hasDeepLinkDecorator = false;
const diffDecorators: Decorator[] = [];
for (const decorator of classDeclaration.decorators || []) {
if (decorator.expression && (decorator.expression as CallExpression).expression
&& ((decorator.expression as CallExpression).expression as Identifier).escapedText === DEEPLINK_DECORATOR_TEXT) {
hasDeepLinkDecorator = true;
} else {
diffDecorators.push(decorator);
}
export function purgeDeepLinkDecoratorTSTransformImpl(transformContext: TransformationContext) {
function visitClassDeclaration(classDeclaration: ClassDeclaration) {
let hasDeepLinkDecorator = false;
const diffDecorators: Decorator[] = [];
for (const decorator of classDeclaration.decorators || []) {
if (decorator.expression && (decorator.expression as CallExpression).expression
&& ((decorator.expression as CallExpression).expression as Identifier).escapedText === DEEPLINK_DECORATOR_TEXT) {
hasDeepLinkDecorator = true;
} else {
diffDecorators.push(decorator);
}
}

if (hasDeepLinkDecorator) {
return updateClassDeclaration(
classDeclaration,
diffDecorators,
classDeclaration.modifiers,
classDeclaration.name,
classDeclaration.typeParameters,
classDeclaration.heritageClauses,
classDeclaration.members
);
}
if (hasDeepLinkDecorator) {
return updateClassDeclaration(
classDeclaration,
diffDecorators,
classDeclaration.modifiers,
classDeclaration.name,
classDeclaration.typeParameters,
classDeclaration.heritageClauses,
classDeclaration.members
);

return classDeclaration;
}

function visit(node: Node): VisitResult<Node> {
switch (node.kind) {
return classDeclaration;
}

case SyntaxKind.ClassDeclaration:
return visitClassDeclaration(node as ClassDeclaration);
function visitImportDeclaration(importDeclaration: ImportDeclaration, sourceFile: SourceFile): ImportDeclaration {

default:
return visitEachChild(node, (node) => {
return visit(node);
}, transformContext);
}
if (importDeclaration.moduleSpecifier
&& (importDeclaration.moduleSpecifier as StringLiteral).text === 'ionic-angular'
&& importDeclaration.importClause
&& importDeclaration.importClause.namedBindings
&& (importDeclaration.importClause.namedBindings as NamedImports).elements
) {
// loop over each import and store it
const importSpecifiers: ImportSpecifier[] = [];
(importDeclaration.importClause.namedBindings as NamedImports).elements.forEach((importSpecifier: ImportSpecifier) => {

if (importSpecifier.name.escapedText !== DEEPLINK_DECORATOR_TEXT) {
importSpecifiers.push(importSpecifier);
}
});
const emptyNamedImports = createNamedImports(importSpecifiers);
const newImportClause = updateImportClause(importDeclaration.importClause, importDeclaration.importClause.name, emptyNamedImports);

return updateImportDeclaration(
importDeclaration,
importDeclaration.decorators,
importDeclaration.modifiers,
newImportClause,
importDeclaration.moduleSpecifier
);
}

return (sourceFile: SourceFile) => {
return visit(sourceFile) as SourceFile;
};

return importDeclaration;
}

function visit(node: Node, sourceFile: SourceFile): VisitResult<Node> {
switch (node.kind) {

case SyntaxKind.ClassDeclaration:
return visitClassDeclaration(node as ClassDeclaration);

case SyntaxKind.ImportDeclaration:
return visitImportDeclaration(node as ImportDeclaration, sourceFile);
default:
return visitEachChild(node, (node) => {
return visit(node, sourceFile);
}, transformContext);
}
}

return (sourceFile: SourceFile) => {
return visit(sourceFile, sourceFile) as SourceFile;
};
}

Expand All @@ -442,9 +481,53 @@ export function purgeDeepLinkDecorator(inputText: string): string {
toRemove.forEach(node => {
toReturn = replaceNode('', inputText, node, '');
});

toReturn = purgeDeepLinkImport(toReturn);
return toReturn;
}

export function purgeDeepLinkImport(inputText: string): string {
const sourceFile = getTypescriptSourceFile('', inputText);
const importDeclarations = findNodes(sourceFile, sourceFile, SyntaxKind.ImportDeclaration) as ImportDeclaration[];

importDeclarations.forEach(importDeclaration => {
if (importDeclaration.moduleSpecifier
&& (importDeclaration.moduleSpecifier as StringLiteral).text === 'ionic-angular'
&& importDeclaration.importClause
&& importDeclaration.importClause.namedBindings
&& (importDeclaration.importClause.namedBindings as NamedImports).elements
) {
// loop over each import and store it
let decoratorIsImported = false;
const namedImportStrings: string[] = [];
(importDeclaration.importClause.namedBindings as NamedImports).elements.forEach((importSpecifier: ImportSpecifier) => {

if (importSpecifier.name.escapedText === DEEPLINK_DECORATOR_TEXT) {
decoratorIsImported = true;
} else {
namedImportStrings.push(importSpecifier.name.escapedText as string);
}
});

// okay, cool. If namedImportStrings is empty, then just remove the entire import statement
// otherwise, just replace the named imports with the namedImportStrings separated by a comma
if (decoratorIsImported) {
if (namedImportStrings.length) {
// okay cool, we only want to remove some of these homies
const stringRepresentation = namedImportStrings.join(', ');
const namedImportString = `{ ${stringRepresentation} }`;
inputText = replaceNode('', inputText, importDeclaration.importClause.namedBindings, namedImportString);
} else {
// remove the entire import statement
inputText = replaceNode('', inputText, importDeclaration, '');
}
}
}
});

return inputText;
}

export function getInjectDeepLinkConfigTypescriptTransform() {
const deepLinkString = convertDeepLinkConfigEntriesToString(getParsedDeepLinkConfig());
const appNgModulePath = getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH);
Expand Down

1 comment on commit 84d9ec7

@soumak77
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danbucholtz are these changes intended to fix #1106? If you're doing all this work around deep-linker fixes, it would be nice to have that issue addressed as well. Thanks!

Please sign in to comment.