Skip to content

Commit

Permalink
fix(entries): limit component references to call expressions and html
Browse files Browse the repository at this point in the history
Closes #532
  • Loading branch information
adamdbradley committed Feb 14, 2018
1 parent 39dcfc6 commit 7991017
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 51 deletions.
2 changes: 1 addition & 1 deletion src/compiler/build/build-utils.ts
Expand Up @@ -22,7 +22,7 @@ export function getBuildContext(config: Config, compilerCtx: CompilerCtx, watche
const buildCtx: BuildCtx = {
requiresFullBuild: requiresFullBuild,
buildId: compilerCtx.activeBuildId,
sourceStrings: [],
componentRefs: [],
moduleGraphs: [],
diagnostics: [],
entryPoints: [],
Expand Down
8 changes: 4 additions & 4 deletions src/compiler/entries/component-dependencies.ts
@@ -1,4 +1,4 @@
import { CompilerCtx, ComponentMeta, ComponentReference, ModuleFiles, ModuleGraph, SourceString } from '../../declarations';
import { CompilerCtx, ComponentMeta, ComponentRef, ModuleFiles, ModuleGraph, PotentialComponentRef } from '../../declarations';
import { getComponentRefsFromSourceStrings } from './component-references';


Expand All @@ -23,7 +23,7 @@ export function calcModuleGraphImportPaths(compilerCtx: CompilerCtx, moduleGraph
const SRC_EXTS = ['.tsx', '.ts', '.js'];


export function calcComponentDependencies(allModuleFiles: ModuleFiles, moduleGraphs: ModuleGraph[], sourceStrings: SourceString[]) {
export function calcComponentDependencies(allModuleFiles: ModuleFiles, moduleGraphs: ModuleGraph[], sourceStrings: PotentialComponentRef[]) {
// figure out all the component references seen in each file
const componentRefs = getComponentRefsFromSourceStrings(allModuleFiles, sourceStrings);

Expand All @@ -36,7 +36,7 @@ export function calcComponentDependencies(allModuleFiles: ModuleFiles, moduleGra
}


function getComponentDependencies(moduleGraphs: ModuleGraph[], componentRefs: ComponentReference[], filePath: string, cmpMeta: ComponentMeta) {
function getComponentDependencies(moduleGraphs: ModuleGraph[], componentRefs: ComponentRef[], filePath: string, cmpMeta: ComponentMeta) {
// we may have already figured out some dependencies (collections aready have this info)
cmpMeta.dependencies = cmpMeta.dependencies || [];

Expand All @@ -59,7 +59,7 @@ function getComponentDependencies(moduleGraphs: ModuleGraph[], componentRefs: Co
}


function getComponentDepsFromImports(moduleGraphs: ModuleGraph[], componentRefs: ComponentReference[], importsInspected: string[], moduleGraph: ModuleGraph, cmpMeta: ComponentMeta) {
function getComponentDepsFromImports(moduleGraphs: ModuleGraph[], componentRefs: ComponentRef[], importsInspected: string[], moduleGraph: ModuleGraph, cmpMeta: ComponentMeta) {
moduleGraph.importPaths.forEach(importPath => {
if (importsInspected.includes(importPath)) {
return;
Expand Down
43 changes: 19 additions & 24 deletions src/compiler/entries/component-references.ts
@@ -1,43 +1,41 @@
import { ComponentReference, ModuleFiles, SourceString } from '../../declarations';
import { ComponentRef, ModuleFiles, PotentialComponentRef } from '../../declarations';


export function getComponentRefsFromSourceStrings(allModuleFiles: ModuleFiles, sourceStrings: SourceString[]) {
const componentRefs: ComponentReference[] = [];
export function getComponentRefsFromSourceStrings(allModuleFiles: ModuleFiles, sourceStrings: PotentialComponentRef[]) {
const componentRefs: ComponentRef[] = [];

const tags = Object.keys(allModuleFiles)
.map(filePath => allModuleFiles[filePath].cmpMeta)
.filter(cmpMeta => cmpMeta && cmpMeta.tagNameMeta)
.map(cmpMeta => cmpMeta.tagNameMeta);

sourceStrings.forEach(src => {
if (typeof src.str !== 'string') {
return;
}

src.str = src.str.trim().toLowerCase();
if (typeof src.tag === 'string') {
src.tag = src.tag.toLowerCase();

if (tags.some(tag => src.str === tag)) {
// exact match, we're good
// probably something like h('ion-button') or
// var tag = 'ion-button'; document.createElement(tag);
componentRefs.push({
tag: src.str,
filePath: src.filePath
});
if (tags.some(tag => src.tag === tag)) {
// exact match, we're good
// probably something like h('ion-button') or
// document.createElement('ion-toggle');
componentRefs.push({
tag: src.tag,
filePath: src.filePath
});
}

} else if (src.str.includes('<')) {
} else if (typeof src.html === 'string') {
// string could be HTML
// could be something like elm.innerHTML = '<ion-button>';

// replace any whitespace with a ~ character
// this is especially important for newlines and tabs
// for tag with attributes and has a newline in the tag
src.str = src.str.toLowerCase().replace(/\s/g, '~');
src.html = src.html.toLowerCase().replace(/\s/g, '~');

const foundTags = tags.filter(tag => {
return src.str.includes('<' + tag + '>') ||
src.str.includes('</' + tag + '>') ||
src.str.includes('<' + tag + '~');
return src.html.includes('<' + tag + '>') ||
src.html.includes('</' + tag + '>') ||
src.html.includes('<' + tag + '~');
});

foundTags.forEach(foundTag => {
Expand All @@ -47,9 +45,6 @@ export function getComponentRefsFromSourceStrings(allModuleFiles: ModuleFiles, s
});
});
}

// just to free up memory
src.str = src.filePath = null;
});

sourceStrings.length = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/entries/entry-modules.ts
Expand Up @@ -14,7 +14,7 @@ export function generateEntryModules(config: Config, compilerCtx: CompilerCtx, b
calcModuleGraphImportPaths(compilerCtx, buildCtx.moduleGraphs);

// figure out how modules and components connect
calcComponentDependencies(compilerCtx.moduleFiles, buildCtx.moduleGraphs, buildCtx.sourceStrings);
calcComponentDependencies(compilerCtx.moduleFiles, buildCtx.moduleGraphs, buildCtx.componentRefs);

try {
const allModules = Object.keys(compilerCtx.moduleFiles).map(filePath => compilerCtx.moduleFiles[filePath]);
Expand Down
15 changes: 8 additions & 7 deletions src/compiler/transpile/test/transpile.spec.ts
Expand Up @@ -292,18 +292,19 @@ describe('transpile', () => {
c.config.bundles = [ { components: ['cmp-a'] } ];
await c.fs.writeFiles({
'/src/new-dir/cmp-b.tsx': `@Component({ tag: 'cmp-b' }) export class CmpB {}`,
'/src/new-dir/cmp-c.tsx': `@Component({ tag: 'cmp-c' }) export class CmpC {}`
'/src/new-dir/cmp-c.tsx': `@Component({ tag: 'cmp-c' }) export class CmpC {}`,
'/src/new-dir/no-find.tsx': `@Component({ tag: 'no-find' }) export class NoFind {}`
}, { clearFileCache: true });

await c.fs.writeFile('/src/cmp-a.tsx', `
@Component({ tag: 'cmp-a' }) export class CmpA {
render() {
someFunction('no-find');
if (true) {
return (
h('cmp-b')
);
}
return (
h('cmp-c')
);
Expand All @@ -324,17 +325,17 @@ describe('transpile', () => {
await c.fs.writeFiles({
'/src/new-dir/cmp-b.tsx': `@Component({ tag: 'cmp-b' }) export class CmpB {}`,
'/src/new-dir/cmp-c.tsx': `@Component({ tag: 'cmp-c' }) export class CmpC {}`,
'/src/new-dir/cmp-d.tsx': `@Component({ tag: 'cmp-d' }) export class CmpD {}`
'/src/new-dir/no-find.tsx': `@Component({ tag: 'no-find' }) export class NoFind {}`
}, { clearFileCache: true });

await c.fs.writeFile('/src/cmp-a.tsx', `
@Component({ tag: 'cmp-a' }) export class CmpA {
constructor() {
document.createElement('cmp-b');
var doc = document;
doc.createElementNS('cmp-c');
var tag = 'cmp-d';
document.createElement(tag);
doc.createElementNS('cMp-C');
document.createElement(' no-find ');
doc.someFunction('no-find');
}
}
`, { clearFileCache: true });
Expand All @@ -344,7 +345,7 @@ describe('transpile', () => {
expect(r.diagnostics).toEqual([]);

expect(r.components[0].tag).toBe('cmp-a');
expect(r.components[0].dependencies).toEqual(['cmp-b', 'cmp-c', 'cmp-d']);
expect(r.components[0].dependencies).toEqual(['cmp-b', 'cmp-c']);
});

it('get component dependencies from html string literals', async () => {
Expand Down
75 changes: 65 additions & 10 deletions src/compiler/transpile/transformers/component-dependencies.ts
@@ -1,5 +1,6 @@
import { BuildCtx, CompilerCtx, SourceString } from '../../../declarations';
import { BuildCtx, CompilerCtx, PotentialComponentRef } from '../../../declarations';
import { MEMBER_TYPE } from '../../../util/constants';
import { normalizePath } from '../../util';
import * as ts from 'typescript';


Expand All @@ -8,11 +9,11 @@ export function componentDependencies(compilerCtx: CompilerCtx, buildCtx: BuildC
return (transformContext) => {

function visit(node: ts.Node, filePath: string): ts.VisitResult<ts.Node> {
if (node.kind === ts.SyntaxKind.StringLiteral) {
buildCtx.sourceStrings.push({
str: (node as ts.StringLiteral).text,
filePath: filePath
});
if (node.kind === ts.SyntaxKind.CallExpression) {
callExpression(buildCtx, filePath, node as ts.CallExpression);

} else if (node.kind === ts.SyntaxKind.StringLiteral) {
stringLiteral(buildCtx, filePath, node as ts.StringLiteral);
}

return ts.visitEachChild(node, (node) => {
Expand All @@ -21,15 +22,17 @@ export function componentDependencies(compilerCtx: CompilerCtx, buildCtx: BuildC
}

return (tsSourceFile) => {
addPropConnects(compilerCtx, buildCtx.sourceStrings, tsSourceFile.fileName);
const filePath = normalizePath(tsSourceFile.fileName);

addPropConnects(compilerCtx, buildCtx.componentRefs, filePath);

return visit(tsSourceFile, tsSourceFile.fileName) as ts.SourceFile;
return visit(tsSourceFile, filePath) as ts.SourceFile;
};
};
}


function addPropConnects(compilerCtx: CompilerCtx, sourceStrings: SourceString[], filePath: string) {
function addPropConnects(compilerCtx: CompilerCtx, sourceStrings: PotentialComponentRef[], filePath: string) {
const moduleFile = compilerCtx.moduleFiles[filePath];

const cmpMeta = (moduleFile && moduleFile.cmpMeta);
Expand All @@ -42,10 +45,62 @@ function addPropConnects(compilerCtx: CompilerCtx, sourceStrings: SourceString[]
const memberMeta = cmpMeta.membersMeta[memberName];
if (memberMeta.memberType === MEMBER_TYPE.PropConnect) {
sourceStrings.push({
str: memberMeta.ctrlId,
tag: memberMeta.ctrlId,
filePath: filePath
});
}
});
}
}


function callExpression(buildCtx: BuildCtx, filePath: string, node: ts.CallExpression) {
if (node.arguments && node.arguments[0]) {

if (node.expression.kind === ts.SyntaxKind.Identifier) {
// h('tag')
callExpressionArg(buildCtx, filePath, node.expression as ts.Identifier, node.arguments);

} else if (node.expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
// document.createElement('tag')
if ((node.expression as ts.PropertyAccessExpression).name) {
// const
callExpressionArg(buildCtx, filePath, (node.expression as ts.PropertyAccessExpression).name as ts.Identifier, node.arguments);
}
}
}
}


function callExpressionArg(buildCtx: BuildCtx, filePath: string, callExpressionName: ts.Identifier, args: ts.NodeArray<ts.Expression>) {
if (TAG_CALL_EXPRESSIONS.includes(callExpressionName.escapedText as string)) {

if (args[0].kind === ts.SyntaxKind.StringLiteral) {
const tag = (args[0] as ts.StringLiteral).text;

if (typeof tag === 'string') {
buildCtx.componentRefs.push({
tag: tag,
filePath: filePath
});
}
}
}
}


function stringLiteral(buildCtx: BuildCtx, filePath: string, node: ts.StringLiteral) {
if (typeof node.text === 'string' && node.text.includes('<')) {
buildCtx.componentRefs.push({
html: node.text,
filePath: filePath
});
}
}


const TAG_CALL_EXPRESSIONS = [
'h',
'createElement',
'createElementNS'
];
2 changes: 1 addition & 1 deletion src/declarations/build.ts
Expand Up @@ -3,7 +3,7 @@ import * as d from './index';

export interface BuildCtx {
graphData?: GraphData;
sourceStrings?: d.SourceString[];
componentRefs?: d.PotentialComponentRef[];
moduleGraphs?: d.ModuleGraph[];
buildId: number;
requiresFullBuild: boolean;
Expand Down
7 changes: 4 additions & 3 deletions src/declarations/entry.ts
Expand Up @@ -28,13 +28,14 @@ export interface EntryComponent {
dependencyOf?: string[];
}

export interface ComponentReference {
export interface ComponentRef {
tag: string;
filePath: string;
}

export interface SourceString {
str: string;
export interface PotentialComponentRef {
tag?: string;
html?: string;
filePath: string;
}

Expand Down

0 comments on commit 7991017

Please sign in to comment.