Skip to content

Commit

Permalink
feat: support import & += operator for string
Browse files Browse the repository at this point in the history
  • Loading branch information
cxtom committed Mar 3, 2019
1 parent 12633a5 commit edda661
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 144 deletions.
232 changes: 143 additions & 89 deletions src/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import {
shouldAddDollar,
shouldUseArray,
shouldAddDoubleQuote,
isStringLike
isStringLike,
isClassLike,
isFunctionLike
} from './utilities/nodeTest';

import {
Expand Down Expand Up @@ -352,18 +354,17 @@ export function emitFile(sourceFile: SourceFile, state: CompilerState) {
// return emitImportEqualsDeclaration(<ImportEqualsDeclaration>node);
case SyntaxKind.ImportDeclaration:
return emitImportDeclaration(<ts.ImportDeclaration>node);
// case SyntaxKind.ImportClause:
// return emitImportClause(<ts.ImportClause>node);
case SyntaxKind.ImportClause:
return emitImportClause(<ts.ImportClause>node);
// case SyntaxKind.NamespaceImport:
// return emitNamespaceImport(<NamespaceImport>node);
// case SyntaxKind.NamedImports:
// return emitNamedImports(<ts.NamedImports>node);
// case SyntaxKind.ImportSpecifier:
// return emitImportSpecifier(<ImportSpecifier>node);
// return emitNamespaceImport(<ts.NamespaceImport>node);
case SyntaxKind.NamedImports:
return emitNamedImports(<ts.NamedImports>node);
case SyntaxKind.ImportSpecifier:
return emitImportSpecifier(<ts.ImportSpecifier>node);
// case SyntaxKind.ExportAssignment:
// return emitExportAssignment(<ts.ExportAssignment>node);
// case SyntaxKind.ExportDeclaration:
// console.log(node);
// return emitExportDeclaration(<ts.ExportDeclaration>node);
// case SyntaxKind.NamedExports:
// return emitNamedExports(<NamedExports>node);
Expand Down Expand Up @@ -1094,10 +1095,27 @@ export function emitFile(sourceFile: SourceFile, state: CompilerState) {
}

function emitPropertyAccessExpression(node: ts.PropertyAccessExpression) {
const symbol = typeChecker.getSymbolAtLocation(node.name);
let prefix = '["';
let suffix = '"]';
if (isClassLike(node.expression, typeChecker) && symbol) {
switch (symbol.getFlags()) {
case ts.SymbolFlags.Method:
prefix = '::';
suffix = '';
break;
case ts.SymbolFlags.Property:
prefix = '::$';
suffix = '';
break;
default:
break;
}
}
emitWithHint(ts.EmitHint.Expression, node.expression);
writePunctuation("[\"");
writePunctuation(prefix);
emitWithHint(ts.EmitHint.Unspecified, node.name);
writePunctuation("\"]");
writePunctuation(suffix);
}

// // 1..toString is a valid property access, emit a dot after the literal
Expand Down Expand Up @@ -1253,7 +1271,12 @@ export function emitFile(sourceFile: SourceFile, state: CompilerState) {
function emitBinaryExpression(node: ts.BinaryExpression) {
emitWithHint(ts.EmitHint.Expression, node.left);
writeSpace();
writeTokenNode(node.operatorToken, writeOperator, node.left, node.right);
if (isStringLike(node.left, typeChecker) || isStringLike(node.right, typeChecker)) {
writePunctuation(".=");
}
else {
writeTokenNode(node.operatorToken, writeOperator, node.left, node.right);
}
writeSpace();
emitWithHint(ts.EmitHint.Expression, node.right);
}
Expand Down Expand Up @@ -1868,67 +1891,88 @@ export function emitFile(sourceFile: SourceFile, state: CompilerState) {

function emitImportDeclaration(node: ts.ImportDeclaration) {

// 暂时先不支持模块化,只允许用户引入指定库,方便在开发时有代码补全提示
if (!node.moduleSpecifier || !node.moduleSpecifier.getText) {
state.errors.push({
code: 100,
msg: '模块引入出错'
});
return;
// emitModifiers(node, node.modifiers);
// emitTokenWithComment(SyntaxKind.ImportKeyword, node.modifiers ? node.modifiers.end : node.pos, writeKeyword, node);
// writeSpace();

const moduleName = getImportModuleName(node);
const moduleIt = state.modules[moduleName];

if (moduleIt && !moduleIt.required) {
writeBase(`require_once("${moduleIt.path ? moduleIt.path : state.getModulePath(moduleName)}")`);
writeSemicolon();
writeLine();
moduleIt.required = true;
}

const allowedModules = state.modules;
const importModuleName = getImportModuleName(node);
if (!allowedModules[importModuleName]) {
state.errors.push({
code: 100,
msg: `模块${importModuleName}未找到`
});
return;
if (node.importClause) {
emit(node.importClause);
}

const moduleIt = allowedModules[importModuleName];
// emitExpression(node.moduleSpecifier);
// writeSemicolon();

// 需要将引入的变量跟 module 对应起来
if (node.importClause && node.importClause.namedBindings) {
node.importClause.namedBindings.forEachChild(child => {
const name = getTextOfNode(child);
state.moduleNamedImports[name] = {
className: moduleIt.className,
moduleName: importModuleName
};
});
}
// // 暂时先不支持模块化,只允许用户引入指定库,方便在开发时有代码补全提示
// if (!node.moduleSpecifier || !node.moduleSpecifier.getText) {
// state.errors.push({
// code: 100,
// msg: '模块引入出错'
// });
// return;
// }

if (moduleIt.required) {
return;
}
// const allowedModules = state.modules;
// const importModuleName = getImportModuleName(node);
// if (!allowedModules[importModuleName]) {
// state.errors.push({
// code: 100,
// msg: `模块${importModuleName}未找到`
// });
// return;
// }

if (moduleIt.path) {
writeBase(`require_once("${moduleIt.path}");`);
writeLine();
}
// const moduleIt = allowedModules[importModuleName];

// // 需要将引入的变量跟 module 对应起来
// if (node.importClause && node.importClause.namedBindings) {
// node.importClause.namedBindings.forEachChild(child => {
// const name = getTextOfNode(child);
// state.moduleNamedImports[name] = {
// className: moduleIt.className,
// moduleName: importModuleName
// };
// });
// }

if (node.importClause && node.importClause.name) {
let text = getTextOfNode(node.importClause.name);
state.moduleDefaultImports[text] = {
className: moduleIt.className,
moduleName: importModuleName
};
}
// if (moduleIt.required) {
// return;
// }

// if (moduleIt.path) {
// writeBase(`require_once("${moduleIt.path}");`);
// writeLine();
// }

// if (node.importClause && node.importClause.name) {
// let text = getTextOfNode(node.importClause.name);
// state.moduleDefaultImports[text] = {
// className: moduleIt.className,
// moduleName: importModuleName
// };
// }

moduleIt.required = true;
// moduleIt.required = true;

}

// function emitImportClause(node: ts.ImportClause) {
// // emit(node.name);
// // if (node.name && node.namedBindings) {
// // emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node);
// // writeSpace();
// // }
// emit(node.namedBindings);
// }
function emitImportClause(node: ts.ImportClause) {
emit(node.name);
if (node.name && node.namedBindings) {
emitTokenWithComment(SyntaxKind.CommaToken, node.name.end, writePunctuation, node);
writeSpace();
}
emit(node.namedBindings);
}

// function emitNamespaceImport(node: NamespaceImport) {
// const asPos = emitTokenWithComment(SyntaxKind.AsteriskToken, node.pos, writePunctuation, node);
Expand All @@ -1938,17 +1982,13 @@ export function emitFile(sourceFile: SourceFile, state: CompilerState) {
// emit(node.name);
// }

// function emitNamedImports(node: ts.NamedImports) {
// node.forEachChild(child => {
// const name = getTextOfNode(child);
// const moduleName = getImportModuleName(node.parent.parent);
// varModuleMap[name] = moduleName;
// });
// }
function emitNamedImports(node: ts.NamedImports) {
emitNamedImportsOrExports(node);
}

// function emitImportSpecifier(node: ImportSpecifier) {
// emitImportOrExportSpecifier(node);
// }
function emitImportSpecifier(node: ts.ImportSpecifier) {
emitImportOrExportSpecifier(node);
}

// function emitExportAssignment(node: ExportAssignment) {
// const nextPos = emitTokenWithComment(SyntaxKind.ExportKeyword, node.pos, writeKeyword, node);
Expand Down Expand Up @@ -2002,22 +2042,36 @@ export function emitFile(sourceFile: SourceFile, state: CompilerState) {
// emitImportOrExportSpecifier(node);
// }

// function emitNamedImportsOrExports(node: ts.NamedImportsOrExports) {
// writePunctuation("{");
// emitList(node, node.elements, ts.ListFormat.NamedImportsOrExportsElements);
// writePunctuation("}");
// }

// function emitImportOrExportSpecifier(node: ImportOrExportSpecifier) {
// if (node.propertyName) {
// emit(node.propertyName);
// writeSpace();
// emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node);
// writeSpace();
// }
function emitNamedImportsOrExports(node: ts.NamedImportsOrExports) {
const importNode = node.parent.parent;

// emit(node.name);
// }
if (ts.isImportDeclaration(importNode)) {
const moduleName = getImportModuleName(importNode);
node.forEachChild(element => {
if (isClassLike(element, typeChecker) || isFunctionLike(element, typeChecker)) {
writePunctuation("use");
writeSpace();
const namespace = state.modules[moduleName] && state.modules[moduleName].namespace;
namespace && writeBase(namespace);
emit(element);
writeSemicolon();
writeLine();
}
});
}
}

function emitImportOrExportSpecifier(node: ts.ImportOrExportSpecifier) {
if (node.propertyName) {
emit(node.propertyName);
writeSpace();
emitTokenWithComment(SyntaxKind.AsKeyword, node.propertyName.end, writeKeyword, node);
writeSpace();
}

emit(node.name);
}

// //
// // Module references
Expand Down Expand Up @@ -3085,10 +3139,10 @@ export function emitFile(sourceFile: SourceFile, state: CompilerState) {
}

// 是从模块中引入的
if (state.moduleNamedImports[name]) {
const className = state.moduleNamedImports[name].className;
head = className + '::' + head;
}
// if (state.moduleNamedImports[name]) {
// const className = state.moduleNamedImports[name].className;
// head = className + '::' + head;
// }

return head + idText(<ts.Identifier>node) + tail;
}
Expand Down
19 changes: 18 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ function reportErrors(errors: ReadonlyArray<ts.Diagnostic>, host: ts.FormatDiagn

const defaultOptions = {
showSemanticDiagnostics: true,
emitHeader: true
emitHeader: true,
getModulePath: name => name,
getModuleNamespace: () => '\\',
modules: {}
};

export function compile(filePath: string, options?: Ts2phpOptions) {
Expand Down Expand Up @@ -73,8 +76,22 @@ export function compile(filePath: string, options?: Ts2phpOptions) {

setState(state);

if (state.modules) {
for (let name of Object.keys(state.modules)) {
state.modules[name].name = name;
}
}

for (const sourceFile of program.getSourceFiles()) {
if (sourceFile.fileName === filePath && !sourceFile.isDeclarationFile) {
sourceFile.resolvedModules && sourceFile.resolvedModules.forEach((item, name) => {
state.modules[name] = {
name,
path: state.getModulePath(name, item),
namespace: state.getModuleNamespace(name, item),
...state.modules[name]
};
});
return {
phpCode: emitter.emitFile(sourceFile, state),
errors: state.errors
Expand Down
38 changes: 16 additions & 22 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@

import ts from 'typescript';

interface moduleInfo {
name: string;
path: string;
namespace: string | false;
required?: boolean;
fileName?: string;
}

export interface Ts2phpOptions {
modules: {
[moduleName: string]: {
path: string;
className: string;
required?: boolean;
}
[name: string]: moduleInfo
},
getNamespace: () => string,
namespace: string,
plugins: {emit: Function}[],
cacheDirectory: string,
emitHeader: boolean,
namespace: string;
plugins: {emit: Function}[];
cacheDirectory: string;
emitHeader: boolean;
showSemanticDiagnostics: boolean;
getNamespace: () => string;
getModulePath: (name: string, module?: ts.ResolvedModuleFull) => string;
getModuleNamespace: (name: string, module?: ts.ResolvedModuleFull) => string;
}

export interface CompilerState extends Ts2phpOptions {
errors: ErrorInfo[],
typeChecker: ts.TypeChecker,
helpers: {},
moduleNamedImports: {
[name: string]: {
className: string;
moduleName: string;
}
},
moduleDefaultImports: {
[name: string]: {
className: string;
moduleName: string;
}
},
sourceFile?: ts.SourceFile
}

Expand Down
Loading

0 comments on commit edda661

Please sign in to comment.