traceur
Version:
ES6 to ES5 compiler
577 lines (522 loc) • 16.4 kB
JavaScript
// Copyright 2012 Traceur Authors.
//
// 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.
function enumerableOnlyObject(obj) {
let result = Object.create(null);
Object.keys(obj).forEach(function(key) {
Object.defineProperty(result, key, {enumerable: true, value: obj[key]});
});
return result;
}
// Traceur sets these default options and no others for v 0.1.*
export const optionsV01 = enumerableOnlyObject({
annotations: false,
arrayComprehension: false,
arrowFunctions: true,
asyncFunctions: false,
asyncGenerators: false,
blockBinding: true,
classes: true,
commentCallback: false,
computedPropertyNames: true,
debug: false,
debugNames: false,
defaultParameters: true,
destructuring: true,
exponentiation: false,
exportFromExtended: false,
forOf: true,
forOn: false,
freeVariableChecker: false,
generatorComprehension: false,
generators: true,
importRuntime: false,
inputSourceMap: false,
jsx: false,
lowResolutionSourceMap: false,
memberVariables: false,
moduleName: 'default',
modules: 'bootstrap',
numericLiterals: true,
outputLanguage: 'es5',
properTailCalls: false,
propertyMethods: true,
propertyNameShorthand: true,
referrer: '',
require: false,
restParameters: true,
script: false,
sourceMaps: false,
sourceRoot: false,
spread: true,
spreadProperties: false,
symbols: true,
templateLiterals: true,
types: false,
unicodeEscapeSequences: true,
unicodeExpressions: true,
validate: false,
});
export const versionLockedOptions = optionsV01;
// Options are just a plain old object. There are two read only views on this
// object, parseOptions and transformOptions.
//
// To set an option you do `options.classes = true`.
//
// An option value is either true, false or a string. If the value is set to
// the string "parse" then the transformOption for that option
// will return false. For example:
//
// options.destructuring = 'parse';
// options.parseOptions.destructuring === true;
// options.transformOptions.destructuring === false;
//
// This allows you to parse certain features without transforming them, leaving
// the syntax intact in the output.
let defaultValues = Object.create(null);
let featureOptions = Object.create(null);
let experimentalOptions = Object.create(null);
let moduleOptions =
['amd', 'commonjs', 'closure', 'instantiate', 'inline', 'bootstrap', 'parse'];
const EXPERIMENTAL = 0;
const ON_BY_DEFAULT = 1;
/**
* Adds a feature option. Feature options can be tested with parseOptions
* and transformOptions.
*/
function addFeatureOption(name, kind) {
featureOptions[name] = true;
if (kind === EXPERIMENTAL)
experimentalOptions[name] = true;
let defaultValue = kind === ON_BY_DEFAULT;
defaultValues[name] = defaultValue;
}
/**
* Adds a simple boolean option.
*/
function addBoolOption(name) {
defaultValues[name] = false;
}
// ON_BY_DEFAULT
addFeatureOption('arrowFunctions', ON_BY_DEFAULT); // 13.2
addFeatureOption('blockBinding', ON_BY_DEFAULT); // 12.1
addFeatureOption('classes', ON_BY_DEFAULT); // 13.5
addFeatureOption('computedPropertyNames', ON_BY_DEFAULT); // 11.1.5
addFeatureOption('defaultParameters', ON_BY_DEFAULT); // Cant find in the spec
addFeatureOption('destructuring', ON_BY_DEFAULT); // 11.13.1
addFeatureOption('forOf', ON_BY_DEFAULT); // 12.6.4
addFeatureOption('generators', ON_BY_DEFAULT); // 13.4
addFeatureOption('modules', 'SPECIAL'); // 14
addFeatureOption('numericLiterals', ON_BY_DEFAULT);
addFeatureOption('propertyMethods', ON_BY_DEFAULT); // 13.3
addFeatureOption('propertyNameShorthand', ON_BY_DEFAULT);
addFeatureOption('restParameters', ON_BY_DEFAULT); // 13.1
addFeatureOption('sourceMaps', 'SPECIAL');
addFeatureOption('spread', ON_BY_DEFAULT); // 11.1.4, 11.2.5
addFeatureOption('symbols', ON_BY_DEFAULT);
addFeatureOption('templateLiterals', ON_BY_DEFAULT); // 7.6.8
addFeatureOption('unicodeEscapeSequences', ON_BY_DEFAULT); // 11.8.4
addFeatureOption('unicodeExpressions', ON_BY_DEFAULT);
// EXPERIMENTAL due to performance impact although properly part of ES6
addFeatureOption('properTailCalls', EXPERIMENTAL);
// EXPERIMENTAL
addFeatureOption('annotations', EXPERIMENTAL);
addFeatureOption('arrayComprehension', EXPERIMENTAL); // 11.4.1.2
addFeatureOption('asyncFunctions', EXPERIMENTAL);
addFeatureOption('asyncGenerators', EXPERIMENTAL);
addFeatureOption('exponentiation', EXPERIMENTAL);
addFeatureOption('exportFromExtended', EXPERIMENTAL);
addFeatureOption('forOn', EXPERIMENTAL);
addFeatureOption('generatorComprehension', EXPERIMENTAL);
addFeatureOption('importRuntime', EXPERIMENTAL);
addFeatureOption('jsx', EXPERIMENTAL);
addFeatureOption('memberVariables', EXPERIMENTAL);
addFeatureOption('require', EXPERIMENTAL);
addFeatureOption('spreadProperties', EXPERIMENTAL);
addFeatureOption('types', EXPERIMENTAL);
let transformOptionsPrototype = {};
Object.keys(featureOptions).forEach((name) => {
Object.defineProperty(transformOptionsPrototype, name, {
get: function() {
let v = this.proxiedOptions_[name];
if (v === 'parse')
return false;
return v;
},
enumerable: true
});
});
let parseOptionsPrototype = {};
Object.keys(featureOptions).forEach((name) => {
Object.defineProperty(parseOptionsPrototype, name, {
get: function() {
return !!this.proxiedOptions_[name];
},
enumerable: true
});
});
addBoolOption('commentCallback');
addBoolOption('debug');
addBoolOption('debugNames');
addBoolOption('freeVariableChecker');
addBoolOption('script');
addBoolOption('validate');
export class Options {
constructor(options = Object.create(null)) {
this.reset();
// Make sure non option fields are non enumerable.
Object.defineProperties(this, {
modules_: {
value: versionLockedOptions.modules,
writable: true,
enumerable: false
},
sourceMaps_: {
value: versionLockedOptions.sourceMaps,
writable: true,
enumerable: false
},
sourceRoot_: {
value: versionLockedOptions.sourceRoot,
writable: true,
enumerable: false
},
transformOptions: {
value: Object.create(transformOptionsPrototype, {
proxiedOptions_: {
value: this,
enumerable: false
}}),
enumerable: false
},
parseOptions: {
value: Object.create(parseOptionsPrototype, {
proxiedOptions_: {
value: this,
enumerable: false
}}),
enumerable: false
}
});
this.setFromObject(options);
}
/**
* Meta option. Sets all options that are of Kind.experimental
* When getting this will return null if not all options of this kind
* have the same value.
* @type {boolean|string|null}
*/
set experimental(v) {
v = coerceOptionValue(v);
Object.keys(experimentalOptions).forEach((name) => {
this[name] = v;
});
}
get experimental() {
let value;
Object.keys(experimentalOptions).every((name) => {
let currentValue = this[name];
if (value === undefined) {
value = currentValue;
return true;
}
if (currentValue !== value) {
value = null;
return false;
}
return true;
});
return value;
}
/**
* @return {Options} with every experimental option set true.
*/
static experimental() {
return new Options(experimentalOptions);
}
get atscript() {
return this.types && this.annotations && this.memberVariables;
}
set atscript(value) {
this.types = value;
this.annotations = value;
this.memberVariables = value;
}
/**
* @return {Options} with every atScript option set true.
*/
static atscript() {
return new Options({types: true, annotations: true, memberVariables: true});
}
get modules() {
return this.modules_;
}
set modules(value) {
if (typeof value === 'boolean' && !value)
value = 'bootstrap';
if (moduleOptions.indexOf(value) === -1) {
throw new Error('Invalid \'modules\' option \'' + value + '\', not in ' +
moduleOptions.join(', '));
}
this.modules_ = value;
}
get sourceMaps() {
return this.sourceMaps_;
}
set sourceMaps(value) {
if (value === null || typeof value === 'boolean') {
this.sourceMaps_ = value ? 'file' : false;
return;
}
if (value === 'file' || value === 'inline' || value === 'memory') {
this.sourceMaps_ = value;
} else {
throw new Error('Option sourceMaps should be ' +
'[false|inline|file|memory], not ' + value);
}
}
/**
* Resets all options to the default value or to false if |allOff| is
* true.
* @param {boolean=} allOff
*/
reset(allOff = undefined) {
let useDefault = allOff === undefined;
Object.keys(defaultValues).forEach((name) => {
this[name] = useDefault && defaultValues[name];
});
this.setDefaults();
}
/**
* Set values for non-boolean options. Some non-boolean options allow
* boolean values.
*/
setDefaults() {
this.modules = 'bootstrap';
this.moduleName = 'default';
this.outputLanguage = 'es5';
this.referrer = '';
this.sourceMaps = false;
this.sourceRoot = false;
this.lowResolutionSourceMap = false;
this.inputSourceMap = false;
}
static listUnknownOptions(obj) {
let unknowns = [];
Object.keys(obj).forEach((propName) => {
if (!(propName in optionsV01)) {
unknowns.push(propName);
}
});
return unknowns;
}
/**
* Sets the options based on an object.
*/
setFromObject(object) {
Object.keys(this).forEach((name) => {
if (name in object)
this.setOption(name, object[name]);
});
this.modules = object.modules || this.modules;
if (typeof object.sourceMaps === 'boolean' ||
typeof object.sourceMaps === 'string') {
this.sourceMaps = object.sourceMaps;
}
if (object.sourceRoot !== undefined)
this.sourceRoot = object.sourceRoot;
return this;
}
setOption(name, value) {
name = toCamelCase(name);
if (name in this) {
this[name] = value;
} else {
throw Error('Unknown option: ' + name);
}
}
diff(ref) {
let mismatches = [];
Object.keys(this).forEach((key) => {
if (this[key] !== ref[key]) {
mismatches.push({
key: key,
now: $traceurRuntime.options[key],
v01: ref[key]
});
}
});
return mismatches;
}
};
// TODO: Refactor this so that we can keep all of these in one place.
let descriptions = {
experimental: 'Turns on all experimental features',
require: 'Generate require function argument for node when modules=register',
sourceMaps: 'Generate source map and (\'file\') write to .map' +
' or (\'inline\') append data URL',
};
export class CommandOptions extends Options {
/**
* Takes a string and parses it and sets the options based on that
* string.
*/
static fromString(s) {
return CommandOptions.fromArgv(s.split(/\s+/));
}
/**
* Takes an array of command line params and sets the options based on that.
*/
static fromArgv(args) {
let options = new CommandOptions();
args.forEach((arg) => options.parseCommand(arg));
return options;
}
/**
* Parses a part of a command line and sets the respective option.
* The following patterns are supported.
*
* --spread, --spread=true
* --spread=parse
* --spread=false
* --arrowFunctions --arrow-functions
* --modules=amd
*/
parseCommand(s) {
let re = /--([^=]+)(?:=(.+))?/;
let m = re.exec(s);
if (m)
this.setOptionCoerced(m[1], m[2]);
}
setOptionCoerced(name, value) {
// commander.js give value = null if no argument follows --option-name
if (typeof value !== 'undefined' && value !== null)
value = coerceOptionValue(value);
else
value = true;
this.setOption(name, value);
}
}
function coerceOptionValue(v) {
switch (v) {
case 'false':
return false;
case 'true':
case true:
return true;
default:
// Falsey values will be false.
return !!v && String(v);
}
}
/**
* Converts a string from aaa-bbb-ccc to aaaBbbCcc.
*/
function toCamelCase(s) {
return s.replace(/-\w/g, function(ch) {
return ch[1].toUpperCase();
});
}
/**
* Converts a string from aaaBbbCcc to aaa-bbb-ccc.
*/
export function toDashCase(s) {
return s.replace(/[A-Z]/g, function(ch) {
return '-' + ch.toLowerCase();
});
}
/**
* TODO(jjb): move to src/node
* This is called by build.js to add options to the commander command line
* library.
* @param {Commander} flags The commander object.
*/
export function addOptions(flags, commandOptions) {
// Start with the non-boolean options.
flags.option('--referrer <name>',
'Bracket output code with System.referrerName=<name>',
(name) => {
commandOptions.setOption('referrer', name);
System.map = System.semverMap(name);
return name;
});
flags.option('--modules <' + moduleOptions.join(', ') + '>',
'select the output format for modules',
(moduleFormat) => {
commandOptions.modules = moduleFormat;
});
flags.option('--moduleName [true|false|default]',
'true for named, false for anonymous modules; default depends on --modules',
(moduleName) => {
if (moduleName === 'true')
moduleName = true;
else if (moduleName === 'false')
moduleName = false;
else
moduleName = 'default';
commandOptions.moduleName = moduleName;
});
flags.option('--outputLanguage <es6|es5>',
'compilation target language',
(outputLanguage) => {
if (outputLanguage === 'es6' || outputLanguage === 'es5')
commandOptions.outputLanguage = outputLanguage;
else
throw new Error('outputLanguage must be one of es5, es6');
});
flags.option('--source-maps [file|inline|memory]',
'sourceMaps generated to file or inline with data: URL',
(to) => { return commandOptions.sourceMaps = to; }
);
flags.option('--source-root <true|false|string>',
'sourcemap sourceRoot value. false to omit, ' +
'true for directory of output file.',
(to) => {
if (to === 'false')
to = false;
else if (to === 'true')
to = true;
return commandOptions.sourceRoot = to;
}
);
flags.option('--low-resolution-source-maps',
'Lower sourceMaps granularity to one mapping per output line',
() => { return commandOptions.lowResolutionSourceMap = true; }
);
flags.option('--experimental',
'Turns on all experimental features',
() => { commandOptions.experimental = true; }
);
flags.option('--atscript',
'Turns on all AtScript features',
() => { commandOptions.atscript = true; }
);
Object.keys(commandOptions).forEach(function(name) {
let dashedName = toDashCase(name);
if (flags.optionFor('--' + name) || flags.optionFor('--' + dashedName)) {
return; // non-boolean already in flags.
} else if (name in featureOptions) {
flags.option('--' + dashedName + ' [true|false|parse]',
descriptions[name]);
flags.on(dashedName, (value) =>
commandOptions.setOptionCoerced(dashedName, value));
} else if (commandOptions[name] !== null) {
flags.option('--' + dashedName, descriptions[name]);
flags.on(dashedName, () => commandOptions.setOption(dashedName, true));
} else {
throw new Error('Unexpected null commandOption ' + name);
}
});
// After we've processed the commandOptions, set defaults for commandOptions.
commandOptions.setDefaults();
}