cleanup: clean up ast utils
This commit is contained in:
parent
2897cd2ba5
commit
da8395c36f
@ -6,217 +6,12 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import { Change, InsertChange } from './change';
|
import {Tree} from '@angular-devkit/schematics';
|
||||||
import { insertImport } from './route-utils';
|
import {getDecoratorMetadata} from '@schematics/angular/utility/ast-utils';
|
||||||
|
import {Change, InsertChange, NoopChange} from '@schematics/angular/utility/change';
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all nodes from the AST in the subtree of node of SyntaxKind kind.
|
|
||||||
* @param node
|
|
||||||
* @param kind
|
|
||||||
* @param max The maximum number of items to return.
|
|
||||||
* @return all nodes of kind, or [] if none is found
|
|
||||||
*/
|
|
||||||
export function findNodes(node: ts.Node, kind: ts.SyntaxKind, max = Infinity): ts.Node[] {
|
|
||||||
if (!node || max == 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const arr: ts.Node[] = [];
|
|
||||||
if (node.kind === kind) {
|
|
||||||
arr.push(node);
|
|
||||||
max--;
|
|
||||||
}
|
|
||||||
if (max > 0) {
|
|
||||||
for (const child of node.getChildren()) {
|
|
||||||
findNodes(child, kind, max).forEach(node => {
|
|
||||||
if (max > 0) {
|
|
||||||
arr.push(node);
|
|
||||||
}
|
|
||||||
max--;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (max <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all the nodes from a source.
|
|
||||||
* @param sourceFile The source file object.
|
|
||||||
* @returns {Observable<ts.Node>} An observable of all the nodes in the source.
|
|
||||||
*/
|
|
||||||
export function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {
|
|
||||||
const nodes: ts.Node[] = [sourceFile];
|
|
||||||
const result = [];
|
|
||||||
|
|
||||||
while (nodes.length > 0) {
|
|
||||||
const node = nodes.shift();
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
result.push(node);
|
|
||||||
if (node.getChildCount(sourceFile) >= 0) {
|
|
||||||
nodes.unshift(...node.getChildren());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper for sorting nodes.
|
|
||||||
* @return function to sort nodes in increasing order of position in sourceFile
|
|
||||||
*/
|
|
||||||
function nodesByPosition(first: ts.Node, second: ts.Node): number {
|
|
||||||
return first.pos - second.pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`
|
|
||||||
* or after the last of occurence of `syntaxKind` if the last occurence is a sub child
|
|
||||||
* of ts.SyntaxKind[nodes[i].kind] and save the changes in file.
|
|
||||||
*
|
|
||||||
* @param nodes insert after the last occurence of nodes
|
|
||||||
* @param toInsert string to insert
|
|
||||||
* @param file file to insert changes into
|
|
||||||
* @param fallbackPos position to insert if toInsert happens to be the first occurence
|
|
||||||
* @param syntaxKind the ts.SyntaxKind of the subchildren to insert after
|
|
||||||
* @return Change instance
|
|
||||||
* @throw Error if toInsert is first occurence but fall back is not set
|
|
||||||
*/
|
|
||||||
export function insertAfterLastOccurrence(nodes: ts.Node[],
|
|
||||||
toInsert: string,
|
|
||||||
file: string,
|
|
||||||
fallbackPos: number,
|
|
||||||
syntaxKind?: ts.SyntaxKind): Change {
|
|
||||||
let lastItem = nodes.sort(nodesByPosition).pop();
|
|
||||||
if (syntaxKind) {
|
|
||||||
lastItem = findNodes(lastItem !, syntaxKind).sort(nodesByPosition).pop();
|
|
||||||
}
|
|
||||||
if (!lastItem && fallbackPos == undefined) {
|
|
||||||
throw new Error(`tried to insert ${toInsert} as first occurence with no fallback position`);
|
|
||||||
}
|
|
||||||
const lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;
|
|
||||||
|
|
||||||
return new InsertChange(file, lastItemPosition, toInsert);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function getContentOfKeyLiteral(_source: ts.SourceFile, node: ts.Node): string | null {
|
|
||||||
if (node.kind == ts.SyntaxKind.Identifier) {
|
|
||||||
return (node as ts.Identifier).text;
|
|
||||||
} else if (node.kind == ts.SyntaxKind.StringLiteral) {
|
|
||||||
return (node as ts.StringLiteral).text;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function _angularImportsFromNode(node: ts.ImportDeclaration,
|
|
||||||
_sourceFile: ts.SourceFile): {[name: string]: string} {
|
|
||||||
const ms = node.moduleSpecifier;
|
|
||||||
let modulePath: string | null = null;
|
|
||||||
switch (ms.kind) {
|
|
||||||
case ts.SyntaxKind.StringLiteral:
|
|
||||||
modulePath = (ms as ts.StringLiteral).text;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modulePath.startsWith('@angular/')) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.importClause) {
|
|
||||||
if (node.importClause.name) {
|
|
||||||
// This is of the form `import Name from 'path'`. Ignore.
|
|
||||||
return {};
|
|
||||||
} else if (node.importClause.namedBindings) {
|
|
||||||
const nb = node.importClause.namedBindings;
|
|
||||||
if (nb.kind == ts.SyntaxKind.NamespaceImport) {
|
|
||||||
// This is of the form `import * as name from 'path'`. Return `name.`.
|
|
||||||
return {
|
|
||||||
[(nb as ts.NamespaceImport).name.text + '.']: modulePath,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// This is of the form `import {a,b,c} from 'path'`
|
|
||||||
const namedImports = nb as ts.NamedImports;
|
|
||||||
|
|
||||||
return namedImports.elements
|
|
||||||
.map((is: ts.ImportSpecifier) => is.propertyName ? is.propertyName.text : is.name.text)
|
|
||||||
.reduce((acc: {[name: string]: string}, curr: string) => {
|
|
||||||
acc[curr] = modulePath !;
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
} else {
|
|
||||||
// This is of the form `import 'path';`. Nothing to do.
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function getDecoratorMetadata(source: ts.SourceFile, identifier: string,
|
|
||||||
module: string): ts.Node[] {
|
|
||||||
const angularImports: {[name: string]: string}
|
|
||||||
= findNodes(source, ts.SyntaxKind.ImportDeclaration)
|
|
||||||
.map((node: ts.ImportDeclaration) => _angularImportsFromNode(node, source))
|
|
||||||
.reduce((acc: {[name: string]: string}, current: {[name: string]: string}) => {
|
|
||||||
for (const key of Object.keys(current)) {
|
|
||||||
acc[key] = current[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return getSourceNodes(source)
|
|
||||||
.filter(node => {
|
|
||||||
return node.kind == ts.SyntaxKind.Decorator
|
|
||||||
&& (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression;
|
|
||||||
})
|
|
||||||
.map(node => (node as ts.Decorator).expression as ts.CallExpression)
|
|
||||||
.filter(expr => {
|
|
||||||
if (expr.expression.kind == ts.SyntaxKind.Identifier) {
|
|
||||||
const id = expr.expression as ts.Identifier;
|
|
||||||
|
|
||||||
return id.getFullText(source) == identifier
|
|
||||||
&& angularImports[id.getFullText(source)] === module;
|
|
||||||
} else if (expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression) {
|
|
||||||
// This covers foo.NgModule when importing * as foo.
|
|
||||||
const paExpr = expr.expression as ts.PropertyAccessExpression;
|
|
||||||
// If the left expression is not an identifier, just give up at that point.
|
|
||||||
if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = paExpr.name.text;
|
|
||||||
const moduleId = (paExpr.expression as ts.Identifier).getText(source);
|
|
||||||
|
|
||||||
return id === identifier && (angularImports[moduleId + '.'] === module);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
.filter(expr => expr.arguments[0]
|
|
||||||
&& expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression)
|
|
||||||
.map(expr => expr.arguments[0] as ts.ObjectLiteralExpression);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
// This should be moved to @schematics/angular once it allows to pass custom expressions as providers
|
||||||
function _addSymbolToNgModuleMetadata(source: ts.SourceFile,
|
function _addSymbolToNgModuleMetadata(source: ts.SourceFile,
|
||||||
ngModulePath: string, metadataField: string,
|
ngModulePath: string, metadataField: string,
|
||||||
expression: string): Change[] {
|
expression: string): Change[] {
|
||||||
@ -351,3 +146,18 @@ export function addProviderToModule(source: ts.SourceFile,
|
|||||||
modulePath: string, symbolName: string): Change[] {
|
modulePath: string, symbolName: string): Change[] {
|
||||||
return _addSymbolToNgModuleMetadata(source, modulePath, 'providers', symbolName);
|
return _addSymbolToNgModuleMetadata(source, modulePath, 'providers', symbolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function insert(host: Tree, modulePath: string, changes: Change[]) {
|
||||||
|
const recorder = host.beginUpdate(modulePath);
|
||||||
|
for (const change of changes) {
|
||||||
|
if (change instanceof InsertChange) {
|
||||||
|
recorder.insertLeft(change.pos, change.toAdd);
|
||||||
|
} else if (change instanceof NoopChange) {
|
||||||
|
// do nothing
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unexpected Change '${change}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
host.commitUpdate(recorder);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,127 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. 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
|
|
||||||
*/
|
|
||||||
export interface Host {
|
|
||||||
write(path: string, content: string): Promise<void>;
|
|
||||||
read(path: string): Promise<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface Change {
|
|
||||||
apply(host: Host): Promise<void>;
|
|
||||||
|
|
||||||
// The file this change should be applied to. Some changes might not apply to
|
|
||||||
// a file (maybe the config).
|
|
||||||
readonly path: string | null;
|
|
||||||
|
|
||||||
// The order this change should be applied. Normally the position inside the file.
|
|
||||||
// Changes are applied from the bottom of a file to the top.
|
|
||||||
readonly order: number;
|
|
||||||
|
|
||||||
// The description of this change. This will be outputted in a dry or verbose run.
|
|
||||||
readonly description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An operation that does nothing.
|
|
||||||
*/
|
|
||||||
export class NoopChange implements Change {
|
|
||||||
description = 'No operation.';
|
|
||||||
order = Infinity;
|
|
||||||
path = null;
|
|
||||||
apply() { return Promise.resolve(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will add text to the source code.
|
|
||||||
*/
|
|
||||||
export class InsertChange implements Change {
|
|
||||||
|
|
||||||
order: number;
|
|
||||||
description: string;
|
|
||||||
|
|
||||||
constructor(public path: string, public pos: number, public toAdd: string) {
|
|
||||||
if (pos < 0) {
|
|
||||||
throw new Error('Negative positions are invalid');
|
|
||||||
}
|
|
||||||
this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;
|
|
||||||
this.order = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method does not insert spaces if there is none in the original string.
|
|
||||||
*/
|
|
||||||
apply(host: Host) {
|
|
||||||
return host.read(this.path).then(content => {
|
|
||||||
const prefix = content.substring(0, this.pos);
|
|
||||||
const suffix = content.substring(this.pos);
|
|
||||||
|
|
||||||
return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will remove text from the source code.
|
|
||||||
*/
|
|
||||||
export class RemoveChange implements Change {
|
|
||||||
|
|
||||||
order: number;
|
|
||||||
description: string;
|
|
||||||
|
|
||||||
constructor(public path: string, private pos: number, private toRemove: string) {
|
|
||||||
if (pos < 0) {
|
|
||||||
throw new Error('Negative positions are invalid');
|
|
||||||
}
|
|
||||||
this.description = `Removed ${toRemove} into position ${pos} of ${path}`;
|
|
||||||
this.order = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(host: Host): Promise<void> {
|
|
||||||
return host.read(this.path).then(content => {
|
|
||||||
const prefix = content.substring(0, this.pos);
|
|
||||||
const suffix = content.substring(this.pos + this.toRemove.length);
|
|
||||||
|
|
||||||
// TODO: throw error if toRemove doesn't match removed string.
|
|
||||||
return host.write(this.path, `${prefix}${suffix}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will replace text from the source code.
|
|
||||||
*/
|
|
||||||
export class ReplaceChange implements Change {
|
|
||||||
order: number;
|
|
||||||
description: string;
|
|
||||||
|
|
||||||
constructor(public path: string, private pos: number, private oldText: string,
|
|
||||||
private newText: string) {
|
|
||||||
if (pos < 0) {
|
|
||||||
throw new Error('Negative positions are invalid');
|
|
||||||
}
|
|
||||||
this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;
|
|
||||||
this.order = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(host: Host): Promise<void> {
|
|
||||||
return host.read(this.path).then(content => {
|
|
||||||
const prefix = content.substring(0, this.pos);
|
|
||||||
const suffix = content.substring(this.pos + this.oldText.length);
|
|
||||||
const text = content.substring(this.pos, this.pos + this.oldText.length);
|
|
||||||
|
|
||||||
if (text !== this.oldText) {
|
|
||||||
return Promise.reject(new Error(`Invalid replace: "${text}" != "${this.oldText}".`));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: throw error if oldText doesn't match removed string.
|
|
||||||
return host.write(this.path, `${prefix}${this.newText}${suffix}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. 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, normalizePath } from '@angular-devkit/schematics';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to find the "closest" module to a generated file's path.
|
|
||||||
*/
|
|
||||||
export function findModule(host: Tree, generateDir: string): string {
|
|
||||||
let closestModule = generateDir;
|
|
||||||
const allFiles = host.files;
|
|
||||||
|
|
||||||
let modulePath: string | null = null;
|
|
||||||
const moduleRe = /\.module\.ts$/;
|
|
||||||
while (closestModule) {
|
|
||||||
const normalizedRoot = normalizePath(closestModule);
|
|
||||||
const matches = allFiles.filter(p => moduleRe.test(p) && p.startsWith(normalizedRoot));
|
|
||||||
|
|
||||||
if (matches.length == 1) {
|
|
||||||
modulePath = matches[0];
|
|
||||||
break;
|
|
||||||
} else if (matches.length > 1) {
|
|
||||||
throw new Error('More than one module matches. Use skip-import option to skip importing '
|
|
||||||
+ 'the component into the closest module.');
|
|
||||||
}
|
|
||||||
closestModule = closestModule.split('/').slice(0, -1).join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modulePath) {
|
|
||||||
throw new Error('Could not find an NgModule for the new component. Use the skip-import '
|
|
||||||
+ 'option to skip importing components in NgModule.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return modulePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a relative path from one file path to another file path.
|
|
||||||
*/
|
|
||||||
export function buildRelativePath(from: string, to: string) {
|
|
||||||
// Convert to arrays.
|
|
||||||
const fromParts = from.split('/');
|
|
||||||
const toParts = to.split('/');
|
|
||||||
|
|
||||||
// Remove file names (preserving destination)
|
|
||||||
fromParts.pop();
|
|
||||||
const toFileName = toParts.pop();
|
|
||||||
|
|
||||||
const relativePath = path.relative(fromParts.join('/'), toParts.join('/'));
|
|
||||||
let pathPrefix = '';
|
|
||||||
|
|
||||||
// Set the path prefix for same dir or child dir, parent dir starts with `..`
|
|
||||||
if (!relativePath) {
|
|
||||||
pathPrefix = '.';
|
|
||||||
} else if (!relativePath.startsWith('.')) {
|
|
||||||
pathPrefix = `./`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${pathPrefix}${relativePath}/${toFileName}`;
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. 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 * as ts from 'typescript';
|
|
||||||
import { findNodes, insertAfterLastOccurrence } from './ast-utils';
|
|
||||||
import { Change, NoopChange } from './change';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add Import `import { symbolName } from fileName` if the import doesn't exit
|
|
||||||
* already. Assumes fileToEdit can be resolved and accessed.
|
|
||||||
* @param fileToEdit (file we want to add import to)
|
|
||||||
* @param symbolName (item to import)
|
|
||||||
* @param fileName (path to the file)
|
|
||||||
* @param isDefault (if true, import follows style for importing default exports)
|
|
||||||
* @return Change
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function insertImport(source: ts.SourceFile, fileToEdit: string, symbolName: string,
|
|
||||||
fileName: string, isDefault = false): Change {
|
|
||||||
const rootNode = source;
|
|
||||||
const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);
|
|
||||||
|
|
||||||
// get nodes that map to import statements from the file fileName
|
|
||||||
const relevantImports = allImports.filter(node => {
|
|
||||||
// StringLiteral of the ImportDeclaration is the import file (fileName in this case).
|
|
||||||
const importFiles = node.getChildren()
|
|
||||||
.filter(child => child.kind === ts.SyntaxKind.StringLiteral)
|
|
||||||
.map(n => (n as ts.StringLiteral).text);
|
|
||||||
|
|
||||||
return importFiles.filter(file => file === fileName).length === 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (relevantImports.length > 0) {
|
|
||||||
let importsAsterisk = false;
|
|
||||||
// imports from import file
|
|
||||||
const imports: ts.Node[] = [];
|
|
||||||
relevantImports.forEach(n => {
|
|
||||||
Array.prototype.push.apply(imports, findNodes(n, ts.SyntaxKind.Identifier));
|
|
||||||
if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {
|
|
||||||
importsAsterisk = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// if imports * from fileName, don't add symbolName
|
|
||||||
if (importsAsterisk) {
|
|
||||||
return new NoopChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
const importTextNodes = imports.filter(n => (n as ts.Identifier).text === symbolName);
|
|
||||||
|
|
||||||
// insert import if it's not there
|
|
||||||
if (importTextNodes.length === 0) {
|
|
||||||
const fallbackPos = findNodes(relevantImports[0], ts.SyntaxKind.CloseBraceToken)[0].pos ||
|
|
||||||
findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].pos;
|
|
||||||
|
|
||||||
return insertAfterLastOccurrence(imports, `, ${symbolName}`, fileToEdit, fallbackPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NoopChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
// no such import declaration exists
|
|
||||||
const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral)
|
|
||||||
.filter((n: ts.StringLiteral) => n.text === 'use strict');
|
|
||||||
let fallbackPos = 0;
|
|
||||||
if (useStrict.length > 0) {
|
|
||||||
fallbackPos = useStrict[0].end;
|
|
||||||
}
|
|
||||||
const open = isDefault ? '' : '{ ';
|
|
||||||
const close = isDefault ? '' : ' }';
|
|
||||||
// if there are no imports or 'use strict' statement, insert import at beginning of file
|
|
||||||
const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;
|
|
||||||
const separator = insertAtBeginning ? '' : ';\n';
|
|
||||||
const toInsert = `${separator}import ${open}${symbolName}${close}` +
|
|
||||||
` from '${fileName}'${insertAtBeginning ? ';\n' : ''}`;
|
|
||||||
|
|
||||||
return insertAfterLastOccurrence(
|
|
||||||
allImports,
|
|
||||||
toInsert,
|
|
||||||
fileToEdit,
|
|
||||||
fallbackPos,
|
|
||||||
ts.SyntaxKind.StringLiteral,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user