diff --git a/migrator/.gitignore b/migrator/.gitignore index dd4b81cb7d09745fcf2596f00a73bba233e45729..dbd1f889b856dca123ba6151783f4cf02f4f87f8 100644 --- a/migrator/.gitignore +++ b/migrator/.gitignore @@ -15,3 +15,4 @@ StaticTS*Listener.java StaticTS*Visitor.java StaticTSLexer.java StaticTSParser.java +node_modules \ No newline at end of file diff --git a/migrator/README.md b/migrator/README.md index 3034dcd0a21f9d6b3db08324def3194bac62bd52..ec1ab15d3723560b1cbd98d694eb12eb7117695b 100644 --- a/migrator/README.md +++ b/migrator/README.md @@ -10,6 +10,8 @@ This project is using the **Apache Ant** tool for building. You can download bin You also need to use **Java 8** (or newer) to build the project. +The TypeScript translator is written on TypeScript and requires NodeJS to build the project and run the translator. For details, see the [typescript](typescript) page. + ### Steps to build The build supports two main targets: **clean** and **build**: diff --git a/migrator/build.xml b/migrator/build.xml index a16ddfa0782124b72b3bff52e148dc4e66875fa0..bcc107d6a5c31dbbf918efdd94180517c23e7128 100644 --- a/migrator/build.xml +++ b/migrator/build.xml @@ -13,11 +13,11 @@ * limitations under the License. --> - + Migration Tool - + @@ -32,6 +32,9 @@ + + + @@ -61,7 +64,11 @@ - + + + + + @@ -73,7 +80,7 @@ - + @@ -103,7 +110,26 @@ - + + + + + + + + + + + + + + + + + + + + @@ -113,7 +139,7 @@ - + @@ -121,7 +147,7 @@ - + @@ -130,7 +156,7 @@ - + @@ -138,100 +164,63 @@ - - - - - - - - - - - - - - - -One or several tests have failed! -STDERR: -${test.err} - - - - - - - - - - - - - - - - - - - - - - - - -One or several tests have failed! -STDERR: -${test.err} - - - - - - - - - - - - - - - - - - - - - - - - One or several tests have failed! - STDERR: - ${test.err} - - - - - - + + + + + + + + + + + + + + + + + + One or several tests have failed! + STDERR: + ${test.err} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/migrator/src/com/ohos/migrator/Main.java b/migrator/src/com/ohos/migrator/Main.java index d5aaeafeb71940e6cec28b17a94bacf3c6ca03a5..d6d897bb55978fb7aa4ab3fffe03f962cebacafd 100644 --- a/migrator/src/com/ohos/migrator/Main.java +++ b/migrator/src/com/ohos/migrator/Main.java @@ -18,6 +18,7 @@ package com.ohos.migrator; import com.ohos.migrator.java.JavaTranspiler; import com.ohos.migrator.kotlin.KotlinTranspiler; import com.ohos.migrator.staticTS.StaticTSSyntaxChecker; +import com.ohos.migrator.ts.TSTranspiler; import org.apache.commons.cli.*; import java.io.File; @@ -31,12 +32,13 @@ import java.util.List; public class Main { static final String TOOL_NAME = "migrator"; - static final String VERSION_STRING = "version 0.1"; + static final String VERSION_STRING = "version 1.0"; static final String OPTION_VALUE_SEPARATOR = ","; static final String JAVA_EXT = ".java"; static final String KOTLIN_EXT = ".kt"; static final String LIB_EXT = ".jar"; static final String STS_EXT = ".sts"; + static final String TS_EXT = ".ts"; static List errorList = new ArrayList<>(); static boolean verboseMode = false; static boolean strictMode = false; @@ -90,7 +92,8 @@ public class Main { options.addOption(new Option("T","check-sts-syntax",false,"Check syntactical correctness of StaticTS sources")); options.addOption(new Option("R", "conversion-rate", false, "Report conversion rate")); options.addOption(new Option("noxrefs", "noxrefs", false, "Don't resolve cross-references in the input source files")); - options.addOption(new Option("verbose","verbose",false,"Prints extended diagnostic messages")); + options.addOption(new Option("verbose","verbose",false,"Report extended diagnostic info")); + options.addOption(new Option("k", "keep-temp-files", false, "Keep temporary files created by migrator")); options.addOption(new Option("v","version",false,"Version information")); CommandLineParser parser = new DefaultParser(); @@ -135,6 +138,7 @@ public class Main { List javaSources = new ArrayList<>(); List kotlinSources = new ArrayList<>(); List stsSources = new ArrayList<>(); + List tsSources = new ArrayList<>(); // fill sources lists for (String s : sourceFileNames) { @@ -145,13 +149,18 @@ public class Main { } String fileName = f.getName().toLowerCase(); - if(fileName.endsWith(JAVA_EXT)) { + if (fileName.endsWith(JAVA_EXT)) { javaSources.add(f); - } else if(fileName.endsWith(KOTLIN_EXT)) { + } + else if (fileName.endsWith(KOTLIN_EXT)) { kotlinSources.add(f); - } else if(fileName.endsWith(STS_EXT)) { + } + else if (fileName.endsWith(STS_EXT)) { stsSources.add(f); } + else if (fileName.endsWith(TS_EXT)) { + tsSources.add(f); + } else { System.err.println("Source file " + f + " is not supported"); } @@ -218,6 +227,18 @@ public class Main { ++numLanguages; } + if (!tsSources.isEmpty()) { + System.out.println("Transpiling " + tsSources.size() + " TypeScript files."); + + TSTranspiler tsTranspiler = new TSTranspiler(tsSources, outDir, cmd.hasOption("k")); + resultCode = ResultCode.majorValue(tsTranspiler.transpile(), resultCode); + outFiles.addAll(tsTranspiler.getOutFiles()); + errorList.addAll(tsTranspiler.getErrorList()); + + if (convRateMode) convRate += tsTranspiler.getConversionRate(); + ++numLanguages; + } + if (resultCode == ResultCode.OK) System.out.println("Transpilation OK."); if (convRateMode) { diff --git a/migrator/src/com/ohos/migrator/TestRunner.java b/migrator/src/com/ohos/migrator/TestRunner.java index 69e59aad624c7caeaed606370d18970ba8e9aaec..806012992aa55096ddeacd8b581d13b36b9e5195 100644 --- a/migrator/src/com/ohos/migrator/TestRunner.java +++ b/migrator/src/com/ohos/migrator/TestRunner.java @@ -19,25 +19,35 @@ import com.ohos.migrator.util.FileUtils; import java.io.*; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; public class TestRunner { private static final Map testExtensions = new HashMap<>(); - + private static final Set langsWithCommentsMigration = new HashSet<>(); private static boolean hasComparisonFailures = false; static { testExtensions.put("java", Main.JAVA_EXT); testExtensions.put("kotlin", Main.KOTLIN_EXT); testExtensions.put("staticTS", Main.STS_EXT); + testExtensions.put("ts", Main.TS_EXT); + + // NOTE: Add languages here when comments + // migration is implemented for them! + langsWithCommentsMigration.add("java"); } + private static boolean usesOffset(String lang) { + return !langsWithCommentsMigration.contains(lang); + } public static void main(String[] args) { assert(args.length == 1); - - String ext = testExtensions.get(args[0]); - File testDir = new File("test", args[0]); + String lang = args[0]; + String ext = testExtensions.get(lang); + File testDir = new File("test", lang); File testResultDir = new File(testDir, "results"); if (testDir.exists() && testDir.isDirectory()) { String[] testFiles = testDir.list((dir, name) -> name.endsWith(ext)); @@ -70,7 +80,7 @@ public class TestRunner { File expectedFile = new File(testDir, testFile + Main.STS_EXT); // Set a flag if comparison fails but keep comparing to report all failures. - if (!FileUtils.textuallyEqual(resultFile, expectedFile)) { + if (!FileUtils.textuallyEqual(resultFile, expectedFile, usesOffset(lang))) { ++failed; System.err.println("Resulting and expected STS files differ for test " + testFile); hasComparisonFailures = true; diff --git a/migrator/src/com/ohos/migrator/kotlin/KotlinTranspiler.java b/migrator/src/com/ohos/migrator/kotlin/KotlinTranspiler.java index a9087d65d3eb5430f83e899b41029fbc9e5ca1c0..878cd98f15d1a979009352dccf44455418d9dc5c 100644 --- a/migrator/src/com/ohos/migrator/kotlin/KotlinTranspiler.java +++ b/migrator/src/com/ohos/migrator/kotlin/KotlinTranspiler.java @@ -23,6 +23,7 @@ import com.ohos.migrator.staticTS.parser.StaticTSParser.CompilationUnitContext; import com.intellij.openapi.Disposable; import com.intellij.openapi.util.Disposer; +import com.ohos.migrator.util.FileUtils; import org.jetbrains.kotlin.analyzer.AnalysisResult; import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys; import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport; @@ -136,8 +137,7 @@ public class KotlinTranspiler extends AbstractTranspiler { // directory as the application jar. File[] jarFiles = null; try { - File libDir = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile(); - jarFiles = libDir.listFiles(file -> isBuiltinKotlinJar(getManifestImplementationTitle(file))); + jarFiles = FileUtils.getMigratorLibDir().listFiles(file -> isBuiltinKotlinJar(getManifestImplementationTitle(file))); } catch (URISyntaxException e1) {} diff --git a/migrator/src/com/ohos/migrator/staticTS/XMLReader.java b/migrator/src/com/ohos/migrator/staticTS/XMLReader.java new file mode 100755 index 0000000000000000000000000000000000000000..27b117ebb015956f09bd7fa5e2369d84312326e8 --- /dev/null +++ b/migrator/src/com/ohos/migrator/staticTS/XMLReader.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.migrator.staticTS; + +import com.ohos.migrator.Main; +import com.ohos.migrator.ResultCode; +import com.ohos.migrator.staticTS.parser.StaticTSContextBase; +import com.ohos.migrator.staticTS.parser.StaticTSParser; +import com.ohos.migrator.staticTS.parser.StaticTSParser.*; +import org.antlr.v4.runtime.tree.TerminalNode; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.EndElement; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.Stack; +public class XMLReader { + private File xmlFile; + private Stack nodeStack = new Stack<>(); + private static final QName terminalKindAttr = new QName("kind"); + private static final QName terminalTextAttr = new QName("text"); + private static final String classNamePrefix = StaticTSParser.class.getName(); + + private static final String terminalNodeName = "TerminalNode"; + + private static final String terminalIdentKind = "Identifier"; + + public XMLReader(File xmlFile) { + this.xmlFile = xmlFile; + } + + public CompilationUnitContext read() throws XMLStreamException, IOException { + FileInputStream fis = new FileInputStream(xmlFile); + XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + XMLEventReader xmlReader = xmlInputFactory.createXMLEventReader(fis); + + CompilationUnitContext result = null; + try { + while (xmlReader.hasNext()) { + XMLEvent event = xmlReader.nextEvent(); + + try { + StaticTSContextBase lastNode = nodeStack.empty() ? null : nodeStack.peek(); + + if (event.isStartElement()) { + StartElement startElement = event.asStartElement(); + String tagName = startElement.getName().getLocalPart(); + + if (terminalNodeName.equals(tagName)) { + // Create the terminal and add it to the last node in the stack. + // Ignore if node stack is empty or terminal is invalid. + if (lastNode != null) { + TerminalNode terminal = createTerminal(startElement); + if (terminal != null) + lastNode.addChild(terminal); + else + reportError("Invalid XML tag emitted by TS transpiler", startElement); + } + + continue; + } + + // These two calls work as a filter. If current tag doesn't correspond to + // an AST node, they will throw, and the tag will be ignored as the result. + Class tagClass = Class.forName(classNamePrefix + "$" + tagName); + Class nodeClass = + tagClass.asSubclass(StaticTSContextBase.class); + + // Create node object and push it onto node stack. + // Also add it to AST if it exists (i.e., lastNode not null). + StaticTSContextBase node = createNode(nodeClass, lastNode); + + if (node != null) { + if (lastNode != null) + lastNode.addChild(node).setParent(lastNode); + + nodeStack.push(node); + } + else + reportError("Failed to create AST node for XML tag emitted by TS transpiler", startElement); + } + else if (event.isEndElement()) { + EndElement endElement = event.asEndElement(); + String tagName = endElement.getName().getLocalPart(); + + // Terminals are never added into nodeStack + // (see above) so just ignore them here. + if (terminalNodeName.equals(tagName)) continue; + + // Check that node stack is not empty and closing tag name matches + // type of the last node in stack before popping it off. The latter + // test takes care of the tags we skipped above due to exceptions. + if (lastNode != null && lastNode.getClass().getSimpleName().equals(tagName)) { + StaticTSContextBase node = nodeStack.pop(); + + // Bail out when we see closing tag for CompilationUnitContext. + if (node.getRuleIndex() == StaticTSParser.RULE_compilationUnit) { + result = (CompilationUnitContext) node; + break; + } + } + } + } catch (Exception e) { + // Report error and swallow exception to ignore the tag and continue iterating + reportError("Ignoring unexpected XML tag emitted by TS transpiler", event); + } + } + } + finally { + // Close resources, most importantly the FileInputStream + // object, to allow further manipulations with XML file, + // e.g., deleting it (see TSTranspiler.transpileFile method) + fis.close(); + xmlReader.close(); + } + + return result; + } + + private TerminalNode createTerminal(StartElement startElement) { + // Sanity check. + if (!terminalNodeName.equals(startElement.getName().getLocalPart())) + return null; + + Attribute kindAttr = startElement.getAttributeByName(terminalKindAttr); + if (kindAttr == null) return null; + + String attrName = kindAttr.getValue(); + if (attrName == null || attrName.isEmpty()) return null; + + if (terminalIdentKind.equals(attrName)) { + Attribute textAttr = startElement.getAttributeByName(terminalTextAttr); + if (textAttr == null) return null; + + String text = textAttr.getValue(); + if (text == null || text.isEmpty()) return null; + + return NodeBuilderBase.terminalIdentifier(text); + } + + for (int i = 1; i < StaticTSParser.VOCABULARY.getMaxTokenType(); ++i) { + if (attrName.equals(StaticTSParser.VOCABULARY.getSymbolicName(i))) + return NodeBuilderBase.terminalNode(i); + } + + return null; + } + + // NOTE: We don't care about specific exception types this function + // can throw as we catch and process them all the same way. + private StaticTSContextBase createNode(Class nodeClass, + StaticTSContextBase parent) throws Exception { + // Pick up node ctor with one or two parameters. + // AntLR guarantees that all rule-based node classes + // have at least ctor that satisfies this condition. + Constructor nodeCtor = null; + for (Constructor ctor : nodeClass.getConstructors()) { + int numParams = ctor.getParameterCount(); + if (numParams > 0 && numParams < 3) { + nodeCtor = ctor; + break; + } + } + + // This check should never be true! + if (nodeCtor == null) return null; + + // Construct arguments array and invoke selected ctor. + Object[] ctorArgs = nodeCtor.getParameterCount() > 1 ? + new Object[] { parent, 0 } : + new Object[] { parent }; + + return (StaticTSContextBase)nodeCtor.newInstance(ctorArgs); + } + + private void reportError(String message, XMLEvent xmlEvent) { + Main.addError(ResultCode.TranspileError, message + " at " + + xmlFile.getPath() + ":" + xmlEvent.getLocation().getLineNumber()); + } +} diff --git a/migrator/src/com/ohos/migrator/staticTS/parser/DummyContext.java b/migrator/src/com/ohos/migrator/staticTS/parser/DummyContext.java index 01c35e47b1d8d68037ab474fdd7fb94612bd0770..dc3104e0a095e15dda3e4c683fc4859b8142efa1 100755 --- a/migrator/src/com/ohos/migrator/staticTS/parser/DummyContext.java +++ b/migrator/src/com/ohos/migrator/staticTS/parser/DummyContext.java @@ -1,3 +1,17 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.ohos.migrator.staticTS.parser; import com.ohos.migrator.staticTS.writer.StaticTSWriter; diff --git a/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSContextBase.java b/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSContextBase.java index 87309989bb7ea3e40b0b50e07240762ee8fb2851..4e3bc1108604052dae7001b0fb7770e2272a6008 100755 --- a/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSContextBase.java +++ b/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSContextBase.java @@ -1,8 +1,21 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.ohos.migrator.staticTS.parser; -import com.ohos.migrator.staticTS.writer.StaticTSWriter; import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.tree.ParseTreeVisitor; import org.antlr.v4.runtime.tree.TerminalNode; import java.util.LinkedList; diff --git a/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSParser.g4 b/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSParser.g4 index b9cfeb56375be1ca8299af871a25de9fbe590da1..1e94361fe967f0fb0e2f14ff649a9d24b4c77e34 100644 --- a/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSParser.g4 +++ b/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSParser.g4 @@ -50,13 +50,6 @@ options { contextSuperClass=StaticTSContextBase; } -@members { - // These are context-dependent keywords, i.e. those that - // can't be used as identifiers only in specific contexts - public static final String IN = "in"; // keyword in type argument and type parameter contexts - public static final String OUT = "out"; // keyword in type argument and type parameter contexts -} - compilationUnit : packageDeclaration? importDeclaration* topDeclaration* EOF ; @@ -274,7 +267,7 @@ predefinedType ; arrayType - : (predefinedType | typeReference) {notLineTerminator()}? (OpenBracket CloseBracket)+ + : (predefinedType | typeReference) {this.notLineTerminator()}? (OpenBracket CloseBracket)+ ; typeReference @@ -295,7 +288,7 @@ typeParameterList ; typeParameter - : ({ this.next(IN) || this.next(OUT) }? Identifier)? Identifier constraint? + : ({ this.next(StaticTSParser.IN) || this.next(StaticTSParser.OUT) }? Identifier)? Identifier constraint? ; constraint @@ -317,8 +310,8 @@ typeArgument ; wildcardType - : { this.next(IN) }? Identifier typeReference - | { this.next(OUT) }? Identifier typeReference? + : { this.next(StaticTSParser.IN) }? Identifier typeReference + | { this.next(StaticTSParser.OUT) }? Identifier typeReference? ; // Statements diff --git a/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSParserBase.java b/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSParserBase.java index 2426e9c106915eea31e19c2570086221f8bc7269..748dff08cbd6a66470d15c71bd4ae51841f80f43 100644 --- a/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSParserBase.java +++ b/migrator/src/com/ohos/migrator/staticTS/parser/StaticTSParserBase.java @@ -23,6 +23,11 @@ import org.antlr.v4.runtime.*; */ public abstract class StaticTSParserBase extends Parser { + // These are context-dependent keywords, i.e. those that + // can't be used as identifiers only in specific contexts + public static final String IN = "in"; // keyword in type argument and type parameter contexts + public static final String OUT = "out"; // keyword in type argument and type parameter contexts + public StaticTSParserBase(TokenStream input) { super(input); } diff --git a/migrator/src/com/ohos/migrator/ts/TSTranspiler.java b/migrator/src/com/ohos/migrator/ts/TSTranspiler.java new file mode 100755 index 0000000000000000000000000000000000000000..82c334a16c0d71c9c1c11d2ea7cfb643f439a625 --- /dev/null +++ b/migrator/src/com/ohos/migrator/ts/TSTranspiler.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ohos.migrator.ts; + +import com.ohos.migrator.AbstractTranspiler; +import com.ohos.migrator.ResultCode; +import com.ohos.migrator.TranspileException; +import com.ohos.migrator.staticTS.XMLReader; +import com.ohos.migrator.staticTS.parser.StaticTSParser.*; +import com.ohos.migrator.util.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; + +public class TSTranspiler extends AbstractTranspiler { + + private static String XML_EXT = ".xml"; + private static String tsTranspilerJS = "typescript/build/javascript/src/transpiler/TypeScriptTranspiler.js"; + + private boolean keepXML; + public TSTranspiler(List src, String outDir, boolean keepXML) { + super(src, null, outDir); + this.keepXML = keepXML; + } + + @Override + protected void transpileFile(File srcFile) throws TranspileException { + try { + String baseDir = FileUtils.getMigratorJarPath().getParentFile().getParent(); + String tsTranspilerPath = new File(baseDir, tsTranspilerJS).getPath(); + + // Run TS translator and wait for it to exit. + Process p = new ProcessBuilder("node", tsTranspilerPath, srcFile.getPath()).start(); + int exitCode = p.waitFor(); + if (exitCode != 0) { + String stderr = p.getErrorStream().toString(); + throw new TranspileException(ResultCode.TranspileError, "TS transpiler exited abnormally.\nSTDERR:\n" + stderr); + } + + // Pick up XML file created by TS translator and process it. + File xmlFile = new File(srcFile.getPath() + XML_EXT); + if (xmlFile.exists()) { + // Wipe out XML file created by TS translator + // unless specifically instructed by user not to. + if (!keepXML) xmlFile.deleteOnExit(); + + try { + XMLReader xmlReader = new XMLReader(xmlFile); + CompilationUnitContext stsCU = xmlReader.read(); + write(stsCU, srcFile); + } + catch (Exception e) { + throw new TranspileException(ResultCode.TranspileError, e); + } + } + else + throw new TranspileException(ResultCode.TranspileError, "TS transpiler failed for " + + srcFile.getPath() + ": No XML output."); + } + catch (URISyntaxException use) { + throw new TranspileException(ResultCode.CmdLineError, use); + } + catch (IOException ioe) { + throw new TranspileException(ResultCode.InputError, ioe); + } + catch (InterruptedException ie) { + throw new TranspileException(ResultCode.TranspileError, ie); + } + } +} diff --git a/migrator/src/com/ohos/migrator/util/FileUtils.java b/migrator/src/com/ohos/migrator/util/FileUtils.java index 5a02aef51191b40e45e3b960b9e8b77ebda1e7f5..13780dc8324eade395a925a5e9de684db5a393ee 100644 --- a/migrator/src/com/ohos/migrator/util/FileUtils.java +++ b/migrator/src/com/ohos/migrator/util/FileUtils.java @@ -15,7 +15,11 @@ package com.ohos.migrator.util; +import com.ohos.migrator.Main; + import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -41,15 +45,18 @@ public class FileUtils { } } - public static boolean textuallyEqual(File resultFile, File expectedFile) { + private static final int offset = 14; + public static boolean textuallyEqual(File resultFile, File expectedFile, boolean useOffset) { String[] resultText = readFile(resultFile); String[] expectedText = readFile(expectedFile); - if (resultText.length != expectedText.length) + int expectedLen = useOffset ? expectedText.length-offset : expectedText.length; + if (resultText.length != expectedLen) return false; for (int i = 0; i < resultText.length; ++i) { - if (!resultText[i].equals(expectedText[i])) + int expectedInd = useOffset ? i+offset : i; + if (!resultText[i].equals(expectedText[expectedInd])) return false; } @@ -86,4 +93,13 @@ public class FileUtils { System.err.println("Failed to copy file " + file.getPath()); } } + + public static File getMigratorJarPath() throws URISyntaxException { + URI mainClassURI = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI(); + return new File(mainClassURI); + } + + public static File getMigratorLibDir() throws URISyntaxException { + return getMigratorJarPath().getParentFile(); + } } diff --git a/migrator/test/kotlin/global_functions.kt.sts b/migrator/test/kotlin/global_functions.kt.sts index a7efad8055825cb6656b35e739a4a44164a27e9e..830fcbee76b9ad234bf08becabad5411075bf3e1 100644 --- a/migrator/test/kotlin/global_functions.kt.sts +++ b/migrator/test/kotlin/global_functions.kt.sts @@ -18,10 +18,10 @@ package com.ohos.migrator.test.kotlin; export function main(): kotlin.Unit { } export function doubleNum(x : Int): Int { - return __untranslated_expression(/* x * 2 */); + return __untranslated_expression( /* x * 2 */); } -function sum(... x: Int ): __UnknownType__ { - return __untranslated_expression(/* x.sum() */); +function sum(... x: Int ): kotlin.Int { + return __untranslated_expression( /* x.sum() */); } function doNothing(): kotlin.Unit { } diff --git a/migrator/test/kotlin/imports.kt.sts b/migrator/test/kotlin/imports.kt.sts index 5a75e8f443494b5719765829609042bfcdb0e4a7..77c78ca61a0e3c716364dcb9575b6b075bceb23d 100644 --- a/migrator/test/kotlin/imports.kt.sts +++ b/migrator/test/kotlin/imports.kt.sts @@ -19,7 +19,7 @@ import kotlin.random.Random; import kotlin.math.*; import kotlin.reflect.KClass as Class; export function main(): kotlin.Unit { - __untranslated_statement(/* println(PI) */); - let rnd : Random = __untranslated_expression(/* Random(5) */); + __untranslated_statement( /* println(PI) */); + let rnd : Random = __untranslated_expression( /* Random(5) */); let kk : Class ; -} \ No newline at end of file +} diff --git a/migrator/test/ts/empty_class.ts b/migrator/test/ts/empty_class.ts new file mode 100644 index 0000000000000000000000000000000000000000..c8c6efa6c68e92c18d049f08720d2884548355c7 --- /dev/null +++ b/migrator/test/ts/empty_class.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class empty { +} diff --git a/migrator/test/ts/empty_class.ts.sts b/migrator/test/ts/empty_class.ts.sts new file mode 100644 index 0000000000000000000000000000000000000000..ebfb0fdac5e337c170d0cbcc1742074562014c06 --- /dev/null +++ b/migrator/test/ts/empty_class.ts.sts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class empty { +} diff --git a/migrator/test/ts/empty_function.ts b/migrator/test/ts/empty_function.ts new file mode 100644 index 0000000000000000000000000000000000000000..b3546d4f6dbc5b29024d73f092d0d10e1e931f1e --- /dev/null +++ b/migrator/test/ts/empty_function.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class MyClass { +} + +function foo() {} + +function bar(a: boolean, b: number, c: MyClass): void {} \ No newline at end of file diff --git a/migrator/test/ts/empty_function.ts.sts b/migrator/test/ts/empty_function.ts.sts new file mode 100644 index 0000000000000000000000000000000000000000..241869c6a06dbcfede98dcc94bebe9d7b0247f52 --- /dev/null +++ b/migrator/test/ts/empty_function.ts.sts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class MyClass { +} + +function foo(): __UnknownType__ { +} +function bar(a : boolean, b : double, c : MyClass): void { +} diff --git a/migrator/typescript/.gitignore b/migrator/typescript/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..63c28855ae229234dffdb950772223f826d010d9 --- /dev/null +++ b/migrator/typescript/.gitignore @@ -0,0 +1,3 @@ +generated +node_modules +*.tsbuildinfo \ No newline at end of file diff --git a/migrator/typescript/README.md b/migrator/typescript/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ea2f4d78ac4e66e149acdec683a64c64a559d31e --- /dev/null +++ b/migrator/typescript/README.md @@ -0,0 +1,37 @@ +# TypeScript Translator +Translator from TypeScript to StaticTS, written on TypeScript and using TSC API. + +## Prerequisits + +### Visual Studio Code +For development, it's recommended to use `VS Code`, as it has a full built-in support for TypeScript language. + +### NodeJS and NPM +In order to build and run TypeScript translator, you need to install `NodeJS` and `NPM`. It is recommended using a `Node version manager` to install Node and NPM ([nvm](https://github.com/nvm-sh/nvm) for Linux; [nvm-windows](https://github.com/coreybutler/nvm-windows) for windows - v1.1.9 is the most stable). You can also follow the [official guide](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). + +### TypeScript Compiler +The translator itself is written on TypeScript, therefore you need to use a TSC in order to build the project. The build script uses the TypeScript compiler downloaded as a local project dependency, but you can also install TypeScript Compiler globally and use it to compile the sources separately (see the [Intermediate build steps](#intermediate-build-steps) below): +```bash +npm install -g typescript +``` +See https://www.npmjs.com/package/typescript for more details. + +## Building +All commands below are run from `migrator/typescript`. + +### Full build +- `npm install` - installs/updates project dependencies and compiles the project sources. +- `npm run build` - builds the project, but skips checking the project dependencies (at the `node_modules` folder). + +### Intermediate build steps +If you want to build only certain part of the typescript module, use the following commands: +- `npm run antlr4ts` - generates StaticTS parser/lexer using ANTLR grammar. +- `npm run tsc` (or just `tsc` if using **global** tsc) - compiles project sources. + +Compiled JavaScript code is located at `build/javascript` directory. + +## Running +Run the following command from the same directory: +```bash +node build\javascript\src\transpiler\TypeScriptTranspiler.js [input_files] +``` \ No newline at end of file diff --git a/migrator/typescript/package-lock.json b/migrator/typescript/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..4e5b2394a0404341c2626e9a8ce826e79208d931 --- /dev/null +++ b/migrator/typescript/package-lock.json @@ -0,0 +1,75 @@ +{ + "name": "ts-transpiler", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "ts-transpiler", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "18.11.7", + "antlr4ts": "0.5.0-alpha.4", + "typescript": "4.8.4" + }, + "devDependencies": { + "antlr4ts-cli": "0.5.0-alpha.4" + } + }, + "node_modules/@types/node": { + "version": "18.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", + "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==" + }, + "node_modules/antlr4ts": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==" + }, + "node_modules/antlr4ts-cli": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts-cli/-/antlr4ts-cli-0.5.0-alpha.4.tgz", + "integrity": "sha512-lVPVBTA2CVHRYILSKilL6Jd4hAumhSZZWA7UbQNQrmaSSj7dPmmYaN4bOmZG79cOy0lS00i4LY68JZZjZMWVrw==", + "dev": true, + "bin": { + "antlr4ts": "antlr4ts" + } + }, + "node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "@types/node": { + "version": "18.11.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.7.tgz", + "integrity": "sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==" + }, + "antlr4ts": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==" + }, + "antlr4ts-cli": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts-cli/-/antlr4ts-cli-0.5.0-alpha.4.tgz", + "integrity": "sha512-lVPVBTA2CVHRYILSKilL6Jd4hAumhSZZWA7UbQNQrmaSSj7dPmmYaN4bOmZG79cOy0lS00i4LY68JZZjZMWVrw==", + "dev": true + }, + "typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==" + } + } +} diff --git a/migrator/typescript/package.json b/migrator/typescript/package.json new file mode 100644 index 0000000000000000000000000000000000000000..ee7a7bcbe7ad0b891ac1eb89327b8f3584133286 --- /dev/null +++ b/migrator/typescript/package.json @@ -0,0 +1,21 @@ +{ + "name": "ts-transpiler", + "version": "1.0.0", + "private": true, + "license": "Apache-2.0", + "scripts": { + "antlr4ts": "antlr4ts -no-listener -no-visitor -o build/typescript -Xexact-output-dir ..\\src\\com\\ohos\\migrator\\staticTS\\parser\\StaticTSLexer.g4 ..\\src\\com\\ohos\\migrator\\staticTS\\parser\\StaticTSParser.g4", + "postantlr4ts": "node scripts/fix-generated-parser.mjs build/typescript", + "tsc": "tsc", + "build": "npm run antlr4ts && npm run tsc", + "prepare": "npm run build" + }, + "dependencies": { + "@types/node": "18.11.7", + "antlr4ts": "0.5.0-alpha.4", + "typescript": "4.8.4" + }, + "devDependencies": { + "antlr4ts-cli": "0.5.0-alpha.4" + } +} \ No newline at end of file diff --git a/migrator/typescript/scripts/fix-generated-parser.mjs b/migrator/typescript/scripts/fix-generated-parser.mjs new file mode 100644 index 0000000000000000000000000000000000000000..6e7cd47af0aac31169a113a27d2a214e87b5dd29 --- /dev/null +++ b/migrator/typescript/scripts/fix-generated-parser.mjs @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This script is intended to fix lexer and parser files generated + * by 'antlr4ts' by adding missing imports for base classes. +*/ + +import { openSync, readFileSync, writeSync, closeSync } from "fs"; +import { EOL } from "os"; + +function appendText(file, textToAppend) { + var error; + try { + var data = readFileSync(file); // Read existing contents into data + var fd = openSync(file, 'w+'); + var buffer = Buffer.from(textToAppend + EOL); + + writeSync(fd, buffer, 0, buffer.length, 0); // Write new data + writeSync(fd, data, 0, data.length, buffer.length); // Append old data + } catch (e) { + error = e; + } finally { + closeSync(fd); + + if (error) { + console.error(error); + process.exit(1); + } + } +} + +var base_dir = process.argv[2]; + +var lexer_imports = 'import { StaticTSLexerBase } from "../../src/staticts/StaticTSLexerBase"' +appendText(`${base_dir}/StaticTSLexer.ts`, lexer_imports); + +var parser_imports = `import { StaticTSParserBase } from "../../src/staticts/StaticTSParserBase" +import { StaticTSContextBase } from "../../src/staticts/StaticTSContextBase"` +appendText(`${base_dir}/StaticTSParser.ts`, parser_imports); diff --git a/migrator/typescript/src/staticts/DummyContext.ts b/migrator/typescript/src/staticts/DummyContext.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a6dcb53181c1fa603b6d91b2dfa410820f5c841 --- /dev/null +++ b/migrator/typescript/src/staticts/DummyContext.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ParserRuleContext } from 'antlr4ts' +import { StaticTSContextBase } from './StaticTSContextBase'; + +export class DummyContext extends StaticTSContextBase { + constructor(parent: ParserRuleContext, invokingStateNumber: number) { + super(parent, invokingStateNumber); + } +} diff --git a/migrator/typescript/src/staticts/StaticTSContextBase.ts b/migrator/typescript/src/staticts/StaticTSContextBase.ts new file mode 100644 index 0000000000000000000000000000000000000000..99ce6ea5231eacb306f710390a5e47670d6e06c8 --- /dev/null +++ b/migrator/typescript/src/staticts/StaticTSContextBase.ts @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ParserRuleContext, Token } from 'antlr4ts' +import { ParseTree, TerminalNode } from 'antlr4ts/tree' +import { StaticTSParser } from '../../build/typescript/StaticTSParser' + +export class StaticTSContextBase extends ParserRuleContext { + private leadingComments: TerminalNode[]; + private trailingComments: TerminalNode[]; + + constructor(parent: ParserRuleContext | undefined, invokingStateNumber: number) { + super(parent, invokingStateNumber); + } + + addLeadingComment(stsComment: TerminalNode): void { + if (!this.leadingComments) this.leadingComments = []; + this.leadingComments.push(stsComment); + } + + addTrailingComment(stsComment: TerminalNode): void { + if (!this.trailingComments) this.trailingComments = []; + this.trailingComments.push(stsComment); + } + + setLeadingComments(stsComments: TerminalNode[]): void { + this.leadingComments = stsComments; + } + + setTrailingComments(stsComments: TerminalNode[]): void { + this.trailingComments = stsComments; + } + + getLeadingComments(): TerminalNode[] { + return this.leadingComments; + } + + getTrailingComments(): TerminalNode[] { + return this.trailingComments; + } + + hasLeadingComments(): boolean { + return this.leadingComments && this.leadingComments.length != 0; + } + + hasTrailingComments(): boolean { + return this.trailingComments && this.trailingComments.length != 0; + } + + private static indent : number; + + toXML() : string { + // Reset indent before recursing into AST + StaticTSContextBase.indent = 0; + let header : string = "\n"; + return header + this.toXMLImpl(); + } + + private toXMLImpl() : string { + let nodeName : string = this.constructor.name; + let xmlIndent : string = " ".repeat(4*StaticTSContextBase.indent); + + // If current node has no children, return one-line XML tag; + // otherwise create opening XML and proceed to child nodes. + let result : string = xmlIndent + "<" + nodeName; + if (this.childCount === 0) { + return result + "/>\n"; + } + + result += ">\n"; + + // Increase indent and process children. + ++StaticTSContextBase.indent; + for (let i = 0; i < this.childCount; ++i) { + let childNode : ParseTree = this.getChild(i); + + if (childNode instanceof StaticTSContextBase) { + result += (childNode as StaticTSContextBase).toXMLImpl(); + } + else if (childNode instanceof TerminalNode) { + // Can't recurse into TerminalNode as it doesn't extend + // the current class, so process terminals in place. + let token : Token = (childNode as TerminalNode).symbol; + let kind : string = StaticTSParser.VOCABULARY.getSymbolicName(token.type); + result += xmlIndent + " ".repeat(4) + "\n"; + + return result; + } +} diff --git a/migrator/typescript/src/staticts/StaticTSLexerBase.ts b/migrator/typescript/src/staticts/StaticTSLexerBase.ts new file mode 100644 index 0000000000000000000000000000000000000000..7351627e55f2bccdb27bd0e4eecc77383f1e441b --- /dev/null +++ b/migrator/typescript/src/staticts/StaticTSLexerBase.ts @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Lexer, Token, Vocabulary } from "antlr4ts" + +/** + * All lexer methods that used in grammar (IsStrictMode) + * should start with Upper Case Char similar to Lexer rules. + */ +export class StaticTSLexerBase extends Lexer { + + constructor(input: any) { + super(input); + } + + get ruleNames(): string[] { + return null; + } + get grammarFileName(): string { + return ""; + } + get vocabulary(): Vocabulary { + return null; + } + get channelNames(): string[] { + return null + } + get modeNames(): string[] { + return null; + } + + getStrictDefault(): boolean { + return false; + } + setUseStrictDefault(value: boolean): void { + } + public IsStrictMode(): boolean { + return false; + } + public IsInTemplateString(): boolean { + return false; + } + public nextToken(): Token { + return null; + } + protected ProcessOpenBrace(): void { + } + protected ProcessCloseBrace(): void { + } + protected ProcessStringLiteral(): void { + } +} diff --git a/migrator/typescript/src/staticts/StaticTSParserBase.ts b/migrator/typescript/src/staticts/StaticTSParserBase.ts new file mode 100644 index 0000000000000000000000000000000000000000..0482e51444c39b0258d2d495e09bf3544bcd2348 --- /dev/null +++ b/migrator/typescript/src/staticts/StaticTSParserBase.ts @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Parser, TokenStream, Vocabulary } from "antlr4ts" + +/** + * All parser methods that used in grammar (p, prev, notLineTerminator, etc.) + * should start with lower case char similar to parser rules. + */ +export class StaticTSParserBase extends Parser { + + // These are context-dependent keywords, i.e. those that + // can't be used as identifiers only in specific contexts + public static IN = "in"; // keyword in type argument and type parameter contexts + public static OUT = "out"; // keyword in type argument and type parameter contexts + + constructor(input: TokenStream) { + super(input); + } + + get ruleNames(): string[] { + return null; + } + get grammarFileName(): string { + return ""; + } + get vocabulary(): Vocabulary { + return null; + } + protected p(str: string): boolean { + return false; + } + protected prev(str: string): boolean { + return false; + } + protected n(str: string): boolean { + return false; + } + protected next(str: string): boolean { + return false; + } + protected notLineTerminator(): boolean { + return false; + } + protected notOpenBraceAndNotFunction(): boolean { + return false; + } + protected closeBrace(): boolean { + return false; + } + protected lineTerminatorAhead(): boolean { + return false; + } +} diff --git a/migrator/typescript/src/transpiler/NodeBuilder.ts b/migrator/typescript/src/transpiler/NodeBuilder.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc72c4478bbbd47bdb5809312af972bf5bcf7455 --- /dev/null +++ b/migrator/typescript/src/transpiler/NodeBuilder.ts @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CommonToken, ParserRuleContext } from "antlr4ts"; +import { TerminalNode } from "antlr4ts/tree"; +import * as sts from "../../build/typescript/StaticTSParser"; +import * as ts from "typescript"; + +export const UNKNOWN_TYPE_NAME = "__UnknownType__"; + +export type STSTypeContext = sts.TypeReferenceContext | sts.ArrayTypeContext | sts.PredefinedTypeContext; + +export function terminalIdentifier(name: string): TerminalNode { + return terminalNode(sts.StaticTSParser.Identifier, name); +} + +export function terminalNode(tokenKind: number, tokenName?: string): TerminalNode { + if (!tokenName) tokenName = stsTokenName(tokenKind); + return new TerminalNode(new CommonToken(tokenKind, tokenName)); +} + +export function stsTokenName(tokenKind: number): string { + return sts.StaticTSParser.VOCABULARY.getLiteralName(tokenKind); +} + +export function entityNameToString(fqName: ts.EntityName): string { + if (ts.isIdentifier(fqName)) { + return fqName.text; + } + else { + return entityNameToString(fqName.left) + "." + fqName.right.text; + } +} + +export function qualifiedName(fqName: ts.EntityName | string): sts.QualifiedNameContext { + let stsQualifiedName = new sts.QualifiedNameContext(null, 0); + if (typeof(fqName) === 'string') { + stsQualifiedName.addChild(terminalIdentifier(fqName)); + } + else { + stsQualifiedName.addChild(terminalIdentifier(entityNameToString(fqName))); + } + return stsQualifiedName; +} + +export function typeReference(typeName: ts.EntityName | string): sts.TypeReferenceContext { + let stsTypeRef = new sts.TypeReferenceContext(null, 0); + let stsTypeRefPart = typeReferencePart(typeName); + stsTypeRef.addChild(stsTypeRefPart); + return stsTypeRef; +} + +export function typeReferencePart(typeName: ts.EntityName | string): sts.TypeReferencePartContext { + let stsTypeRefPart = new sts.TypeReferencePartContext(null, 0); + stsTypeRefPart.addChild(qualifiedName(typeName)); + return stsTypeRefPart; +} + +export function unknownTypeReference(): sts.TypeReferenceContext { + let stsTypeRef = typeReference(UNKNOWN_TYPE_NAME); + return stsTypeRef; +} + +export function typeAnnotation(stsType: STSTypeContext | string): sts.TypeAnnotationContext { + if (typeof (stsType) === 'string') { + let stsTypeRef = typeReference(stsType); + return typeAnnotation(stsTypeRef); + } + else { + let stsTypeAnno = new sts.TypeAnnotationContext(null, 0); + let stsPrimaryType = new sts.PrimaryTypeContext(stsTypeAnno, 0); + stsPrimaryType.addChild(stsType); + stsTypeAnno.addChild(stsPrimaryType); + return stsTypeAnno; + } +} + +export function unknownTypeAnnotation(): sts.TypeAnnotationContext { + return typeAnnotation(UNKNOWN_TYPE_NAME); +} + +function stsPredefinedTypeCode(tsTypeKind: ts.SyntaxKind): number { + let stsTypeCode = -1; + + if (tsTypeKind === ts.SyntaxKind.BooleanKeyword) + stsTypeCode = sts.StaticTSParser.Boolean; + else if (tsTypeKind === ts.SyntaxKind.NumberKeyword) + stsTypeCode = sts.StaticTSParser.Double; // number type corresponds to double + else if (tsTypeKind === ts.SyntaxKind.BigIntKeyword) + stsTypeCode = sts.StaticTSParser.Long; // TODO: BigInt doesn't have maximum limit + else if (tsTypeKind === ts.SyntaxKind.StringKeyword) + stsTypeCode = sts.StaticTSParser.String; + else if (tsTypeKind === ts.SyntaxKind.VoidKeyword) + stsTypeCode = sts.StaticTSParser.Void; + //else TODO: Report an error + + return stsTypeCode; +} + +export function predefinedType(tsType: ts.TypeNode): sts.PredefinedTypeContext | sts.TypeReferenceContext { + let stsPredefinedType = new sts.PredefinedTypeContext(null, 0); + let stsTokenKind = stsPredefinedTypeCode(tsType.kind); + + // Return UNKNOWN_TYPE for unrecognized primitive type + if (stsTokenKind === -1) return unknownTypeReference(); + + stsPredefinedType.addChild(terminalNode(stsTokenKind)); + return stsPredefinedType; +} \ No newline at end of file diff --git a/migrator/typescript/src/transpiler/TranslationUtils.ts b/migrator/typescript/src/transpiler/TranslationUtils.ts new file mode 100644 index 0000000000000000000000000000000000000000..0110ee385cd22c3cfa30baf53ccf037733662569 --- /dev/null +++ b/migrator/typescript/src/transpiler/TranslationUtils.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as ts from "typescript"; + +export function isFunctionBlock(tsNode: ts.Node): boolean { + return tsNode && tsNode.kind === ts.SyntaxKind.Block && ts.isFunctionLike(tsNode.parent); +} diff --git a/migrator/typescript/src/transpiler/TypeScriptTransformer.ts b/migrator/typescript/src/transpiler/TypeScriptTransformer.ts new file mode 100644 index 0000000000000000000000000000000000000000..b41a3d6fd5d19d3b97181d3a9723d4d347236b43 --- /dev/null +++ b/migrator/typescript/src/transpiler/TypeScriptTransformer.ts @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CommonToken } from "antlr4ts"; +import { TerminalNode } from "antlr4ts/tree"; +import * as ts from "typescript"; +import { StaticTSContextBase } from "../staticts/StaticTSContextBase"; +import * as sts from "../../build/typescript/StaticTSParser"; +import * as TranslationUtils from "./TranslationUtils"; +import * as NodeBuilder from "./NodeBuilder"; + +type VisitFunction = (tsNode: T) => StaticTSContextBase; +type VisitorTable = { [key: number]: VisitFunction }; + +export class TypeScriptTransformer { + tsTypeChecker: ts.TypeChecker; + + constructor(private tsSourceFile: ts.SourceFile, private tsProgram: ts.Program) { + this.tsTypeChecker = tsProgram.getTypeChecker(); + } + + public transform(): sts.CompilationUnitContext { + return this.visitSourceFile(this.tsSourceFile); + } + + readonly visitorTable : VisitorTable = { + [ts.SyntaxKind.SourceFile]: this.visitSourceFile, + [ts.SyntaxKind.ClassDeclaration]: this.visitClassDeclaration, + [ts.SyntaxKind.FunctionDeclaration]: this.visitFunctionDeclaration, + [ts.SyntaxKind.Parameter]: this.visitParameter, + [ts.SyntaxKind.Block]: this.visitBlock, + [ts.SyntaxKind.TypeReference]: this.visitTypeReference + } + + /** + * Calls corresponding visit function for specified node. + * @param tsNode The node to be visited + */ + visitNode(tsNode: ts.Node): StaticTSContextBase { + let visitFn = this.visitorTable[tsNode.kind]; + + if (visitFn) { + // Since we call certain 'visit' function as a callback, the 'this' + // inside 'visit' function won't refer to the instance context, and + // instead it will be 'undefined'. We need to explicitly bind 'this' + // to the method call. For this purpose, use the 'Function.call' API. + return visitFn.call(this, tsNode); + } else { + return null; + // Print untranslated node? + } + } + + visitSourceFile(tsSourceFile: ts.SourceFile): sts.CompilationUnitContext { + let stsCU = new sts.CompilationUnitContext(null, 0); + for(const tsNode of tsSourceFile.statements) { + let stsChildNode = this.visitNode(tsNode); + if (stsChildNode) { + let stsTopDecl = new sts.TopDeclarationContext(stsCU, 0); + stsTopDecl.addChild(stsChildNode); + stsCU.addChild(stsTopDecl); + } + } + + return stsCU; + } + + visitClassDeclaration(tsClassDecl: ts.ClassDeclaration): sts.ClassDeclarationContext { + let stsClassDecl = new sts.ClassDeclarationContext(null, 0); + let tsClassName : string = tsClassDecl.name.getText(); + stsClassDecl.addChild(NodeBuilder.terminalNode(sts.StaticTSParser.Class)); + stsClassDecl.addChild(NodeBuilder.terminalIdentifier(tsClassName)); + stsClassDecl.addChild(new sts.ClassBodyContext(stsClassDecl, 0)); + return stsClassDecl; + } + + visitFunctionDeclaration(tsFunDecl: ts.FunctionDeclaration): sts.FunctionDeclarationContext { + let stsFunDecl = new sts.FunctionDeclarationContext(null, 0); + stsFunDecl.addChild(NodeBuilder.terminalNode(sts.StaticTSParser.Function)); + stsFunDecl.addChild(NodeBuilder.terminalIdentifier(tsFunDecl.name.text)); + stsFunDecl.addChild(this.translateSignature(tsFunDecl)); + + if (tsFunDecl.body) { + stsFunDecl.addChild(this.visitNode(tsFunDecl.body)); + } + + return stsFunDecl; + } + + translateSignature(tsFunLikeDecl: ts.FunctionLikeDeclarationBase): sts.SignatureContext { + let stsSignature = new sts.SignatureContext(null, 0); + + // TODO: Translate type parameters. + + let tsParameters = tsFunLikeDecl.parameters; + if (tsParameters && tsParameters.length > 0) { + let stsParameterList = new sts.ParameterListContext(null, 0); + for (const tsParameter of tsParameters) { + let stsParam = this.visitNode(tsParameter); + stsParameterList.addChild(stsParam); + } + stsSignature.addChild(stsParameterList); + } + + let stsTypeRef : NodeBuilder.STSTypeContext; + let tsRetType = tsFunLikeDecl.type; + if (tsRetType) { + stsTypeRef = this.translateType(tsRetType); + } else { + // TODO: Check the return type with TypeChecker. + stsTypeRef = NodeBuilder.unknownTypeReference(); + } + + stsSignature.addChild(NodeBuilder.typeAnnotation(stsTypeRef)); + + return stsSignature; + } + + visitParameter(tsParameter: ts.ParameterDeclaration): StaticTSContextBase { + let stsParam = new sts.ParameterContext(null, 0); + + let tsParamName = tsParameter.name; + if (ts.isIdentifier(tsParamName)) { + stsParam.addChild(NodeBuilder.terminalIdentifier(tsParamName.text)); + + let stsTypeRef : NodeBuilder.STSTypeContext; + let tsParamType = tsParameter.type; + if (tsParamType) { + stsTypeRef = this.translateType(tsParamType); + } else { + // TODO: Check the parameter type with TypeChecker. + stsTypeRef = NodeBuilder.unknownTypeReference(); + } + + stsParam.addChild(NodeBuilder.typeAnnotation(stsTypeRef)); + } else { + // TODO: Translate destructuring parameter. + } + + return stsParam; + } + + visitBlock(tsBlock: ts.Block): StaticTSContextBase { + let stsBlock = new sts.BlockContext(null, 0); + + for (const tsStmt of tsBlock.statements) { + // TODO: Translate statements. + } + + if (!TranslationUtils.isFunctionBlock(tsBlock)) { + // Block itself is a statement. Wrap it up with Statement context. + let stsBlockStmt = new sts.StatementContext(stsBlock, 0); + stsBlockStmt.addChild(stsBlock); + return stsBlockStmt; + } + + return stsBlock; + } + + translateType(tsType: ts.TypeNode): NodeBuilder.STSTypeContext { + switch (tsType.kind) { + case ts.SyntaxKind.NumberKeyword: + case ts.SyntaxKind.BooleanKeyword: + case ts.SyntaxKind.BigIntKeyword: + case ts.SyntaxKind.StringKeyword: + case ts.SyntaxKind.VoidKeyword: + return NodeBuilder.predefinedType(tsType); + case ts.SyntaxKind.AnyKeyword: + case ts.SyntaxKind.ObjectKeyword: + case ts.SyntaxKind.SymbolKeyword: + case ts.SyntaxKind.IntrinsicKeyword: + case ts.SyntaxKind.NeverKeyword: + case ts.SyntaxKind.UndefinedKeyword: + case ts.SyntaxKind.UnknownKeyword: + // TODO: for now, return __UnknownType__ for all of these type kinds. + return NodeBuilder.unknownTypeReference(); + default: + return (this.visitNode(tsType) as sts.TypeReferenceContext | sts.ArrayTypeContext); + } + } + + visitTypeReference(tsTypeRef: ts.TypeReferenceNode): sts.TypeReferenceContext { + let stsTypeRef = new sts.TypeReferenceContext(null, 0); + let stsTypeRefPart = NodeBuilder.typeReferencePart(tsTypeRef.typeName); + stsTypeRef.addChild(stsTypeRefPart); + // TODO: translate type arguments. + return stsTypeRef; + } +} \ No newline at end of file diff --git a/migrator/typescript/src/transpiler/TypeScriptTranspiler.ts b/migrator/typescript/src/transpiler/TypeScriptTranspiler.ts new file mode 100644 index 0000000000000000000000000000000000000000..7b9c05cbda6d8db77cea9366acba8c1d8ad5b31c --- /dev/null +++ b/migrator/typescript/src/transpiler/TypeScriptTranspiler.ts @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as ts from "typescript"; +import { writeFileSync } from "fs"; +import { TypeScriptTransformer } from "./TypeScriptTransformer"; + +function compile(fileNames: string[], options: ts.CompilerOptions): ts.Program { + let program = ts.createProgram(fileNames, options); + + // Log errors + let diagnostics = ts.getPreEmitDiagnostics(program); + diagnostics.forEach(diagnostic => { + if (diagnostic.file) { + let { line, character } = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start!); + let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); + console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); + } else { + console.log(ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")); + } + }); + + return program; +} + +function transpile() { + const inputFiles = process.argv.slice(2); + + const tsProgram = compile(inputFiles, { + noEmitOnError: true, + noImplicitAny: true, + target: ts.ScriptTarget.ES5, + module: ts.ModuleKind.CommonJS, + //skipLibCheck: true + }); + + // Retrieve AST for input files. + let tsSrcFiles = inputFiles.map((val, idx, array) => tsProgram.getSourceFile(val)); + + for(let tsSrcFile of tsSrcFiles) { + let transformer = new TypeScriptTransformer(tsSrcFile, tsProgram); + let stsCompUnit = transformer.transform(); + + let xmlString = stsCompUnit.toXML(); + let xmlFile = tsSrcFile.fileName + ".xml"; + writeFileSync(xmlFile, xmlString); + } +} + +transpile(); \ No newline at end of file diff --git a/migrator/typescript/tsconfig.json b/migrator/typescript/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..3a4babf5148f0988b2c9a26317af9d617a696640 --- /dev/null +++ b/migrator/typescript/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "target": "ES6", + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true, + "outDir": "build/javascript" + }, + + "include": ["build/typescript/**/*", "src/**/*"], +} \ No newline at end of file