Skip to content

Commit d673417

Browse files
authored
Support alternative file extensions (#1231)
1 parent e257567 commit d673417

File tree

13 files changed

+116
-57
lines changed

13 files changed

+116
-57
lines changed

Diff for: cli/asc.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ interface CompilerOptions {
137137
printrtti?: boolean;
138138
/** Disables terminal colors. */
139139
noColors?: boolean;
140+
/** Specifies an alternative file extension. */
141+
extension?: string;
140142
}
141143

142144
/** Compiler API options. */

Diff for: cli/asc.js

+70-44
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ const EOL = process.platform === "win32" ? "\r\n" : "\n";
4242
const SEP = process.platform === "win32" ? "\\" : "/";
4343
const binaryen = global.binaryen || (global.binaryen = require("binaryen"));
4444

45+
// Sets up an extension with its definition counterpart and relevant regexes.
46+
function setupExtension(extension) {
47+
if (!extension.startsWith(".")) extension = "." + extension;
48+
return {
49+
ext: extension,
50+
ext_d: ".d" + extension,
51+
re: new RegExp("\\" + extension + "$"),
52+
re_d: new RegExp("\\.d\\" + extension + "$"),
53+
re_except_d: new RegExp("^(?!.*\\.d\\" + extension + "$).*\\" + extension + "$"),
54+
re_index: new RegExp("(?:^|[\\\\\\/])index\\" + extension + "$")
55+
};
56+
}
57+
58+
const defaultExtension = setupExtension(".ts");
59+
4560
// Proxy Binaryen's ready event
4661
Object.defineProperty(exports, "ready", {
4762
get: function() { return binaryen.ready; }
@@ -109,23 +124,23 @@ exports.defaultShrinkLevel = 1;
109124
exports.libraryFiles = exports.isBundle ? BUNDLE_LIBRARY : (() => { // set up if not a bundle
110125
const libDir = path.join(__dirname, "..", "std", "assembly");
111126
const bundled = {};
112-
find.files(libDir, find.TS_EXCEPT_DTS)
113-
.forEach(file => bundled[file.replace(/\.ts$/, "")] = fs.readFileSync(path.join(libDir, file), "utf8" ));
127+
find.files(libDir, defaultExtension.re_except_d)
128+
.forEach(file => bundled[file.replace(defaultExtension.re, "")] = fs.readFileSync(path.join(libDir, file), "utf8" ));
114129
return bundled;
115130
})();
116131

117132
/** Bundled definition files. */
118133
exports.definitionFiles = exports.isBundle ? BUNDLE_DEFINITIONS : (() => { // set up if not a bundle
119134
const stdDir = path.join(__dirname, "..", "std");
120135
return {
121-
"assembly": fs.readFileSync(path.join(stdDir, "assembly", "index.d.ts"), "utf8"),
122-
"portable": fs.readFileSync(path.join(stdDir, "portable", "index.d.ts"), "utf8")
136+
"assembly": fs.readFileSync(path.join(stdDir, "assembly", "index" + defaultExtension.ext_d), "utf8"),
137+
"portable": fs.readFileSync(path.join(stdDir, "portable", "index" + defaultExtension.ext_d), "utf8")
123138
};
124139
})();
125140

126141
/** Convenience function that parses and compiles source strings directly. */
127142
exports.compileString = (sources, options) => {
128-
if (typeof sources === "string") sources = { "input.ts": sources };
143+
if (typeof sources === "string") sources = { ["input" + defaultExtension.ext]: sources };
129144
const output = Object.create({
130145
stdout: createMemoryStream(),
131146
stderr: createMemoryStream()
@@ -169,6 +184,7 @@ exports.main = function main(argv, options, callback) {
169184
const writeFile = options.writeFile || writeFileNode;
170185
const listFiles = options.listFiles || listFilesNode;
171186
const stats = options.stats || createStats();
187+
let extension = defaultExtension;
172188

173189
// Output must be specified if not present in the environment
174190
if (!stdout) throw Error("'options.stdout' must be specified");
@@ -213,6 +229,15 @@ exports.main = function main(argv, options, callback) {
213229
return callback(null);
214230
}
215231

232+
// Use another extension if requested
233+
if (typeof args.extension === "string") {
234+
if (/^\.?[0-9a-zA-Z]{1,14}$/.test(args.extension)) {
235+
extension = setupExtension(args.extension);
236+
} else {
237+
return callback(Error("Invalid extension: " + args.extension));
238+
}
239+
}
240+
216241
// Print the help message if requested or no source files are provided
217242
if (args.help || !argv.length) {
218243
var out = args.help ? stdout : stderr;
@@ -222,9 +247,9 @@ exports.main = function main(argv, options, callback) {
222247
" " + color.cyan("asc") + " [entryFile ...] [options]",
223248
"",
224249
color.white("EXAMPLES"),
225-
" " + color.cyan("asc") + " hello.ts",
226-
" " + color.cyan("asc") + " hello.ts -b hello.wasm -t hello.wat",
227-
" " + color.cyan("asc") + " hello1.ts hello2.ts -b -O > hello.wasm",
250+
" " + color.cyan("asc") + " hello" + extension.ext,
251+
" " + color.cyan("asc") + " hello" + extension.ext + " -b hello.wasm -t hello.wat",
252+
" " + color.cyan("asc") + " hello1" + extension.ext + " hello2" + extension.ext + " -b -O > hello.wasm",
228253
"",
229254
color.white("OPTIONS"),
230255
].concat(
@@ -319,7 +344,7 @@ exports.main = function main(argv, options, callback) {
319344
let transformArgs = args.transform;
320345
for (let i = 0, k = transformArgs.length; i < k; ++i) {
321346
let filename = transformArgs[i].trim();
322-
if (!tsNodeRegistered && filename.endsWith('.ts')) {
347+
if (!tsNodeRegistered && filename.endsWith(".ts")) { // ts-node requires .ts specifically
323348
require("ts-node").register({ transpileOnly: true, skipProject: true, compilerOptions: { target: "ES2016" } });
324349
tsNodeRegistered = true;
325350
}
@@ -363,7 +388,7 @@ exports.main = function main(argv, options, callback) {
363388
if (libPath.indexOf("/") >= 0) return; // in sub-directory: imported on demand
364389
stats.parseCount++;
365390
stats.parseTime += measure(() => {
366-
assemblyscript.parse(program, exports.libraryFiles[libPath], exports.libraryPrefix + libPath + ".ts", false);
391+
assemblyscript.parse(program, exports.libraryFiles[libPath], exports.libraryPrefix + libPath + extension.ext, false);
367392
});
368393
});
369394
const customLibDirs = [];
@@ -374,7 +399,7 @@ exports.main = function main(argv, options, callback) {
374399
for (let i = 0, k = customLibDirs.length; i < k; ++i) { // custom
375400
let libDir = customLibDirs[i];
376401
let libFiles;
377-
if (libDir.endsWith(".ts")) {
402+
if (libDir.endsWith(extension.ext)) {
378403
libFiles = [ path.basename(libDir) ];
379404
libDir = path.dirname(libDir);
380405
} else {
@@ -385,7 +410,7 @@ exports.main = function main(argv, options, callback) {
385410
let libText = readFile(libPath, libDir);
386411
if (libText === null) return callback(Error("Library file '" + libPath + "' not found."));
387412
stats.parseCount++;
388-
exports.libraryFiles[libPath.replace(/\.ts$/, "")] = libText;
413+
exports.libraryFiles[libPath.replace(extension.re, "")] = libText;
389414
stats.parseTime += measure(() => {
390415
assemblyscript.parse(program, libText, exports.libraryPrefix + libPath, false);
391416
});
@@ -406,13 +431,13 @@ exports.main = function main(argv, options, callback) {
406431
const libraryPrefix = exports.libraryPrefix;
407432
const libraryFiles = exports.libraryFiles;
408433

409-
// Try file.ts, file/index.ts, file.d.ts
434+
// Try file.ext, file/index.ext, file.d.ext
410435
if (!internalPath.startsWith(libraryPrefix)) {
411-
if ((sourceText = readFile(sourcePath = internalPath + ".ts", baseDir)) == null) {
412-
if ((sourceText = readFile(sourcePath = internalPath + "/index.ts", baseDir)) == null) {
413-
// portable d.ts: uses the .js file next to it in JS or becomes an import in Wasm
414-
sourcePath = internalPath + ".ts";
415-
sourceText = readFile(internalPath + ".d.ts", baseDir);
436+
if ((sourceText = readFile(sourcePath = internalPath + extension.ext, baseDir)) == null) {
437+
if ((sourceText = readFile(sourcePath = internalPath + "/index" + extension.ext, baseDir)) == null) {
438+
// portable d.ext: uses the .js file next to it in JS or becomes an import in Wasm
439+
sourcePath = internalPath + extension.ext;
440+
sourceText = readFile(internalPath + extension.ext_d, baseDir);
416441
}
417442
}
418443

@@ -422,18 +447,18 @@ exports.main = function main(argv, options, callback) {
422447
const indexName = plainName + "/index";
423448
if (libraryFiles.hasOwnProperty(plainName)) {
424449
sourceText = libraryFiles[plainName];
425-
sourcePath = libraryPrefix + plainName + ".ts";
450+
sourcePath = libraryPrefix + plainName + extension.ext;
426451
} else if (libraryFiles.hasOwnProperty(indexName)) {
427452
sourceText = libraryFiles[indexName];
428-
sourcePath = libraryPrefix + indexName + ".ts";
453+
sourcePath = libraryPrefix + indexName + extension.ext;
429454
} else { // custom lib dirs
430455
for (const libDir of customLibDirs) {
431-
if ((sourceText = readFile(plainName + ".ts", libDir)) != null) {
432-
sourcePath = libraryPrefix + plainName + ".ts";
456+
if ((sourceText = readFile(plainName + extension.ext, libDir)) != null) {
457+
sourcePath = libraryPrefix + plainName + extension.ext;
433458
break;
434459
} else {
435-
if ((sourceText = readFile(indexName + ".ts", libDir)) != null) {
436-
sourcePath = libraryPrefix + indexName + ".ts";
460+
if ((sourceText = readFile(indexName + extension.ext, libDir)) != null) {
461+
sourcePath = libraryPrefix + indexName + extension.ext;
437462
break;
438463
}
439464
}
@@ -463,25 +488,25 @@ exports.main = function main(argv, options, callback) {
463488
try {
464489
let json = JSON.parse(jsonText);
465490
if (typeof json.ascMain === "string") {
466-
mainPath = json.ascMain.replace(/[\/\\]index\.ts$/, "");
491+
mainPath = json.ascMain.replace(extension.re_index, "");
467492
packageMains.set(packageName, mainPath);
468493
}
469494
} catch (e) { }
470495
}
471496
}
472497
const mainDir = path.join(currentPath, packageName, mainPath);
473498
const plainName = filePath;
474-
if ((sourceText = readFile(path.join(mainDir, plainName + ".ts"), baseDir)) != null) {
475-
sourcePath = libraryPrefix + packageName + "/" + plainName + ".ts";
476-
packageBases.set(sourcePath.replace(/\.ts$/, ""), path.join(currentPath, packageName));
477-
if (args.traceResolution) stderr.write(" -> " + path.join(mainDir, plainName + ".ts") + EOL);
499+
if ((sourceText = readFile(path.join(mainDir, plainName + extension.ext), baseDir)) != null) {
500+
sourcePath = libraryPrefix + packageName + "/" + plainName + extension.ext;
501+
packageBases.set(sourcePath.replace(extension.re, ""), path.join(currentPath, packageName));
502+
if (args.traceResolution) stderr.write(" -> " + path.join(mainDir, plainName + extension.ext) + EOL);
478503
break;
479504
} else if (!isPackageRoot) {
480505
const indexName = filePath + "/index";
481-
if ((sourceText = readFile(path.join(mainDir, indexName + ".ts"), baseDir)) !== null) {
482-
sourcePath = libraryPrefix + packageName + "/" + indexName + ".ts";
483-
packageBases.set(sourcePath.replace(/\.ts$/, ""), path.join(currentPath, packageName));
484-
if (args.traceResolution) stderr.write(" -> " + path.join(mainDir, indexName + ".ts") + EOL);
506+
if ((sourceText = readFile(path.join(mainDir, indexName + extension.ext), baseDir)) !== null) {
507+
sourcePath = libraryPrefix + packageName + "/" + indexName + extension.ext;
508+
packageBases.set(sourcePath.replace(extension.re, ""), path.join(currentPath, packageName));
509+
if (args.traceResolution) stderr.write(" -> " + path.join(mainDir, indexName + extension.ext) + EOL);
485510
break;
486511
}
487512
}
@@ -519,33 +544,34 @@ exports.main = function main(argv, options, callback) {
519544
let runtimeText = exports.libraryFiles[runtimePath];
520545
if (runtimeText == null) {
521546
runtimePath = runtimeName;
522-
runtimeText = readFile(runtimePath + ".ts", baseDir);
547+
runtimeText = readFile(runtimePath + extension.ext, baseDir);
523548
if (runtimeText == null) return callback(Error("Runtime '" + runtimeName + "' not found."));
524549
} else {
525550
runtimePath = "~lib/" + runtimePath;
526551
}
527552
stats.parseCount++;
528553
stats.parseTime += measure(() => {
529-
assemblyscript.parse(program, runtimeText, runtimePath, true);
554+
assemblyscript.parse(program, runtimeText, runtimePath + extension.ext, true);
530555
});
531556
}
532557

533558
// Include entry files
534559
for (let i = 0, k = argv.length; i < k; ++i) {
535560
const filename = argv[i];
536561

537-
let sourcePath = String(filename).replace(/\\/g, "/").replace(/(\.ts|\/)$/, "");
562+
let sourcePath = String(filename).replace(/\\/g, "/").replace(extension.re, "").replace(/[\\\/]$/, "");
563+
538564
// Setting the path to relative path
539565
sourcePath = path.isAbsolute(sourcePath) ? path.relative(baseDir, sourcePath) : sourcePath;
540566

541-
// Try entryPath.ts, then entryPath/index.ts
542-
let sourceText = readFile(sourcePath + ".ts", baseDir);
567+
// Try entryPath.ext, then entryPath/index.ext
568+
let sourceText = readFile(sourcePath + extension.ext, baseDir);
543569
if (sourceText == null) {
544-
sourceText = readFile(sourcePath + "/index.ts", baseDir);
545-
if (sourceText == null) return callback(Error("Entry file '" + sourcePath + ".ts' not found."));
546-
sourcePath += "/index.ts";
570+
sourceText = readFile(sourcePath + "/index" + extension.ext, baseDir);
571+
if (sourceText == null) return callback(Error("Entry file '" + sourcePath + extension.ext + "' not found."));
572+
sourcePath += "/index" + extension.ext;
547573
} else {
548-
sourcePath += ".ts";
574+
sourcePath += extension.ext;
549575
}
550576

551577
stats.parseCount++;
@@ -741,7 +767,7 @@ exports.main = function main(argv, options, callback) {
741767
map.sourceRoot = "./" + basename;
742768
let contents = [];
743769
map.sources.forEach((name, index) => {
744-
let text = assemblyscript.getSource(program, name.replace(/\.ts$/, ""));
770+
let text = assemblyscript.getSource(program, name.replace(extension.re, ""));
745771
if (text == null) return callback(Error("Source of file '" + name + "' not found."));
746772
contents[index] = text;
747773
});
@@ -882,7 +908,7 @@ exports.main = function main(argv, options, callback) {
882908
var files;
883909
try {
884910
stats.readTime += measure(() => {
885-
files = fs.readdirSync(path.join(baseDir, dirname)).filter(file => /^(?!.*\.d\.ts$).*\.ts$/.test(file));
911+
files = fs.readdirSync(path.join(baseDir, dirname)).filter(file => extension.re_except_d.test(file))
886912
});
887913
return files;
888914
} catch (e) {

Diff for: cli/asc.json

+4
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@
252252
"type": "s",
253253
"default": "."
254254
},
255+
"extension": {
256+
"description": "Specifies an alternative file extension to use.",
257+
"type": "s"
258+
},
255259
"noUnsafe": {
256260
"description": [
257261
"Disallows the use of unsafe features in user code.",

Diff for: cli/util/find.d.ts

-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,3 @@
44
*/
55

66
export function files(dirname: string, filter?: ((name: string) => bool) | RegExp): string[];
7-
export const TS: RegExp;
8-
export const TS_EXCEPT_DTS: RegExp;

Diff for: cli/util/find.js

-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,3 @@ function findFiles(dirname, filter) {
1919
}
2020

2121
exports.files = findFiles;
22-
23-
exports.TS = /\.ts$/;
24-
exports.TS_EXCEPT_DTS = /(?:(?!\.d).{2}|^.{0,1})\.ts$/;

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@
5555
"check": "npm run check:config && npm run check:compiler && tsc --noEmit --target ESNEXT --module commonjs --experimentalDecorators tests/require/index",
5656
"check:config": "tsc --noEmit -p src --diagnostics --listFiles",
5757
"check:compiler": "tslint -c tslint.json --project src --formatters-dir lib/lint/formatters --format as",
58-
"test": "npm run test:parser && npm run test:compiler && npm run test:packages",
58+
"test": "npm run test:parser && npm run test:compiler && npm run test:packages && npm run test:extension",
5959
"test:parser": "node tests/parser",
6060
"test:compiler": "node tests/compiler",
6161
"test:packages": "cd tests/packages && npm run test",
62+
"test:extension": "cd tests/extension && npm run test",
6263
"make": "npm run clean && npm test && npm run build && npm test",
6364
"all": "npm run check && npm run make",
6465
"docs": "typedoc --tsconfig tsconfig-docs.json --mode modules --name \"AssemblyScript Compiler API\" --out ./docs/api --ignoreCompilerErrors --excludeNotExported --excludePrivate --excludeExternals --exclude **/std/** --includeDeclarations --readme src/README.md",

Diff for: src/ast.ts

+18-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ import {
2929
import {
3030
normalizePath,
3131
resolvePath,
32-
CharCode
32+
CharCode,
33+
isTrivialAlphanum
3334
} from "./util";
3435

3536
/** Indicates the kind of a node. */
@@ -716,7 +717,7 @@ export abstract class Node {
716717
} else { // absolute
717718
if (!normalizedPath.startsWith(LIBRARY_PREFIX)) normalizedPath = LIBRARY_PREFIX + normalizedPath;
718719
}
719-
node.internalPath = mangleInternalPath(normalizedPath);
720+
node.internalPath = normalizedPath;
720721
} else {
721722
node.internalPath = null;
722723
}
@@ -804,7 +805,7 @@ export abstract class Node {
804805
} else { // absolute in library
805806
if (!normalizedPath.startsWith(LIBRARY_PREFIX)) normalizedPath = LIBRARY_PREFIX + normalizedPath;
806807
}
807-
node.internalPath = mangleInternalPath(normalizedPath);
808+
node.internalPath = normalizedPath;
808809
return node;
809810
}
810811

@@ -825,7 +826,7 @@ export abstract class Node {
825826
} else {
826827
if (!normalizedPath.startsWith(LIBRARY_PREFIX)) normalizedPath = LIBRARY_PREFIX + normalizedPath;
827828
}
828-
node.internalPath = mangleInternalPath(normalizedPath);
829+
node.internalPath = normalizedPath;
829830
return node;
830831
}
831832

@@ -2088,7 +2089,19 @@ export function findDecorator(kind: DecoratorKind, decorators: DecoratorNode[] |
20882089

20892090
/** Mangles an external to an internal path. */
20902091
export function mangleInternalPath(path: string): string {
2091-
if (path.endsWith(".ts")) path = path.substring(0, path.length - 3);
2092+
var pos = path.lastIndexOf(".");
2093+
var len = path.length;
2094+
if (pos >= 0 && len - pos >= 2) { // at least one char plus dot
2095+
let cur = pos;
2096+
while (++cur < len) {
2097+
if (!isTrivialAlphanum(path.charCodeAt(cur))) {
2098+
assert(false); // not a valid external path
2099+
return path;
2100+
}
2101+
}
2102+
return path.substring(0, pos);
2103+
}
2104+
assert(false); // not an external path
20922105
return path;
20932106
}
20942107

Diff for: src/parser.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export class Parser extends DiagnosticEmitter {
125125
/** Whether this is an entry file. */
126126
isEntry: bool
127127
): void {
128-
// the frontend gives us paths with .ts endings
128+
// the frontend gives us paths with file extensions
129129
var normalizedPath = normalizePath(path);
130130
var internalPath = mangleInternalPath(normalizedPath);
131131
// check if already processed

Diff for: src/program.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ export class Program extends DiagnosticEmitter {
532532
) {
533533
super(diagnostics);
534534
this.options = options;
535-
var nativeSource = new Source(LIBRARY_SUBST, "[native code]", SourceKind.LIBRARY_ENTRY);
535+
var nativeSource = new Source(LIBRARY_SUBST + ".wasm", "[native code]", SourceKind.LIBRARY_ENTRY);
536536
this.nativeSource = nativeSource;
537537
var nativeFile = new File(this, nativeSource);
538538
this.nativeFile = nativeFile;

0 commit comments

Comments
 (0)