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

Commit 84d9ec7

Browse files
committed
fix(deep-linking): remove IonicPage import statement in transform/non-transform approachs to work better with strict TS settings
1 parent a16deee commit 84d9ec7

File tree

3 files changed

+283
-47
lines changed

3 files changed

+283
-47
lines changed

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,17 @@
6363
"xml2js": "0.4.17"
6464
},
6565
"devDependencies": {
66-
"@angular/animations": "4.3.6",
67-
"@angular/common": "4.3.6",
68-
"@angular/compiler": "4.3.6",
69-
"@angular/compiler-cli": "4.3.6",
70-
"@angular/core": "4.3.6",
71-
"@angular/forms": "4.3.6",
72-
"@angular/http": "4.3.6",
73-
"@angular/platform-browser": "4.3.6",
74-
"@angular/platform-browser-dynamic": "4.3.6",
75-
"@angular/platform-server": "4.3.6",
76-
"@angular/tsc-wrapped": "4.3.6",
66+
"@angular/animations": "4.4.3",
67+
"@angular/common": "4.4.3",
68+
"@angular/compiler": "4.4.3",
69+
"@angular/compiler-cli": "4.4.3",
70+
"@angular/core": "4.4.3",
71+
"@angular/forms": "4.4.3",
72+
"@angular/http": "4.4.3",
73+
"@angular/platform-browser": "4.4.3",
74+
"@angular/platform-browser-dynamic": "4.4.3",
75+
"@angular/platform-server": "4.4.3",
76+
"@angular/tsc-wrapped": "4.4.3",
7777
"@types/chalk": "^0.4.30",
7878
"@types/chokidar": "1.4.29",
7979
"@types/clean-css": "^3.4.29",

src/deep-linking/util.spec.ts

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2039,7 +2039,7 @@ export class AboutPage {
20392039
const expectedContent = `
20402040
import { Component } from '@angular/core';
20412041
2042-
import { IonicPage, PopoverController } from 'ionic-angular';
2042+
import { PopoverController } from 'ionic-angular';
20432043
20442044
20452045
@Component({
@@ -2061,4 +2061,157 @@ export class AboutPage {
20612061
expect(result).toEqual(expectedContent);
20622062
});
20632063
});
2064+
2065+
describe('purgeDeepLinkImport', () => {
2066+
it('should remove the IonicPage decorator but preserve others', () => {
2067+
const input = `
2068+
import { Component } from '@angular/core';
2069+
2070+
import { IonicPage, PopoverController } from 'ionic-angular';
2071+
2072+
@IonicPage()
2073+
@Component({
2074+
selector: 'page-about',
2075+
templateUrl: 'about.html'
2076+
})
2077+
export class AboutPage {
2078+
conferenceDate = '2047-05-17';
2079+
2080+
constructor(public popoverCtrl: PopoverController) { }
2081+
2082+
presentPopover(event: Event) {
2083+
let popover = this.popoverCtrl.create('PopoverPage');
2084+
popover.present({ ev: event });
2085+
}
2086+
}
2087+
`;
2088+
const expectedText = `
2089+
import { Component } from '@angular/core';
2090+
2091+
import { PopoverController } from 'ionic-angular';
2092+
2093+
@IonicPage()
2094+
@Component({
2095+
selector: 'page-about',
2096+
templateUrl: 'about.html'
2097+
})
2098+
export class AboutPage {
2099+
conferenceDate = '2047-05-17';
2100+
2101+
constructor(public popoverCtrl: PopoverController) { }
2102+
2103+
presentPopover(event: Event) {
2104+
let popover = this.popoverCtrl.create('PopoverPage');
2105+
popover.present({ ev: event });
2106+
}
2107+
}
2108+
`;
2109+
const result = util.purgeDeepLinkImport(input);
2110+
expect(result).toEqual(expectedText);
2111+
});
2112+
2113+
it('should remove the entire import statement', () => {
2114+
const input = `
2115+
import { Component } from '@angular/core';
2116+
2117+
import { IonicPage } from 'ionic-angular';
2118+
2119+
@IonicPage()
2120+
@Component({
2121+
selector: 'page-about',
2122+
templateUrl: 'about.html'
2123+
})
2124+
export class AboutPage {
2125+
conferenceDate = '2047-05-17';
2126+
2127+
constructor(public popoverCtrl: PopoverController) { }
2128+
2129+
presentPopover(event: Event) {
2130+
let popover = this.popoverCtrl.create('PopoverPage');
2131+
popover.present({ ev: event });
2132+
}
2133+
}
2134+
`;
2135+
const expectedText = `
2136+
import { Component } from '@angular/core';
2137+
2138+
2139+
2140+
@IonicPage()
2141+
@Component({
2142+
selector: 'page-about',
2143+
templateUrl: 'about.html'
2144+
})
2145+
export class AboutPage {
2146+
conferenceDate = '2047-05-17';
2147+
2148+
constructor(public popoverCtrl: PopoverController) { }
2149+
2150+
presentPopover(event: Event) {
2151+
let popover = this.popoverCtrl.create('PopoverPage');
2152+
popover.present({ ev: event });
2153+
}
2154+
}
2155+
`;
2156+
const result = util.purgeDeepLinkImport(input);
2157+
expect(result).toEqual(expectedText);
2158+
});
2159+
});
2160+
2161+
describe('purgeDeepLinkDecoratorTSTransform', () => {
2162+
it('should do something', () => {
2163+
const input = `
2164+
import { Component } from '@angular/core';
2165+
2166+
import { IonicPage } from 'ionic-angular';
2167+
2168+
@IonicPage()
2169+
@Component({
2170+
selector: 'page-about',
2171+
templateUrl: 'about.html'
2172+
})
2173+
export class AboutPage {
2174+
conferenceDate = '2047-05-17';
2175+
2176+
constructor(public popoverCtrl: PopoverController) { }
2177+
2178+
presentPopover(event: Event) {
2179+
let popover = this.popoverCtrl.create('PopoverPage');
2180+
popover.present({ ev: event });
2181+
}
2182+
}
2183+
`;
2184+
2185+
const expected = `import { Component } from "@angular/core";
2186+
import { } from "ionic-angular";
2187+
@Component({
2188+
selector: "page-about",
2189+
templateUrl: "about.html"
2190+
})
2191+
export class AboutPage {
2192+
conferenceDate = "2047-05-17";
2193+
constructor(public popoverCtrl: PopoverController) { }
2194+
presentPopover(event: Event) {
2195+
let popover = this.popoverCtrl.create("PopoverPage");
2196+
popover.present({ ev: event });
2197+
}
2198+
}
2199+
`;
2200+
const result = transformSourceFile(input, [util.purgeDeepLinkDecoratorTSTransformImpl]);
2201+
expect(result).toEqual(expected);
2202+
});
2203+
});
20642204
});
2205+
2206+
2207+
2208+
export function transformSourceFile(sourceText: string, transformers: ts.TransformerFactory<ts.SourceFile>[]) {
2209+
const transformed = ts.transform(ts.createSourceFile('source.ts', sourceText, ts.ScriptTarget.ES2015), transformers);
2210+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }, {
2211+
onEmitNode: transformed.emitNodeWithNotification,
2212+
substituteNode: transformed.substituteNode
2213+
});
2214+
const result = printer.printBundle(ts.createBundle(transformed.transformed));
2215+
transformed.dispose();
2216+
return result;
2217+
}

src/deep-linking/util.ts

Lines changed: 118 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ClassDeclaration,
77
createClassDeclaration,
88
createIdentifier,
9+
createNamedImports,
910
Decorator,
1011
Expression,
1112
Identifier,
@@ -24,6 +25,8 @@ import {
2425
TransformerFactory,
2526
updateCall,
2627
updateClassDeclaration,
28+
updateImportClause,
29+
updateImportDeclaration,
2730
updateSourceFile,
2831
visitEachChild,
2932
VisitResult
@@ -378,51 +381,87 @@ export class ${className}Module {}
378381
}
379382

380383
export function purgeDeepLinkDecoratorTSTransform(): TransformerFactory<SourceFile> {
381-
return (transformContext: TransformationContext) => {
384+
return purgeDeepLinkDecoratorTSTransformImpl;
385+
}
382386

383-
function visitClassDeclaration(classDeclaration: ClassDeclaration) {
384-
let hasDeepLinkDecorator = false;
385-
const diffDecorators: Decorator[] = [];
386-
for (const decorator of classDeclaration.decorators || []) {
387-
if (decorator.expression && (decorator.expression as CallExpression).expression
388-
&& ((decorator.expression as CallExpression).expression as Identifier).escapedText === DEEPLINK_DECORATOR_TEXT) {
389-
hasDeepLinkDecorator = true;
390-
} else {
391-
diffDecorators.push(decorator);
392-
}
387+
export function purgeDeepLinkDecoratorTSTransformImpl(transformContext: TransformationContext) {
388+
function visitClassDeclaration(classDeclaration: ClassDeclaration) {
389+
let hasDeepLinkDecorator = false;
390+
const diffDecorators: Decorator[] = [];
391+
for (const decorator of classDeclaration.decorators || []) {
392+
if (decorator.expression && (decorator.expression as CallExpression).expression
393+
&& ((decorator.expression as CallExpression).expression as Identifier).escapedText === DEEPLINK_DECORATOR_TEXT) {
394+
hasDeepLinkDecorator = true;
395+
} else {
396+
diffDecorators.push(decorator);
393397
}
398+
}
394399

395-
if (hasDeepLinkDecorator) {
396-
return updateClassDeclaration(
397-
classDeclaration,
398-
diffDecorators,
399-
classDeclaration.modifiers,
400-
classDeclaration.name,
401-
classDeclaration.typeParameters,
402-
classDeclaration.heritageClauses,
403-
classDeclaration.members
404-
);
405-
}
400+
if (hasDeepLinkDecorator) {
401+
return updateClassDeclaration(
402+
classDeclaration,
403+
diffDecorators,
404+
classDeclaration.modifiers,
405+
classDeclaration.name,
406+
classDeclaration.typeParameters,
407+
classDeclaration.heritageClauses,
408+
classDeclaration.members
409+
);
406410

407-
return classDeclaration;
408411
}
409412

410-
function visit(node: Node): VisitResult<Node> {
411-
switch (node.kind) {
413+
return classDeclaration;
414+
}
412415

413-
case SyntaxKind.ClassDeclaration:
414-
return visitClassDeclaration(node as ClassDeclaration);
416+
function visitImportDeclaration(importDeclaration: ImportDeclaration, sourceFile: SourceFile): ImportDeclaration {
415417

416-
default:
417-
return visitEachChild(node, (node) => {
418-
return visit(node);
419-
}, transformContext);
420-
}
418+
if (importDeclaration.moduleSpecifier
419+
&& (importDeclaration.moduleSpecifier as StringLiteral).text === 'ionic-angular'
420+
&& importDeclaration.importClause
421+
&& importDeclaration.importClause.namedBindings
422+
&& (importDeclaration.importClause.namedBindings as NamedImports).elements
423+
) {
424+
// loop over each import and store it
425+
const importSpecifiers: ImportSpecifier[] = [];
426+
(importDeclaration.importClause.namedBindings as NamedImports).elements.forEach((importSpecifier: ImportSpecifier) => {
427+
428+
if (importSpecifier.name.escapedText !== DEEPLINK_DECORATOR_TEXT) {
429+
importSpecifiers.push(importSpecifier);
430+
}
431+
});
432+
const emptyNamedImports = createNamedImports(importSpecifiers);
433+
const newImportClause = updateImportClause(importDeclaration.importClause, importDeclaration.importClause.name, emptyNamedImports);
434+
435+
return updateImportDeclaration(
436+
importDeclaration,
437+
importDeclaration.decorators,
438+
importDeclaration.modifiers,
439+
newImportClause,
440+
importDeclaration.moduleSpecifier
441+
);
421442
}
422443

423-
return (sourceFile: SourceFile) => {
424-
return visit(sourceFile) as SourceFile;
425-
};
444+
445+
return importDeclaration;
446+
}
447+
448+
function visit(node: Node, sourceFile: SourceFile): VisitResult<Node> {
449+
switch (node.kind) {
450+
451+
case SyntaxKind.ClassDeclaration:
452+
return visitClassDeclaration(node as ClassDeclaration);
453+
454+
case SyntaxKind.ImportDeclaration:
455+
return visitImportDeclaration(node as ImportDeclaration, sourceFile);
456+
default:
457+
return visitEachChild(node, (node) => {
458+
return visit(node, sourceFile);
459+
}, transformContext);
460+
}
461+
}
462+
463+
return (sourceFile: SourceFile) => {
464+
return visit(sourceFile, sourceFile) as SourceFile;
426465
};
427466
}
428467

@@ -442,9 +481,53 @@ export function purgeDeepLinkDecorator(inputText: string): string {
442481
toRemove.forEach(node => {
443482
toReturn = replaceNode('', inputText, node, '');
444483
});
484+
485+
toReturn = purgeDeepLinkImport(toReturn);
445486
return toReturn;
446487
}
447488

489+
export function purgeDeepLinkImport(inputText: string): string {
490+
const sourceFile = getTypescriptSourceFile('', inputText);
491+
const importDeclarations = findNodes(sourceFile, sourceFile, SyntaxKind.ImportDeclaration) as ImportDeclaration[];
492+
493+
importDeclarations.forEach(importDeclaration => {
494+
if (importDeclaration.moduleSpecifier
495+
&& (importDeclaration.moduleSpecifier as StringLiteral).text === 'ionic-angular'
496+
&& importDeclaration.importClause
497+
&& importDeclaration.importClause.namedBindings
498+
&& (importDeclaration.importClause.namedBindings as NamedImports).elements
499+
) {
500+
// loop over each import and store it
501+
let decoratorIsImported = false;
502+
const namedImportStrings: string[] = [];
503+
(importDeclaration.importClause.namedBindings as NamedImports).elements.forEach((importSpecifier: ImportSpecifier) => {
504+
505+
if (importSpecifier.name.escapedText === DEEPLINK_DECORATOR_TEXT) {
506+
decoratorIsImported = true;
507+
} else {
508+
namedImportStrings.push(importSpecifier.name.escapedText as string);
509+
}
510+
});
511+
512+
// okay, cool. If namedImportStrings is empty, then just remove the entire import statement
513+
// otherwise, just replace the named imports with the namedImportStrings separated by a comma
514+
if (decoratorIsImported) {
515+
if (namedImportStrings.length) {
516+
// okay cool, we only want to remove some of these homies
517+
const stringRepresentation = namedImportStrings.join(', ');
518+
const namedImportString = `{ ${stringRepresentation} }`;
519+
inputText = replaceNode('', inputText, importDeclaration.importClause.namedBindings, namedImportString);
520+
} else {
521+
// remove the entire import statement
522+
inputText = replaceNode('', inputText, importDeclaration, '');
523+
}
524+
}
525+
}
526+
});
527+
528+
return inputText;
529+
}
530+
448531
export function getInjectDeepLinkConfigTypescriptTransform() {
449532
const deepLinkString = convertDeepLinkConfigEntriesToString(getParsedDeepLinkConfig());
450533
const appNgModulePath = getStringPropertyValue(Constants.ENV_APP_NG_MODULE_PATH);

0 commit comments

Comments
 (0)