added migrations for instant delivery

This commit is contained in:
Senad Uka
2015-07-26 19:10:16 +02:00
parent af6864954a
commit 68b25bf491
374 changed files with 135456 additions and 6 deletions

1
node_modules/react/node_modules/.bin/envify generated vendored Symbolic link
View File

@@ -0,0 +1 @@
../envify/bin/envify

2
node_modules/react/node_modules/envify/.npmignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
test.js
.travis.yml

145
node_modules/react/node_modules/envify/README.md generated vendored Normal file
View File

@@ -0,0 +1,145 @@
# envify [![Build Status](https://secure.travis-ci.org/hughsk/envify.png?branch=master)](http://travis-ci.org/hughsk/envify) [![stable](http://hughsk.github.io/stability-badges/dist/stable.svg)](http://github.com/hughsk/stability-badges) #
Selectively replace Node-style environment variables with plain strings.
Available as a standalone CLI tool and a
[Browserify](http://browserify.org) v2 transform.
Works best in combination with [uglifyify](http://github.com/hughsk/uglifyify).
## Installation ##
If you're using the module with Browserify:
``` bash
npm install envify browserify
```
Or, for the CLI:
``` bash
sudo npm install -g envify
```
## Usage ##
envify will replace your environment variable checks with ordinary strings -
only the variables you use will be included, so you don't have to worry about,
say, `AWS_SECRET_KEY` leaking through either. Take this example script:
``` javascript
if (process.env.NODE_ENV === "development") {
console.log('development only')
}
```
After running it through envify with `NODE_ENV` set to `production`, you'll
get this:
``` javascript
if ("production" === "development") {
console.log('development only')
}
```
By running this through a good minifier (e.g.
[UglifyJS2](https://github.com/mishoo/UglifyJS)), the above code would be
stripped out completely.
However, if you bundled the same script with `NODE_ENV` set to `development`:
``` javascript
if ("development" === "development") {
console.log('development only')
}
```
The `if` statement will evaluate to `true`, so the code won't be removed.
## CLI Usage ##
With browserify:
``` bash
browserify index.js -t envify > bundle.js
```
Or standalone:
``` bash
envify index.js > bundle.js
```
You can also specify additional custom environment variables using
browserify's [subarg](http://github.com/substack/subarg) syntax, which is
available in versions 3.25.0 and above:
``` bash
browserify index.js -t [ envify --NODE_ENV development ] > bundle.js
browserify index.js -t [ envify --NODE_ENV production ] > bundle.js
```
## Module Usage ##
**require('envify')**
Returns a transform stream that updates based on the Node process'
`process.env` object.
**require('envify/custom')([environment])**
If you want to stay away from your environment variables, you can supply
your own object to use in its place:
``` javascript
var browserify = require('browserify')
, envify = require('envify/custom')
, fs = require('fs')
var b = browserify('main.js')
, output = fs.createWriteStream('bundle.js')
b.transform(envify({
NODE_ENV: 'development'
}))
b.bundle().pipe(output)
```
## Purging `process.env` ##
By default, environment variables that are not defined will be left untouched.
This is because in some cases, you might want to run an envify transform over
your source more than once, and removing these values would make that
impossible.
However, if any references to `process.env` are remaining after transforming
your source with envify, browserify will automatically insert its shim for
Node's process object, which will increase the size of your bundle. This weighs
in at around 2KB, so if you're trying to be conservative with your bundle size
you can "purge" these remaining variables such that any missing ones are simply
replaced with undefined.
To do so through the command-line, simply use the subarg syntax and include
`purge` after `envify`, e.g.:
``` bash
browserify index.js -t [ envify purge --NODE_ENV development ]
```
Or if you're using the module API, you can pass `_: "purge"` into your
arguments like so:
``` javascript
b.transform(envify({
_: 'purge'
, NODE_ENV: 'development'
}))
```
## Contributors ##
* [hughsk](http://github.com/hughsk)
* [benjamn](http://github.com/benjamn)
* [zag2art](http://github.com/zag2art)
* [bjoerge](http://github.com/bjoerge)
* [andreypopp](http://github.com/andreypopp)
* [jupl](http://github.com/jupl)

17
node_modules/react/node_modules/envify/bin/envify generated vendored Executable file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env node
var envify = require('../')
, fs = require('fs')
if (process.argv[2]) {
fs.createReadStream(process.argv[2], { encoding: 'utf8' })
.pipe(envify(process.argv[2]))
.pipe(process.stdout)
} else {
process.stdin.resume()
process.stdin
.pipe(envify(__filename))
.pipe(process.stdout)
}

38
node_modules/react/node_modules/envify/custom.js generated vendored Normal file
View File

@@ -0,0 +1,38 @@
var through = require('through')
, jstransform = require('jstransform')
, createVisitors = require('./visitors')
var processEnvPattern = /\bprocess\.env\b/
module.exports = function(rootEnv) {
rootEnv = rootEnv || process.env || {}
return function envify(file, argv) {
if (/\.json$/.test(file)) return through()
var buffer = []
argv = argv || {}
return through(write, flush)
function write(data) {
buffer.push(data)
}
function flush() {
var source = buffer.join('')
if (processEnvPattern.test(source)) {
try {
var visitors = createVisitors([argv, rootEnv])
source = jstransform.transform(visitors, source).code
} catch(err) {
return this.emit('error', err)
}
}
this.queue(source)
this.queue(null)
}
}
}

1
node_modules/react/node_modules/envify/index.js generated vendored Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./custom')(process.env)

View File

@@ -0,0 +1,28 @@
{
"boss": true,
"curly": true,
"devel": false,
"eqnull": false,
"esnext": true,
"expr": true,
"funcscope": true,
"globals": {
"afterEach": true,
"beforeEach": true,
"describe": true,
"expect": true,
"it": true,
"jest": true
},
"globalstrict": false,
"laxbreak": true,
"loopfunc": false,
"newcap": true,
"node": true,
"noempty": true,
"nonstandard": false,
"onecase": false,
"trailing": true,
"undef": true,
"unused": "vars"
}

View File

@@ -0,0 +1,4 @@
.DS_STORE
node_modules
*.swp
*~

View File

@@ -0,0 +1,8 @@
language: node_js
node_js:
- "0.10"
# Because of package.json dependency versions that start with '^', it's not
# possible to `npm install` using the version of npm packaged with node 0.8
# As a result, travis *always* fails for 0.8 now -- and thus it's not possible
# to test jstransform on 0.8 with Travis CI :(
# - "0.8"

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@@ -0,0 +1,98 @@
# JSTransform [![Build Status](https://travis-ci.org/facebook/jstransform.svg?branch=master)](https://travis-ci.org/facebook/jstransform)
A simple utility for pluggable JS syntax transforms using the esprima parser.
* Makes it simple to write and plug-in syntax transformations
* Makes it simple to coalesce multiple syntax transformations in a single pass of the AST
* Gives complete control over the formatting of the output on a per-transformation basis
* Supports source map generation
* Comes pre-bundled with a small set of (optional) ES6 -> ES5 transforms
NOTE: If you're looking for a library for writing new greenfield JS transformations, consider looking at the [Recast](https://github.com/benjamn/recast) library instead of jstransform. We are still actively supporting jstransform (and intend to for the foreseeable future), but longer term we would like to direct efforts toward Recast. Recast does a far better job of supporting a multi-pass JS transformation pipeline, and this is important when attempting to apply many transformations to a source file.
## Examples
Using a pre-bundled or existing transform:
```js
/**
* Reads a source file that may (or may not) contain ES6 classes, transforms it
* to ES5 compatible code using the pre-bundled ES6 class visitors, and prints
* out the result.
*/
var es6ClassVisitors = require('jstransform/visitors/es6-class-visitors').visitorList;
var fs = require('fs');
var jstransform = require('jstransform');
var originalFileContents = fs.readFileSync('path/to/original/file.js', 'utf-8');
var transformedFileData = jstransform.transform(
es6ClassVisitors,
originalFileContents
);
console.log(transformedFileData.code);
```
Using multiple pre-bundled or existing transforms at once:
```js
/**
* Reads a source file that may (or may not) contain ES6 classes *or* arrow
* functions, transforms them to ES5 compatible code using the pre-bundled ES6
* visitors, and prints out the result.
*/
var es6ArrowFuncVisitors = require('jstransform/visitors/es6-arrow-function-visitors').visitorList;
var es6ClassVisitors = require('jstransform/visitors/es6-class-visitors').visitorList;
var jstransform = require('jstransform');
// Normally you'd read this from the filesystem, but I'll just use a string here
// to simplify the example.
var originalFileContents = "var a = (param1) => param1; class FooClass {}";
var transformedFileData = jstransform.transform(
es6ClassVisitors.concat(es6ArrowFuncVisitors),
originalFileContents
);
// var a = function(param1) {return param1;}; function FooClass(){"use strict";}
console.log(transformedFileData.code);
```
Writing a simple custom transform:
```js
/**
* Creates a custom transformation visitor that prefixes all calls to the
* `eval()` function with a call to `alert()` saying how much of a clown you are
* for using eval.
*/
var jstransform = require('jstransform');
var utils = require('jstransform/src/utils');
var Syntax = jstransform.Syntax;
function visitEvalCallExpressions(traverse, node, path, state) {
// Appends an alert() call to the output buffer *before* the visited node
// (in this case the eval call) is appended to the output buffer
utils.append('alert("...eval?...really?...");', state);
// Now we copy the eval expression to the output buffer from the original
// source
utils.catchup(node.range[1], state);
}
visitEvalCallExpressions.test = function(node, path, state) {
return node.type === Syntax.CallExpression
&& node.callee.type === Syntax.Identifier
&& node.callee.name === 'eval';
};
// Normally you'd read this from the filesystem, but I'll just use a string here
// to simplify the example.
var originalFileContents = "eval('foo');";
var transformedFileData = jstransform.transform(
[visitEvalCallExpressions], // Multiple visitors may be applied at once, so an
// array is always expected for the first argument
originalFileContents
);
// alert("...eval?...really?...");eval('foo');
console.log(transformedFileData.code);
```

View File

@@ -0,0 +1 @@
require.requireActual('./polyfill/Object.es6.js');

View File

@@ -0,0 +1,13 @@
var jstransform = require('./src/jstransform');
var arrowFuncVisitors = require('./visitors/es6-arrow-function-visitors');
var restParamVisitors = require('./visitors/es6-rest-param-visitors');
var es7SpreadPropertyVisitors = require('./visitors/es7-spread-property-visitors');
exports.process = function(sourceText, sourcePath) {
return jstransform.transform(
arrowFuncVisitors.visitorList
.concat(restParamVisitors.visitorList)
.concat(es7SpreadPropertyVisitors.visitorList),
sourceText
).code;
};

View File

@@ -0,0 +1 @@
../esprima-fb/bin/esparse.js

View File

@@ -0,0 +1 @@
../esprima-fb/bin/esvalidate.js

View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.6
- 0.8

View File

@@ -0,0 +1,20 @@
Copyright (c) 2012 Andrew Nesbitt
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,30 @@
# Base62.js
[![build status](https://secure.travis-ci.org/andrew/base62.js.png)](http://travis-ci.org/andrew/base62.js)
A javascript Base62 encode/decoder for node.js
## Install
npm install base62
## Usage
Base62 = require('base62')
Base62.encode(999) // 'g7'
Base62.decode('g7') // 999
## Development
Source hosted at [GitHub](http://github.com/andrew/base62.js).
Report Issues/Feature requests on [GitHub Issues](http://github.com/andrew/split/base62.js).
### Note on Patches/Pull Requests
* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a future version unintentionally.
* Send me a pull request. Bonus points for topic branches.
## Copyright
Copyright (c) 2012 Andrew Nesbitt. See [LICENSE](https://github.com/andrew/base62.js/blob/master/LICENSE) for details.

View File

@@ -0,0 +1,27 @@
var Base62 = (function (my) {
my.chars = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
my.encode = function(i){
if (i === 0) {return '0'}
var s = ''
while (i > 0) {
s = this.chars[i % 62] + s
i = Math.floor(i/62)
}
return s
};
my.decode = function(a,b,c,d){
for (
b = c = (
a === (/\W|_|^$/.test(a += "") || a)
) - 1;
d = a.charCodeAt(c++);
)
b = b * 62 + d - [, 48, 29, 87][d >> 5];
return b
};
return my;
}({}));
module.exports = Base62

View File

@@ -0,0 +1,52 @@
{
"author": {
"name": "Andrew Nesbitt",
"email": "andrewnez@gmail.com",
"url": "http://andrew-nesbitt.com/"
},
"name": "base62",
"description": "Javascript Base62 encode/decoder",
"keywords": [
"base-62"
],
"version": "0.1.1",
"repository": {
"type": "git",
"url": "git://github.com/andrew/base62.js.git"
},
"main": "base62.js",
"engines": {
"node": "*"
},
"scripts": {
"test": "mocha test"
},
"devDependencies": {
"mocha": "1.7.x"
},
"_id": "base62@0.1.1",
"dist": {
"shasum": "7b4174c2f94449753b11c2651c083da841a7b084",
"tarball": "http://registry.npmjs.org/base62/-/base62-0.1.1.tgz"
},
"_npmVersion": "1.1.65",
"_npmUser": {
"name": "andrewnez",
"email": "andrewnez@gmail.com"
},
"maintainers": [
{
"name": "andrewnez",
"email": "andrewnez@gmail.com"
}
],
"directories": {},
"_shasum": "7b4174c2f94449753b11c2651c083da841a7b084",
"_resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz",
"_from": "base62@0.1.1",
"bugs": {
"url": "https://github.com/andrew/base62.js/issues"
},
"readme": "ERROR: No README data found!",
"homepage": "https://github.com/andrew/base62.js#readme"
}

View File

@@ -0,0 +1,14 @@
var assert = require('assert')
var Base62 = require('../base62')
describe("encode", function() {
it("should encode a number to a Base62 string", function() {
assert.equal(Base62.encode(999), 'g7')
});
});
describe("decode", function() {
it("should decode a number from a Base62 string", function() {
assert.equal(Base62.decode('g7'), 999)
});
});

View File

@@ -0,0 +1,32 @@
**Esprima** ([esprima.org](http://esprima.org), BSD license) is a high performance,
standard-compliant [ECMAScript](http://www.ecma-international.org/publications/standards/Ecma-262.htm)
parser written in ECMAScript (also popularly known as
[JavaScript](http://en.wikipedia.org/wiki/JavaScript>JavaScript)).
Esprima is created and maintained by [Ariya Hidayat](http://twitter.com/ariyahidayat),
with the help of [many contributors](https://github.com/ariya/esprima/contributors).
**Esprima-FB** is a fork of the [Harmony branch](https://github.com/ariya/esprima/tree/harmony) of Esprima that implements [JSX specification](https://github.com/facebook/jsx) on top of ECMAScript syntax.
### Features
- Full support for ECMAScript 5.1 ([ECMA-262](http://www.ecma-international.org/publications/standards/Ecma-262.htm))
- Experimental support for ES6/Harmony (module, class, destructuring, ...)
- Full support for [JSX syntax extensions](https://github.com/facebook/jsx).
- Sensible [syntax tree format](https://github.com/facebook/jsx/blob/master/AST.md) compatible with Mozilla
[Parser AST](https://developer.mozilla.org/en/SpiderMonkey/Parser_API)
- Optional tracking of syntax node location (index-based and line-column)
- Heavily tested (> 650 [unit tests](http://esprima.org/test/) with [full code coverage](http://esprima.org/test/coverage.html))
- Ongoing support for ES6/Harmony (module, class, destructuring, ...)
### Versioning rules
In order to follow semver rules and keep reference to original Esprima versions at the same time, we left 3 digits of each version part to refer to upstream harmony branch. We then take the most significant digit.
**Example:** 4001.3001.0000-dev-harmony-fb aligns with 1.1.0-dev-harmony (aka 001.001.000-dev-harmony) in upstream, with our own changes on top.
Esprima-FB serves as a **building block** for JSX language tools and transpiler implementations (such as [React](https://github.com/facebook/react) or [JSXDOM](https://github.com/vjeux/jsxdom)).
Esprima-FB runs on many popular web browsers, as well as other ECMAScript platforms such as
[Rhino](http://www.mozilla.org/rhino) and [Node.js](https://npmjs.org/package/esprima).
For more information on original Esprima, check the web site [esprima.org](http://esprima.org).

View File

@@ -0,0 +1,117 @@
#!/usr/bin/env node
/*
Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint sloppy:true node:true rhino:true */
var fs, esprima, fname, content, options, syntax;
if (typeof require === 'function') {
fs = require('fs');
esprima = require('esprima');
} else if (typeof load === 'function') {
try {
load('esprima.js');
} catch (e) {
load('../esprima.js');
}
}
// Shims to Node.js objects when running under Rhino.
if (typeof console === 'undefined' && typeof process === 'undefined') {
console = { log: print };
fs = { readFileSync: readFile };
process = { argv: arguments, exit: quit };
process.argv.unshift('esparse.js');
process.argv.unshift('rhino');
}
function showUsage() {
console.log('Usage:');
console.log(' esparse [options] file.js');
console.log();
console.log('Available options:');
console.log();
console.log(' --comment Gather all line and block comments in an array');
console.log(' --loc Include line-column location info for each syntax node');
console.log(' --range Include index-based range for each syntax node');
console.log(' --raw Display the raw value of literals');
console.log(' --tokens List all tokens in an array');
console.log(' --tolerant Tolerate errors on a best-effort basis (experimental)');
console.log(' -v, --version Shows program version');
console.log();
process.exit(1);
}
if (process.argv.length <= 2) {
showUsage();
}
options = {};
process.argv.splice(2).forEach(function (entry) {
if (entry === '-h' || entry === '--help') {
showUsage();
} else if (entry === '-v' || entry === '--version') {
console.log('ECMAScript Parser (using Esprima version', esprima.version, ')');
console.log();
process.exit(0);
} else if (entry === '--comment') {
options.comment = true;
} else if (entry === '--loc') {
options.loc = true;
} else if (entry === '--range') {
options.range = true;
} else if (entry === '--raw') {
options.raw = true;
} else if (entry === '--tokens') {
options.tokens = true;
} else if (entry === '--tolerant') {
options.tolerant = true;
} else if (entry.slice(0, 2) === '--') {
console.log('Error: unknown option ' + entry + '.');
process.exit(1);
} else if (typeof fname === 'string') {
console.log('Error: more than one input file.');
process.exit(1);
} else {
fname = entry;
}
});
if (typeof fname !== 'string') {
console.log('Error: no input file.');
process.exit(1);
}
try {
content = fs.readFileSync(fname, 'utf-8');
syntax = esprima.parse(content, options);
console.log(JSON.stringify(syntax, null, 4));
} catch (e) {
console.log('Error: ' + e.message);
process.exit(1);
}

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env node
/*
Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint sloppy:true plusplus:true node:true rhino:true */
/*global phantom:true */
var fs, system, esprima, options, fnames, count;
if (typeof esprima === 'undefined') {
// PhantomJS can only require() relative files
if (typeof phantom === 'object') {
fs = require('fs');
system = require('system');
esprima = require('./esprima');
} else if (typeof require === 'function') {
fs = require('fs');
esprima = require('esprima');
} else if (typeof load === 'function') {
try {
load('esprima.js');
} catch (e) {
load('../esprima.js');
}
}
}
// Shims to Node.js objects when running under PhantomJS 1.7+.
if (typeof phantom === 'object') {
fs.readFileSync = fs.read;
process = {
argv: [].slice.call(system.args),
exit: phantom.exit
};
process.argv.unshift('phantomjs');
}
// Shims to Node.js objects when running under Rhino.
if (typeof console === 'undefined' && typeof process === 'undefined') {
console = { log: print };
fs = { readFileSync: readFile };
process = { argv: arguments, exit: quit };
process.argv.unshift('esvalidate.js');
process.argv.unshift('rhino');
}
function showUsage() {
console.log('Usage:');
console.log(' esvalidate [options] file.js');
console.log();
console.log('Available options:');
console.log();
console.log(' --format=type Set the report format, plain (default) or junit');
console.log(' -v, --version Print program version');
console.log();
process.exit(1);
}
if (process.argv.length <= 2) {
showUsage();
}
options = {
format: 'plain'
};
fnames = [];
process.argv.splice(2).forEach(function (entry) {
if (entry === '-h' || entry === '--help') {
showUsage();
} else if (entry === '-v' || entry === '--version') {
console.log('ECMAScript Validator (using Esprima version', esprima.version, ')');
console.log();
process.exit(0);
} else if (entry.slice(0, 9) === '--format=') {
options.format = entry.slice(9);
if (options.format !== 'plain' && options.format !== 'junit') {
console.log('Error: unknown report format ' + options.format + '.');
process.exit(1);
}
} else if (entry.slice(0, 2) === '--') {
console.log('Error: unknown option ' + entry + '.');
process.exit(1);
} else {
fnames.push(entry);
}
});
if (fnames.length === 0) {
console.log('Error: no input file.');
process.exit(1);
}
if (options.format === 'junit') {
console.log('<?xml version="1.0" encoding="UTF-8"?>');
console.log('<testsuites>');
}
count = 0;
fnames.forEach(function (fname) {
var content, timestamp, syntax, name;
try {
content = fs.readFileSync(fname, 'utf-8');
if (content[0] === '#' && content[1] === '!') {
content = '//' + content.substr(2, content.length);
}
timestamp = Date.now();
syntax = esprima.parse(content, { tolerant: true });
if (options.format === 'junit') {
name = fname;
if (name.lastIndexOf('/') >= 0) {
name = name.slice(name.lastIndexOf('/') + 1);
}
console.log('<testsuite name="' + fname + '" errors="0" ' +
' failures="' + syntax.errors.length + '" ' +
' tests="' + syntax.errors.length + '" ' +
' time="' + Math.round((Date.now() - timestamp) / 1000) +
'">');
syntax.errors.forEach(function (error) {
var msg = error.message;
msg = msg.replace(/^Line\ [0-9]*\:\ /, '');
console.log(' <testcase name="Line ' + error.lineNumber + ': ' + msg + '" ' +
' time="0">');
console.log(' <error type="SyntaxError" message="' + error.message + '">' +
error.message + '(' + name + ':' + error.lineNumber + ')' +
'</error>');
console.log(' </testcase>');
});
console.log('</testsuite>');
} else if (options.format === 'plain') {
syntax.errors.forEach(function (error) {
var msg = error.message;
msg = msg.replace(/^Line\ [0-9]*\:\ /, '');
msg = fname + ':' + error.lineNumber + ': ' + msg;
console.log(msg);
++count;
});
}
} catch (e) {
++count;
if (options.format === 'junit') {
console.log('<testsuite name="' + fname + '" errors="1" failures="0" tests="1" ' +
' time="' + Math.round((Date.now() - timestamp) / 1000) + '">');
console.log(' <testcase name="' + e.message + '" ' + ' time="0">');
console.log(' <error type="ParseError" message="' + e.message + '">' +
e.message + '(' + fname + ((e.lineNumber) ? ':' + e.lineNumber : '') +
')</error>');
console.log(' </testcase>');
console.log('</testsuite>');
} else {
console.log('Error: ' + e.message);
}
}
});
if (options.format === 'junit') {
console.log('</testsuites>');
}
if (count > 0) {
process.exit(1);
}
if (count === 0 && typeof phantom === 'object') {
process.exit(0);
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,94 @@
{
"name": "esprima-fb",
"description": "Facebook-specific fork of the esprima project",
"homepage": "https://github.com/facebook/esprima/tree/fb-harmony",
"main": "esprima.js",
"bin": {
"esparse": "./bin/esparse.js",
"esvalidate": "./bin/esvalidate.js"
},
"version": "13001.1001.0-dev-harmony-fb",
"files": [
"bin",
"test/run.js",
"test/runner.js",
"test/test.js",
"test/compat.js",
"test/reflect.js",
"esprima.js"
],
"engines": {
"node": ">=0.4.0"
},
"author": {
"name": "Ariya Hidayat",
"email": "ariya.hidayat@gmail.com"
},
"maintainers": [
{
"name": "jeffmo",
"email": "jeff@anafx.com"
},
{
"name": "zpao",
"email": "paul@oshannessy.com"
},
{
"name": "gabelevi",
"email": "gabelevi@gmail.com"
}
],
"repository": {
"type": "git",
"url": "http://github.com/facebook/esprima.git"
},
"bugs": {
"url": "http://issues.esprima.org"
},
"licenses": [
{
"type": "BSD",
"url": "http://github.com/facebook/esprima/raw/master/LICENSE.BSD"
}
],
"devDependencies": {
"eslint": "~0.12.0",
"jscs": "~1.10.0",
"istanbul": "~0.2.6",
"escomplex-js": "1.0.0",
"complexity-report": "~1.1.1",
"regenerate": "~0.5.4",
"unicode-6.3.0": "~0.1.0",
"json-diff": "~0.3.1",
"commander": "~2.5.0"
},
"scripts": {
"generate-regex": "node tools/generate-identifier-regex.js",
"test": "node test/run.js && npm run lint && npm run coverage",
"lint": "npm run check-version && npm run eslint && npm run jscs && npm run complexity",
"check-version": "node tools/check-version.js",
"jscs": "jscs esprima.js test/*test.js",
"eslint": "node node_modules/eslint/bin/eslint.js esprima.js",
"complexity": "node tools/list-complexity.js && cr -s -l -w --maxcyc 18 esprima.js",
"coverage": "npm run analyze-coverage && npm run check-coverage",
"analyze-coverage": "node node_modules/istanbul/lib/cli.js cover test/runner.js",
"check-coverage": "node node_modules/istanbul/lib/cli.js check-coverage --statement 100 --branch 100 --function 100",
"benchmark": "node test/benchmarks.js",
"benchmark-quick": "node test/benchmarks.js quick"
},
"gitHead": "a3ae0c054867db61166dd72affc579710b36476d",
"_id": "esprima-fb@13001.1001.0-dev-harmony-fb",
"_shasum": "633acdb40d9bd4db8a1c1d68c06a942959fad2b0",
"_from": "esprima-fb@13001.1001.0-dev-harmony-fb",
"_npmVersion": "1.4.16",
"_npmUser": {
"name": "gabelevi",
"email": "gabelevi@gmail.com"
},
"dist": {
"shasum": "633acdb40d9bd4db8a1c1d68c06a942959fad2b0",
"tarball": "http://registry.npmjs.org/esprima-fb/-/esprima-fb-13001.1001.0-dev-harmony-fb.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-13001.1001.0-dev-harmony-fb.tgz"
}

View File

@@ -0,0 +1,244 @@
/*
Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint node: true */
/*global document: true, window:true, esprima: true, testReflect: true */
var runTests;
function getContext(esprima, reportCase, reportFailure) {
'use strict';
var Reflect, Pattern;
// Maps Mozilla Reflect object to our Esprima parser.
Reflect = {
parse: function (code) {
var result;
reportCase(code);
try {
result = esprima.parse(code);
} catch (error) {
result = error;
}
return result;
}
};
// This is used by Reflect test suite to match a syntax tree.
Pattern = function (obj) {
var pattern;
// Poor man's deep object cloning.
pattern = JSON.parse(JSON.stringify(obj));
// Special handling for regular expression literal since we need to
// convert it to a string literal, otherwise it will be decoded
// as object "{}" and the regular expression would be lost.
if (obj.type && obj.type === 'Literal') {
if (obj.value instanceof RegExp) {
pattern = {
type: obj.type,
value: obj.value.toString()
};
}
}
// Special handling for branch statement because SpiderMonkey
// prefers to put the 'alternate' property before 'consequent'.
if (obj.type && obj.type === 'IfStatement') {
pattern = {
type: pattern.type,
test: pattern.test,
consequent: pattern.consequent,
alternate: pattern.alternate
};
}
// Special handling for do while statement because SpiderMonkey
// prefers to put the 'test' property before 'body'.
if (obj.type && obj.type === 'DoWhileStatement') {
pattern = {
type: pattern.type,
body: pattern.body,
test: pattern.test
};
}
function adjustRegexLiteralAndRaw(key, value) {
if (key === 'value' && value instanceof RegExp) {
value = value.toString();
} else if (key === 'raw' && typeof value === "string") {
// Ignore Esprima-specific 'raw' property.
return undefined;
} else if (key === 'regex' && typeof value === "object") {
// Ignore Esprima-specific 'regex' property.
return undefined;
}
return value;
}
if (obj.type && (obj.type === 'Program')) {
pattern.assert = function (tree) {
var actual, expected;
actual = JSON.stringify(tree, adjustRegexLiteralAndRaw, 4);
expected = JSON.stringify(obj, null, 4);
if (expected !== actual) {
reportFailure(expected, actual);
}
};
}
return pattern;
};
return {
Reflect: Reflect,
Pattern: Pattern
};
}
if (typeof window !== 'undefined') {
// Run all tests in a browser environment.
runTests = function () {
'use strict';
var total = 0,
failures = 0;
function setText(el, str) {
if (typeof el.innerText === 'string') {
el.innerText = str;
} else {
el.textContent = str;
}
}
function reportCase(code) {
var report, e;
report = document.getElementById('report');
e = document.createElement('pre');
e.setAttribute('class', 'code');
setText(e, code);
report.appendChild(e);
total += 1;
}
function reportFailure(expected, actual) {
var report, e;
failures += 1;
report = document.getElementById('report');
e = document.createElement('p');
setText(e, 'Expected');
report.appendChild(e);
e = document.createElement('pre');
e.setAttribute('class', 'expected');
setText(e, expected);
report.appendChild(e);
e = document.createElement('p');
setText(e, 'Actual');
report.appendChild(e);
e = document.createElement('pre');
e.setAttribute('class', 'actual');
setText(e, actual);
report.appendChild(e);
}
setText(document.getElementById('version'), esprima.version);
window.setTimeout(function () {
var tick, context = getContext(esprima, reportCase, reportFailure);
tick = new Date();
testReflect(context.Reflect, context.Pattern);
tick = (new Date()) - tick;
if (failures > 0) {
document.getElementById('status').className = 'alert-box alert';
setText(document.getElementById('status'), total + ' tests. ' +
'Failures: ' + failures + '. ' + tick + ' ms');
} else {
document.getElementById('status').className = 'alert-box success';
setText(document.getElementById('status'), total + ' tests. ' +
'No failure. ' + tick + ' ms');
}
}, 11);
};
} else {
(function (global) {
'use strict';
var esprima = require('../esprima'),
tick,
total = 0,
failures = [],
header,
current,
context;
function reportCase(code) {
total += 1;
current = code;
}
function reportFailure(expected, actual) {
failures.push({
source: current,
expected: expected.toString(),
actual: actual.toString()
});
}
context = getContext(esprima, reportCase, reportFailure);
tick = new Date();
require('./reflect').testReflect(context.Reflect, context.Pattern);
tick = (new Date()) - tick;
header = total + ' tests. ' + failures.length + ' failures. ' +
tick + ' ms';
if (failures.length) {
console.error(header);
failures.forEach(function (failure) {
console.error(failure.source + ': Expected\n ' +
failure.expected.split('\n').join('\n ') +
'\nto match\n ' + failure.actual);
});
} else {
console.log(header);
}
process.exit(failures.length === 0 ? 0 : 1);
}(this));
}
/* vim: set sw=4 ts=4 et tw=80 : */

View File

@@ -0,0 +1,421 @@
// This is modified from Mozilla Reflect.parse test suite (the file is located
// at js/src/tests/js1_8_5/extensions/reflect-parse.js in the source tree).
//
// Some notable changes:
// * Removed unsupported features (destructuring, let, comprehensions...).
// * Removed tests for E4X (ECMAScript for XML).
// * Removed everything related to builder.
// * Enclosed every 'Pattern' construct with a scope.
// * Removed the test for bug 632030 and bug 632024.
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
(function (exports) {
function testReflect(Reflect, Pattern) {
function program(elts) { return Pattern({ type: "Program", body: elts }) }
function exprStmt(expr) { return Pattern({ type: "ExpressionStatement", expression: expr }) }
function throwStmt(expr) { return Pattern({ type: "ThrowStatement", argument: expr }) }
function returnStmt(expr) { return Pattern({ type: "ReturnStatement", argument: expr }) }
function yieldExpr(expr) { return Pattern({ type: "YieldExpression", argument: expr }) }
function lit(val) { return Pattern({ type: "Literal", value: val }) }
var thisExpr = Pattern({ type: "ThisExpression" });
function funDecl(id, params, body) { return Pattern({ type: "FunctionDeclaration",
id: id,
params: params,
defaults: [],
body: body,
rest: null,
generator: false,
expression: false
}) }
function genFunDecl(id, params, body) { return Pattern({ type: "FunctionDeclaration",
id: id,
params: params,
defaults: [],
body: body,
rest: null,
generator: true,
expression: false
}) }
function declarator(id, init) { return Pattern({ type: "VariableDeclarator", id: id, init: init }) }
function varDecl(decls) { return Pattern({ type: "VariableDeclaration", declarations: decls, kind: "var" }) }
function letDecl(decls) { return Pattern({ type: "VariableDeclaration", declarations: decls, kind: "let" }) }
function constDecl(decls) { return Pattern({ type: "VariableDeclaration", declarations: decls, kind: "const" }) }
function ident(name) { return Pattern({ type: "Identifier", name: name }) }
function dotExpr(obj, id) { return Pattern({ type: "MemberExpression", computed: false, object: obj, property: id }) }
function memExpr(obj, id) { return Pattern({ type: "MemberExpression", computed: true, object: obj, property: id }) }
function forStmt(init, test, update, body) { return Pattern({ type: "ForStatement", init: init, test: test, update: update, body: body }) }
function forInStmt(lhs, rhs, body) { return Pattern({ type: "ForInStatement", left: lhs, right: rhs, body: body, each: false }) }
function forEachInStmt(lhs, rhs, body) { return Pattern({ type: "ForInStatement", left: lhs, right: rhs, body: body, each: true }) }
function breakStmt(lab) { return Pattern({ type: "BreakStatement", label: lab }) }
function continueStmt(lab) { return Pattern({ type: "ContinueStatement", label: lab }) }
function blockStmt(body) { return Pattern({ type: "BlockStatement", body: body }) }
var emptyStmt = Pattern({ type: "EmptyStatement" });
function ifStmt(test, cons, alt) { return Pattern({ type: "IfStatement", test: test, alternate: alt, consequent: cons }) }
function labStmt(lab, stmt) { return Pattern({ type: "LabeledStatement", label: lab, body: stmt }) }
function withStmt(obj, stmt) { return Pattern({ type: "WithStatement", object: obj, body: stmt }) }
function whileStmt(test, stmt) { return Pattern({ type: "WhileStatement", test: test, body: stmt }) }
function doStmt(stmt, test) { return Pattern({ type: "DoWhileStatement", test: test, body: stmt }) }
function switchStmt(disc, cases) { return Pattern({ type: "SwitchStatement", discriminant: disc, cases: cases }) }
function caseClause(test, stmts) { return Pattern({ type: "SwitchCase", test: test, consequent: stmts }) }
function defaultClause(stmts) { return Pattern({ type: "SwitchCase", test: null, consequent: stmts }) }
function catchClause(id, guard, body) { if (guard) { return Pattern({ type: "GuardedCatchClause", param: id, guard: guard, body: body }) } else { return Pattern({ type: "CatchClause", param: id, body: body }) } }
function tryStmt(body, guarded, catches, fin) { return Pattern({ type: "TryStatement", block: body, guardedHandlers: guarded, handlers: catches, finalizer: fin }) }
function letStmt(head, body) { return Pattern({ type: "LetStatement", head: head, body: body }) }
function funExpr(id, args, body, gen) { return Pattern({ type: "FunctionExpression",
id: id,
params: args,
defaults: [],
body: body,
rest: null,
generator: false,
expression: false
}) }
function genFunExpr(id, args, body) { return Pattern({ type: "FunctionExpression",
id: id,
params: args,
defaults: [],
body: body,
rest: null,
generator: true,
expression: false
}) }
function unExpr(op, arg) { return Pattern({ type: "UnaryExpression", operator: op, argument: arg, prefix: true }) }
function binExpr(op, left, right) { return Pattern({ type: "BinaryExpression", operator: op, left: left, right: right }) }
function aExpr(op, left, right) { return Pattern({ type: "AssignmentExpression", operator: op, left: left, right: right }) }
function updExpr(op, arg, prefix) { return Pattern({ type: "UpdateExpression", operator: op, argument: arg, prefix: prefix }) }
function logExpr(op, left, right) { return Pattern({ type: "LogicalExpression", operator: op, left: left, right: right }) }
function condExpr(test, cons, alt) { return Pattern({ type: "ConditionalExpression", test: test, consequent: cons, alternate: alt }) }
function seqExpr(exprs) { return Pattern({ type: "SequenceExpression", expressions: exprs }) }
function newExpr(callee, args) { return Pattern({ type: "NewExpression", callee: callee, arguments: args }) }
function callExpr(callee, args) { return Pattern({ type: "CallExpression", callee: callee, arguments: args }) }
function arrExpr(elts) { return Pattern({ type: "ArrayExpression", elements: elts }) }
function objExpr(elts) { return Pattern({ type: "ObjectExpression", properties: elts }) }
function objProp(key, value, kind) { return Pattern({ type: "Property", key: key, value: value, kind: kind, method: false, shorthand: false, computed: false }) }
function arrPatt(elts) { return Pattern({ type: "ArrayPattern", elements: elts }) }
function objPatt(elts) { return Pattern({ type: "ObjectPattern", properties: elts }) }
function localSrc(src) { return "(function(){ " + src + " })" }
function localPatt(patt) { return program([exprStmt(funExpr(null, [], blockStmt([patt])))]) }
function blockSrc(src) { return "(function(){ { " + src + " } })" }
function blockPatt(patt) { return program([exprStmt(funExpr(null, [], blockStmt([blockStmt([patt])])))]) }
function assertBlockStmt(src, patt) {
blockPatt(patt).assert(Reflect.parse(blockSrc(src)));
}
function assertBlockExpr(src, patt) {
assertBlockStmt(src, exprStmt(patt));
}
function assertBlockDecl(src, patt, builder) {
blockPatt(patt).assert(Reflect.parse(blockSrc(src), {builder: builder}));
}
function assertLocalStmt(src, patt) {
localPatt(patt).assert(Reflect.parse(localSrc(src)));
}
function assertLocalExpr(src, patt) {
assertLocalStmt(src, exprStmt(patt));
}
function assertLocalDecl(src, patt) {
localPatt(patt).assert(Reflect.parse(localSrc(src)));
}
function assertGlobalStmt(src, patt, builder) {
program([patt]).assert(Reflect.parse(src, {builder: builder}));
}
function assertGlobalExpr(src, patt, builder) {
program([exprStmt(patt)]).assert(Reflect.parse(src, {builder: builder}));
//assertStmt(src, exprStmt(patt));
}
function assertGlobalDecl(src, patt) {
program([patt]).assert(Reflect.parse(src));
}
function assertProg(src, patt) {
program(patt).assert(Reflect.parse(src));
}
function assertStmt(src, patt) {
assertLocalStmt(src, patt);
assertGlobalStmt(src, patt);
assertBlockStmt(src, patt);
}
function assertExpr(src, patt) {
assertLocalExpr(src, patt);
assertGlobalExpr(src, patt);
assertBlockExpr(src, patt);
}
function assertDecl(src, patt) {
assertLocalDecl(src, patt);
assertGlobalDecl(src, patt);
assertBlockDecl(src, patt);
}
function assertError(src, errorType) {
try {
Reflect.parse(src);
} catch (e) {
return;
}
throw new Error("expected " + errorType.name + " for " + uneval(src));
}
// general tests
// NB: These are useful but for now jit-test doesn't do I/O reliably.
//program(_).assert(Reflect.parse(snarf('data/flapjax.txt')));
//program(_).assert(Reflect.parse(snarf('data/jquery-1.4.2.txt')));
//program(_).assert(Reflect.parse(snarf('data/prototype.js')));
//program(_).assert(Reflect.parse(snarf('data/dojo.js.uncompressed.js')));
//program(_).assert(Reflect.parse(snarf('data/mootools-1.2.4-core-nc.js')));
// declarations
assertDecl("var x = 1, y = 2, z = 3",
varDecl([declarator(ident("x"), lit(1)),
declarator(ident("y"), lit(2)),
declarator(ident("z"), lit(3))]));
assertDecl("var x, y, z",
varDecl([declarator(ident("x"), null),
declarator(ident("y"), null),
declarator(ident("z"), null)]));
assertDecl("function foo() { }",
funDecl(ident("foo"), [], blockStmt([])));
assertDecl("function foo() { return 42 }",
funDecl(ident("foo"), [], blockStmt([returnStmt(lit(42))])));
// Bug 591437: rebound args have their defs turned into uses
assertDecl("function f(a) { function a() { } }",
funDecl(ident("f"), [ident("a")], blockStmt([funDecl(ident("a"), [], blockStmt([]))])));
assertDecl("function f(a,b,c) { function b() { } }",
funDecl(ident("f"), [ident("a"),ident("b"),ident("c")], blockStmt([funDecl(ident("b"), [], blockStmt([]))])));
// expressions
assertExpr("true", lit(true));
assertExpr("false", lit(false));
assertExpr("42", lit(42));
assertExpr("(/asdf/)", lit(/asdf/));
assertExpr("this", thisExpr);
assertExpr("foo", ident("foo"));
assertExpr("foo.bar", dotExpr(ident("foo"), ident("bar")));
assertExpr("foo[bar]", memExpr(ident("foo"), ident("bar")));
assertExpr("(function(){})", funExpr(null, [], blockStmt([])));
assertExpr("(function f() {})", funExpr(ident("f"), [], blockStmt([])));
assertExpr("(function f(x,y,z) {})", funExpr(ident("f"), [ident("x"),ident("y"),ident("z")], blockStmt([])));
assertExpr("(++x)", updExpr("++", ident("x"), true));
assertExpr("(x++)", updExpr("++", ident("x"), false));
assertExpr("(+x)", unExpr("+", ident("x")));
assertExpr("(-x)", unExpr("-", ident("x")));
assertExpr("(!x)", unExpr("!", ident("x")));
assertExpr("(~x)", unExpr("~", ident("x")));
assertExpr("(delete x)", unExpr("delete", ident("x")));
assertExpr("(typeof x)", unExpr("typeof", ident("x")));
assertExpr("(void x)", unExpr("void", ident("x")));
assertExpr("(x == y)", binExpr("==", ident("x"), ident("y")));
assertExpr("(x != y)", binExpr("!=", ident("x"), ident("y")));
assertExpr("(x === y)", binExpr("===", ident("x"), ident("y")));
assertExpr("(x !== y)", binExpr("!==", ident("x"), ident("y")));
assertExpr("(x < y)", binExpr("<", ident("x"), ident("y")));
assertExpr("(x <= y)", binExpr("<=", ident("x"), ident("y")));
assertExpr("(x > y)", binExpr(">", ident("x"), ident("y")));
assertExpr("(x >= y)", binExpr(">=", ident("x"), ident("y")));
assertExpr("(x << y)", binExpr("<<", ident("x"), ident("y")));
assertExpr("(x >> y)", binExpr(">>", ident("x"), ident("y")));
assertExpr("(x >>> y)", binExpr(">>>", ident("x"), ident("y")));
assertExpr("(x + y)", binExpr("+", ident("x"), ident("y")));
assertExpr("(w + x + y + z)", binExpr("+", binExpr("+", binExpr("+", ident("w"), ident("x")), ident("y")), ident("z")));
assertExpr("(x - y)", binExpr("-", ident("x"), ident("y")));
assertExpr("(w - x - y - z)", binExpr("-", binExpr("-", binExpr("-", ident("w"), ident("x")), ident("y")), ident("z")));
assertExpr("(x * y)", binExpr("*", ident("x"), ident("y")));
assertExpr("(x / y)", binExpr("/", ident("x"), ident("y")));
assertExpr("(x % y)", binExpr("%", ident("x"), ident("y")));
assertExpr("(x | y)", binExpr("|", ident("x"), ident("y")));
assertExpr("(x ^ y)", binExpr("^", ident("x"), ident("y")));
assertExpr("(x & y)", binExpr("&", ident("x"), ident("y")));
assertExpr("(x in y)", binExpr("in", ident("x"), ident("y")));
assertExpr("(x instanceof y)", binExpr("instanceof", ident("x"), ident("y")));
assertExpr("(x = y)", aExpr("=", ident("x"), ident("y")));
assertExpr("(x += y)", aExpr("+=", ident("x"), ident("y")));
assertExpr("(x -= y)", aExpr("-=", ident("x"), ident("y")));
assertExpr("(x *= y)", aExpr("*=", ident("x"), ident("y")));
assertExpr("(x /= y)", aExpr("/=", ident("x"), ident("y")));
assertExpr("(x %= y)", aExpr("%=", ident("x"), ident("y")));
assertExpr("(x <<= y)", aExpr("<<=", ident("x"), ident("y")));
assertExpr("(x >>= y)", aExpr(">>=", ident("x"), ident("y")));
assertExpr("(x >>>= y)", aExpr(">>>=", ident("x"), ident("y")));
assertExpr("(x |= y)", aExpr("|=", ident("x"), ident("y")));
assertExpr("(x ^= y)", aExpr("^=", ident("x"), ident("y")));
assertExpr("(x &= y)", aExpr("&=", ident("x"), ident("y")));
assertExpr("(x || y)", logExpr("||", ident("x"), ident("y")));
assertExpr("(x && y)", logExpr("&&", ident("x"), ident("y")));
assertExpr("(w || x || y || z)", logExpr("||", logExpr("||", logExpr("||", ident("w"), ident("x")), ident("y")), ident("z")))
assertExpr("(x ? y : z)", condExpr(ident("x"), ident("y"), ident("z")));
assertExpr("(x,y)", seqExpr([ident("x"),ident("y")]))
assertExpr("(x,y,z)", seqExpr([ident("x"),ident("y"),ident("z")]))
assertExpr("(a,b,c,d,e,f,g)", seqExpr([ident("a"),ident("b"),ident("c"),ident("d"),ident("e"),ident("f"),ident("g")]));
assertExpr("(new Object)", newExpr(ident("Object"), []));
assertExpr("(new Object())", newExpr(ident("Object"), []));
assertExpr("(new Object(42))", newExpr(ident("Object"), [lit(42)]));
assertExpr("(new Object(1,2,3))", newExpr(ident("Object"), [lit(1),lit(2),lit(3)]));
assertExpr("(String())", callExpr(ident("String"), []));
assertExpr("(String(42))", callExpr(ident("String"), [lit(42)]));
assertExpr("(String(1,2,3))", callExpr(ident("String"), [lit(1),lit(2),lit(3)]));
assertExpr("[]", arrExpr([]));
assertExpr("[1]", arrExpr([lit(1)]));
assertExpr("[1,2]", arrExpr([lit(1),lit(2)]));
assertExpr("[1,2,3]", arrExpr([lit(1),lit(2),lit(3)]));
assertExpr("[1,,2,3]", arrExpr([lit(1),,lit(2),lit(3)]));
assertExpr("[1,,,2,3]", arrExpr([lit(1),,,lit(2),lit(3)]));
assertExpr("[1,,,2,,3]", arrExpr([lit(1),,,lit(2),,lit(3)]));
assertExpr("[1,,,2,,,3]", arrExpr([lit(1),,,lit(2),,,lit(3)]));
assertExpr("[,1,2,3]", arrExpr([,lit(1),lit(2),lit(3)]));
assertExpr("[,,1,2,3]", arrExpr([,,lit(1),lit(2),lit(3)]));
assertExpr("[,,,1,2,3]", arrExpr([,,,lit(1),lit(2),lit(3)]));
assertExpr("[,,,1,2,3,]", arrExpr([,,,lit(1),lit(2),lit(3)]));
assertExpr("[,,,1,2,3,,]", arrExpr([,,,lit(1),lit(2),lit(3),undefined]));
assertExpr("[,,,1,2,3,,,]", arrExpr([,,,lit(1),lit(2),lit(3),undefined,undefined]));
assertExpr("[,,,,,]", arrExpr([undefined,undefined,undefined,undefined,undefined]));
assertExpr("({})", objExpr([]));
assertExpr("({x:1})", objExpr([objProp(ident("x"), lit(1), "init")]));
assertExpr("({x:1, y:2})", objExpr([objProp(ident("x"), lit(1), "init"),
objProp(ident("y"), lit(2), "init")]));
assertExpr("({x:1, y:2, z:3})", objExpr([objProp(ident("x"), lit(1), "init"),
objProp(ident("y"), lit(2), "init"),
objProp(ident("z"), lit(3), "init") ]));
assertExpr("({x:1, 'y':2, z:3})", objExpr([objProp(ident("x"), lit(1), "init"),
objProp(lit("y"), lit(2), "init"),
objProp(ident("z"), lit(3), "init") ]));
assertExpr("({'x':1, 'y':2, z:3})", objExpr([objProp(lit("x"), lit(1), "init"),
objProp(lit("y"), lit(2), "init"),
objProp(ident("z"), lit(3), "init") ]));
assertExpr("({'x':1, 'y':2, 3:3})", objExpr([objProp(lit("x"), lit(1), "init"),
objProp(lit("y"), lit(2), "init"),
objProp(lit(3), lit(3), "init") ]));
// Bug 571617: eliminate constant-folding
assertExpr("2 + 3", binExpr("+", lit(2), lit(3)));
// Bug 632026: constant-folding
assertExpr("typeof(0?0:a)", unExpr("typeof", condExpr(lit(0), lit(0), ident("a"))));
// Bug 632056: constant-folding
program([exprStmt(ident("f")),
ifStmt(lit(1),
funDecl(ident("f"), [], blockStmt([])),
null)]).assert(Reflect.parse("f; if (1) function f(){}"));
// statements
assertStmt("throw 42", throwStmt(lit(42)));
assertStmt("for (;;) break", forStmt(null, null, null, breakStmt(null)));
assertStmt("for (x; y; z) break", forStmt(ident("x"), ident("y"), ident("z"), breakStmt(null)));
assertStmt("for (var x; y; z) break", forStmt(varDecl([declarator(ident("x"), null)]), ident("y"), ident("z"), breakStmt(null)));
assertStmt("for (var x = 42; y; z) break", forStmt(varDecl([declarator(ident("x"), lit(42))]), ident("y"), ident("z"), breakStmt(null)));
assertStmt("for (x; ; z) break", forStmt(ident("x"), null, ident("z"), breakStmt(null)));
assertStmt("for (var x; ; z) break", forStmt(varDecl([declarator(ident("x"), null)]), null, ident("z"), breakStmt(null)));
assertStmt("for (var x = 42; ; z) break", forStmt(varDecl([declarator(ident("x"), lit(42))]), null, ident("z"), breakStmt(null)));
assertStmt("for (x; y; ) break", forStmt(ident("x"), ident("y"), null, breakStmt(null)));
assertStmt("for (var x; y; ) break", forStmt(varDecl([declarator(ident("x"), null)]), ident("y"), null, breakStmt(null)));
assertStmt("for (var x = 42; y; ) break", forStmt(varDecl([declarator(ident("x"),lit(42))]), ident("y"), null, breakStmt(null)));
assertStmt("for (var x in y) break", forInStmt(varDecl([declarator(ident("x"),null)]), ident("y"), breakStmt(null)));
assertStmt("for (x in y) break", forInStmt(ident("x"), ident("y"), breakStmt(null)));
assertStmt("{ }", blockStmt([]));
assertStmt("{ throw 1; throw 2; throw 3; }", blockStmt([ throwStmt(lit(1)), throwStmt(lit(2)), throwStmt(lit(3))]));
assertStmt(";", emptyStmt);
assertStmt("if (foo) throw 42;", ifStmt(ident("foo"), throwStmt(lit(42)), null));
assertStmt("if (foo) throw 42; else true;", ifStmt(ident("foo"), throwStmt(lit(42)), exprStmt(lit(true))));
assertStmt("if (foo) { throw 1; throw 2; throw 3; }",
ifStmt(ident("foo"),
blockStmt([throwStmt(lit(1)), throwStmt(lit(2)), throwStmt(lit(3))]),
null));
assertStmt("if (foo) { throw 1; throw 2; throw 3; } else true;",
ifStmt(ident("foo"),
blockStmt([throwStmt(lit(1)), throwStmt(lit(2)), throwStmt(lit(3))]),
exprStmt(lit(true))));
assertStmt("foo: for(;;) break foo;", labStmt(ident("foo"), forStmt(null, null, null, breakStmt(ident("foo")))));
assertStmt("foo: for(;;) continue foo;", labStmt(ident("foo"), forStmt(null, null, null, continueStmt(ident("foo")))));
assertStmt("with (obj) { }", withStmt(ident("obj"), blockStmt([])));
assertStmt("with (obj) { obj; }", withStmt(ident("obj"), blockStmt([exprStmt(ident("obj"))])));
assertStmt("while (foo) { }", whileStmt(ident("foo"), blockStmt([])));
assertStmt("while (foo) { foo; }", whileStmt(ident("foo"), blockStmt([exprStmt(ident("foo"))])));
assertStmt("do { } while (foo);", doStmt(blockStmt([]), ident("foo")));
assertStmt("do { foo; } while (foo)", doStmt(blockStmt([exprStmt(ident("foo"))]), ident("foo")));
assertStmt("switch (foo) { case 1: 1; break; case 2: 2; break; default: 3; }",
switchStmt(ident("foo"),
[ caseClause(lit(1), [ exprStmt(lit(1)), breakStmt(null) ]),
caseClause(lit(2), [ exprStmt(lit(2)), breakStmt(null) ]),
defaultClause([ exprStmt(lit(3)) ]) ]));
assertStmt("switch (foo) { case 1: 1; break; case 2: 2; break; default: 3; case 42: 42; }",
switchStmt(ident("foo"),
[ caseClause(lit(1), [ exprStmt(lit(1)), breakStmt(null) ]),
caseClause(lit(2), [ exprStmt(lit(2)), breakStmt(null) ]),
defaultClause([ exprStmt(lit(3)) ]),
caseClause(lit(42), [ exprStmt(lit(42)) ]) ]));
assertStmt("try { } catch (e) { }",
tryStmt(blockStmt([]),
[],
[ catchClause(ident("e"), null, blockStmt([])) ],
null));
assertStmt("try { } catch (e) { } finally { }",
tryStmt(blockStmt([]),
[],
[ catchClause(ident("e"), null, blockStmt([])) ],
blockStmt([])));
assertStmt("try { } finally { }",
tryStmt(blockStmt([]),
[],
[],
blockStmt([])));
// redeclarations (TOK_NAME nodes with lexdef)
assertStmt("function f() { function g() { } function g() { } }",
funDecl(ident("f"), [], blockStmt([funDecl(ident("g"), [], blockStmt([])),
funDecl(ident("g"), [], blockStmt([]))])));
assertStmt("function f() { function g() { } function g() { return 42 } }",
funDecl(ident("f"), [], blockStmt([funDecl(ident("g"), [], blockStmt([])),
funDecl(ident("g"), [], blockStmt([returnStmt(lit(42))]))])));
assertStmt("function f() { var x = 42; var x = 43; }",
funDecl(ident("f"), [], blockStmt([varDecl([declarator(ident("x"),lit(42))]),
varDecl([declarator(ident("x"),lit(43))])])));
// getters and setters
assertExpr("({ get x() { return 42 } })",
objExpr([ objProp(ident("x"),
funExpr(null, [], blockStmt([returnStmt(lit(42))])),
"get" ) ]));
assertExpr("({ set x(v) { return 42 } })",
objExpr([ objProp(ident("x"),
funExpr(null, [ident("v")], blockStmt([returnStmt(lit(42))])),
"set" ) ]));
}
exports.testReflect = testReflect;
}(typeof exports === 'undefined' ? this : exports));

View File

@@ -0,0 +1,66 @@
/*
Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint node:true */
(function () {
'use strict';
var child = require('child_process'),
nodejs = '"' + process.execPath + '"',
ret = 0,
suites,
index;
suites = [
'runner',
'compat'
];
function nextTest() {
var suite = suites[index];
if (index < suites.length) {
child.exec(nodejs + ' ./test/' + suite + '.js', function (err, stdout, stderr) {
if (stdout) {
process.stdout.write(suite + ': ' + stdout);
}
if (stderr) {
process.stderr.write(suite + ': ' + stderr);
}
if (err) {
ret = err.code;
}
index += 1;
nextTest();
});
} else {
process.exit(ret);
}
}
index = 0;
nextTest();
}());

View File

@@ -0,0 +1,515 @@
/*
Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
Copyright (C) 2011 Yusuke Suzuki <utatane.tea@gmail.com>
Copyright (C) 2011 Arpad Borsos <arpad.borsos@googlemail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint browser:true node:true */
/*global esprima:true, testFixture:true */
var runTests;
// Special handling for regular expression literals: remove their `value`
// property since it may be `null` if it represents a regular expression
// that is not supported in the current environment. The `regex` property
// will be compared instead.
function adjustRegexLiteral(key, value) {
'use strict';
if (key === 'value' && value instanceof RegExp) {
value = value.toString();
}
return value;
}
function NotMatchingError(expected, actual) {
'use strict';
Error.call(this, 'Expected ');
this.expected = expected;
this.actual = actual;
}
NotMatchingError.prototype = new Error();
function errorToObject(e) {
'use strict';
var msg = e.toString();
// Opera 9.64 produces an non-standard string in toString().
if (msg.substr(0, 6) !== 'Error:') {
if (typeof e.message === 'string') {
msg = 'Error: ' + e.message;
}
}
return {
index: e.index,
lineNumber: e.lineNumber,
column: e.column,
message: msg
};
}
function needLoc(syntax) {
var need = true;
if (typeof syntax.tokens !== 'undefined' && syntax.tokens.length > 0) {
need = (typeof syntax.tokens[0].loc !== 'undefined');
}
if (typeof syntax.comments !== 'undefined' && syntax.comments.length > 0) {
need = (typeof syntax.comments[0].loc !== 'undefined');
}
return need;
}
function needRange(syntax) {
var need = true;
if (typeof syntax.tokens !== 'undefined' && syntax.tokens.length > 0) {
need = (typeof syntax.tokens[0].range !== 'undefined');
}
if (typeof syntax.comments !== 'undefined' && syntax.comments.length > 0) {
need = (typeof syntax.comments[0].range !== 'undefined');
}
return need;
}
function hasAttachedComment(syntax) {
var key;
for (key in syntax) {
if (key === 'leadingComments' || key === 'trailingComments') {
return true;
}
if (syntax[key] && typeof syntax[key] === 'object') {
if (hasAttachedComment(syntax[key])) {
return true;
}
}
}
return false;
}
function testParse(esprima, code, syntax, testOptions) {
'use strict';
var expected, tree, actual, options, StringObject, i, len, err;
// alias, so that JSLint does not complain.
StringObject = String;
options = {
comment: (typeof syntax.comments !== 'undefined'),
range: needRange(syntax),
loc: needLoc(syntax),
tokens: (typeof syntax.tokens !== 'undefined'),
raw: true,
tolerant: (typeof syntax.errors !== 'undefined'),
source: null,
sourceType: testOptions.sourceType
};
if (options.comment) {
options.attachComment = hasAttachedComment(syntax);
}
if (options.loc) {
options.source = syntax.loc.source;
}
expected = JSON.stringify(syntax, adjustRegexLiteral, 4);
try {
tree = esprima.parse(code, options);
tree = (options.comment || options.tokens || options.tolerant) ? tree : tree.body[0];
if (options.tolerant) {
for (i = 0, len = tree.errors.length; i < len; i += 1) {
tree.errors[i] = errorToObject(tree.errors[i]);
}
}
actual = JSON.stringify(tree, adjustRegexLiteral, 4);
// Only to ensure that there is no error when using string object.
esprima.parse(new StringObject(code), options);
} catch (e) {
throw new NotMatchingError(expected, e.toString());
}
if (expected !== actual) {
throw new NotMatchingError(expected, actual);
}
function filter(key, value) {
if (key === 'value' && value instanceof RegExp) {
value = value.toString();
}
return (key === 'loc' || key === 'range') ? undefined : value;
}
if (options.tolerant) {
return;
}
// Check again without any location info.
options.range = false;
options.loc = false;
expected = JSON.stringify(syntax, filter, 4);
try {
tree = esprima.parse(code, options);
tree = (options.comment || options.tokens) ? tree : tree.body[0];
if (options.tolerant) {
for (i = 0, len = tree.errors.length; i < len; i += 1) {
tree.errors[i] = errorToObject(tree.errors[i]);
}
}
actual = JSON.stringify(tree, filter, 4);
} catch (e) {
throw new NotMatchingError(expected, e.toString());
}
if (expected !== actual) {
throw new NotMatchingError(expected, actual);
}
}
function mustHaveLocRange(testName, node, needLoc, needRange, stack) {
var error;
if (node.hasOwnProperty('type')) {
if (needLoc && !node.loc) {
error = "doesn't have 'loc' property";
}
if (needRange && !node.range) {
error = "doesn't have 'range' property";
}
if (error) {
stack = stack.length ? ' at [' + stack.join('][') + ']' : '';
throw new Error("Test '" + testName + "'" + stack + " (type = " + node.type + ") " + error);
}
}
for (i in node) {
if (node.hasOwnProperty(i) && node[i] !== null && typeof node[i] === 'object') {
stack.push(i);
mustHaveLocRange(testName, node[i], needLoc, needRange, stack);
stack.pop();
}
}
}
function testTokenize(esprima, code, tokens, testOptions) {
'use strict';
var options, expected, actual, tree;
options = {
comment: true,
tolerant: true,
loc: true,
range: true,
sourceType: testOptions.sourceType
};
expected = JSON.stringify(tokens, null, 4);
try {
tree = esprima.tokenize(code, options);
actual = JSON.stringify(tree, null, 4);
} catch (e) {
throw new NotMatchingError(expected, e.toString());
}
if (expected !== actual) {
throw new NotMatchingError(expected, actual);
}
}
function testError(esprima, code, exception, testOptions) {
'use strict';
var i, options, expected, actual, err, handleInvalidRegexFlag, tokenize,
sourceType;
// Different parsing options should give the same error.
options = [
{ sourceType: testOptions.sourceType },
{ sourceType: testOptions.sourceType, comment: true },
{ sourceType: testOptions.sourceType, raw: true },
{ sourceType: testOptions.sourceType, raw: true, comment: true }
];
// If handleInvalidRegexFlag is true, an invalid flag in a regular expression
// will throw an exception. In some old version of V8, this is not the case
// and hence handleInvalidRegexFlag is false.
handleInvalidRegexFlag = false;
try {
'test'.match(new RegExp('[a-z]', 'x'));
} catch (e) {
handleInvalidRegexFlag = true;
}
exception.description = exception.message.replace(/Error: Line [0-9]+: /, '');
if (exception.tokenize) {
tokenize = true;
exception.tokenize = undefined;
}
expected = JSON.stringify(exception);
for (i = 0; i < options.length; i += 1) {
try {
if (tokenize) {
esprima.tokenize(code, options[i])
} else {
esprima.parse(code, options[i]);
}
} catch (e) {
err = errorToObject(e);
err.description = e.description;
actual = JSON.stringify(err);
}
if (expected !== actual) {
// Compensate for old V8 which does not handle invalid flag.
if (exception.message.indexOf('Invalid regular expression') > 0) {
if (typeof actual === 'undefined' && !handleInvalidRegexFlag) {
return;
}
}
throw new NotMatchingError(expected, actual);
}
}
}
function testAPI(esprima, code, result) {
'use strict';
var expected, res, actual;
expected = JSON.stringify(result.result, null, 4);
try {
if (typeof result.property !== 'undefined') {
res = esprima[result.property];
} else {
res = esprima[result.call].apply(esprima, result.args);
}
actual = JSON.stringify(res, adjustRegexLiteral, 4);
} catch (e) {
throw new NotMatchingError(expected, e.toString());
}
if (expected !== actual) {
throw new NotMatchingError(expected, actual);
}
}
function runTest(esprima, code, result, options) {
'use strict';
if (result.hasOwnProperty('lineNumber')) {
testError(esprima, code, result, options);
} else if (result.hasOwnProperty('result')) {
testAPI(esprima, code, result);
} else if (result instanceof Array) {
testTokenize(esprima, code, result, options);
} else {
testParse(esprima, code, result, options);
}
}
if (typeof window !== 'undefined') {
// Run all tests in a browser environment.
runTests = function () {
'use strict';
var total = 0,
failures = 0,
category,
fixture,
source,
tick,
expected,
index,
len;
function setText(el, str) {
if (typeof el.innerText === 'string') {
el.innerText = str;
} else {
el.textContent = str;
}
}
function startCategory(category) {
var report, e;
report = document.getElementById('report');
e = document.createElement('h4');
setText(e, category);
report.appendChild(e);
}
function reportSuccess(code) {
var report, e;
report = document.getElementById('report');
e = document.createElement('pre');
e.setAttribute('class', 'code');
setText(e, code);
report.appendChild(e);
}
function reportFailure(code, expected, actual) {
var report, e;
report = document.getElementById('report');
e = document.createElement('p');
setText(e, 'Code:');
report.appendChild(e);
e = document.createElement('pre');
e.setAttribute('class', 'code');
setText(e, code);
report.appendChild(e);
e = document.createElement('p');
setText(e, 'Expected');
report.appendChild(e);
e = document.createElement('pre');
e.setAttribute('class', 'expected');
setText(e, expected);
report.appendChild(e);
e = document.createElement('p');
setText(e, 'Actual');
report.appendChild(e);
e = document.createElement('pre');
e.setAttribute('class', 'actual');
setText(e, actual);
report.appendChild(e);
}
setText(document.getElementById('version'), esprima.version);
tick = new Date();
for (category in testFixture) {
if (testFixture.hasOwnProperty(category)) {
var categoryOptions = testFixtureOptions[category] || {};
startCategory(category);
fixture = testFixture[category];
for (source in fixture) {
if (fixture.hasOwnProperty(source)) {
var sourceOptions =
categoryOptions.hasOwnProperty(source)
? categoryOptions[source]
: categoryOptions;
expected = fixture[source];
total += 1;
try {
runTest(esprima, source, expected, sourceOptions);
reportSuccess(source, JSON.stringify(expected, null, 4));
} catch (e) {
failures += 1;
reportFailure(source, e.expected, e.actual);
}
}
}
}
}
tick = (new Date()) - tick;
if (failures > 0) {
document.getElementById('status').className = 'alert-box alert';
setText(document.getElementById('status'), total + ' tests. ' +
'Failures: ' + failures + '. ' + tick + ' ms.');
} else {
document.getElementById('status').className = 'alert-box success';
setText(document.getElementById('status'), total + ' tests. ' +
'No failure. ' + tick + ' ms.');
}
};
} else {
(function () {
'use strict';
var esprima = require('../esprima'),
vm = require('vm'),
fs = require('fs'),
diff = require('json-diff').diffString,
total = 0,
failures = [],
tick = new Date(),
expected,
header;
vm.runInThisContext(fs.readFileSync(__dirname + '/test.js', 'utf-8'));
vm.runInThisContext(fs.readFileSync(__dirname + '/harmonytest.js', 'utf-8'));
vm.runInThisContext(fs.readFileSync(__dirname + '/fbtest.rec.js', 'utf-8'));
vm.runInThisContext(fs.readFileSync(__dirname + '/harmonymodulestest.js', 'utf-8'));
Object.keys(testFixture).forEach(function (category) {
var categoryOptions = testFixtureOptions[category] || {};
Object.keys(testFixture[category]).forEach(function (source) {
var sourceOptions =
categoryOptions.hasOwnProperty(source)
? categoryOptions[source]
: categoryOptions;
total += 1;
expected = testFixture[category][source];
if (!expected.hasOwnProperty('lineNumber') && !expected.hasOwnProperty('result')) {
mustHaveLocRange(source, expected, needLoc(expected), needRange(expected), []);
}
try {
runTest(esprima, source, expected, sourceOptions);
} catch (e) {
e.source = source;
failures.push(e);
}
});
});
tick = (new Date()) - tick;
header = total + ' tests. ' + failures.length + ' failures. ' +
tick + ' ms';
if (failures.length) {
console.error(header);
failures.forEach(function (failure) {
try {
var expectedObject = JSON.parse(failure.expected);
var actualObject = JSON.parse(failure.actual);
console.error(failure.source + ': Expected\n ' +
failure.expected.split('\n').join('\n ') +
'\nto match\n ' + failure.actual + '\nDiff:\n' +
diff(expectedObject, actualObject));
} catch (ex) {
console.error(failure.source + ': Expected\n ' +
failure.expected.split('\n').join('\n ') +
'\nto match\n ' + failure.actual);
}
});
} else {
console.log(header);
}
process.exit(failures.length === 0 ? 0 : 1);
}());
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
dist/*
node_modules/*

View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.8
- "0.10"

View File

@@ -0,0 +1,112 @@
# Change Log
## 0.1.31
* Delay parsing the mappings in SourceMapConsumer until queried for a source
location.
* Support Sass source maps (which at the time of writing deviate from the spec
in small ways) in SourceMapConsumer.
## 0.1.30
* Do not join source root with a source, when the source is a data URI.
* Extend the test runner to allow running single specific test files at a time.
* Performance improvements in `SourceNode.prototype.walk` and
`SourceMapConsumer.prototype.eachMapping`.
* Source map browser builds will now work inside Workers.
* Better error messages when attempting to add an invalid mapping to a
`SourceMapGenerator`.
## 0.1.29
* Allow duplicate entries in the `names` and `sources` arrays of source maps
(usually from TypeScript) we are parsing. Fixes github isse 72.
## 0.1.28
* Skip duplicate mappings when creating source maps from SourceNode; github
issue 75.
## 0.1.27
* Don't throw an error when the `file` property is missing in SourceMapConsumer,
we don't use it anyway.
## 0.1.26
* Fix SourceNode.fromStringWithSourceMap for empty maps. Fixes github issue 70.
## 0.1.25
* Make compatible with browserify
## 0.1.24
* Fix issue with absolute paths and `file://` URIs. See
https://bugzilla.mozilla.org/show_bug.cgi?id=885597
## 0.1.23
* Fix issue with absolute paths and sourcesContent, github issue 64.
## 0.1.22
* Ignore duplicate mappings in SourceMapGenerator. Fixes github issue 21.
## 0.1.21
* Fixed handling of sources that start with a slash so that they are relative to
the source root's host.
## 0.1.20
* Fixed github issue #43: absolute URLs aren't joined with the source root
anymore.
## 0.1.19
* Using Travis CI to run tests.
## 0.1.18
* Fixed a bug in the handling of sourceRoot.
## 0.1.17
* Added SourceNode.fromStringWithSourceMap.
## 0.1.16
* Added missing documentation.
* Fixed the generating of empty mappings in SourceNode.
## 0.1.15
* Added SourceMapGenerator.applySourceMap.
## 0.1.14
* The sourceRoot is now handled consistently.
## 0.1.13
* Added SourceMapGenerator.fromSourceMap.
## 0.1.12
* SourceNode now generates empty mappings too.
## 0.1.11
* Added name support to SourceNode.
## 0.1.10
* Added sourcesContent support to the customer and generator.

View File

@@ -0,0 +1,28 @@
Copyright (c) 2009-2011, Mozilla Foundation and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the names of the Mozilla Foundation nor the names of project
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,166 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
var path = require('path');
var fs = require('fs');
var copy = require('dryice').copy;
function removeAmdefine(src) {
src = String(src).replace(
/if\s*\(typeof\s*define\s*!==\s*'function'\)\s*{\s*var\s*define\s*=\s*require\('amdefine'\)\(module,\s*require\);\s*}\s*/g,
'');
src = src.replace(
/\b(define\(.*)('amdefine',?)/gm,
'$1');
return src;
}
removeAmdefine.onRead = true;
function makeNonRelative(src) {
return src
.replace(/require\('.\//g, 'require(\'source-map/')
.replace(/\.\.\/\.\.\/lib\//g, '');
}
makeNonRelative.onRead = true;
function buildBrowser() {
console.log('\nCreating dist/source-map.js');
var project = copy.createCommonJsProject({
roots: [ path.join(__dirname, 'lib') ]
});
copy({
source: [
'build/mini-require.js',
{
project: project,
require: [ 'source-map/source-map-generator',
'source-map/source-map-consumer',
'source-map/source-node']
},
'build/suffix-browser.js'
],
filter: [
copy.filter.moduleDefines,
removeAmdefine
],
dest: 'dist/source-map.js'
});
}
function buildBrowserMin() {
console.log('\nCreating dist/source-map.min.js');
copy({
source: 'dist/source-map.js',
filter: copy.filter.uglifyjs,
dest: 'dist/source-map.min.js'
});
}
function buildFirefox() {
console.log('\nCreating dist/SourceMap.jsm');
var project = copy.createCommonJsProject({
roots: [ path.join(__dirname, 'lib') ]
});
copy({
source: [
'build/prefix-source-map.jsm',
{
project: project,
require: [ 'source-map/source-map-consumer',
'source-map/source-map-generator',
'source-map/source-node' ]
},
'build/suffix-source-map.jsm'
],
filter: [
copy.filter.moduleDefines,
removeAmdefine,
makeNonRelative
],
dest: 'dist/SourceMap.jsm'
});
// Create dist/test/Utils.jsm
console.log('\nCreating dist/test/Utils.jsm');
project = copy.createCommonJsProject({
roots: [ __dirname, path.join(__dirname, 'lib') ]
});
copy({
source: [
'build/prefix-utils.jsm',
'build/assert-shim.js',
{
project: project,
require: [ 'test/source-map/util' ]
},
'build/suffix-utils.jsm'
],
filter: [
copy.filter.moduleDefines,
removeAmdefine,
makeNonRelative
],
dest: 'dist/test/Utils.jsm'
});
function isTestFile(f) {
return /^test\-.*?\.js/.test(f);
}
var testFiles = fs.readdirSync(path.join(__dirname, 'test', 'source-map')).filter(isTestFile);
testFiles.forEach(function (testFile) {
console.log('\nCreating', path.join('dist', 'test', testFile.replace(/\-/g, '_')));
copy({
source: [
'build/test-prefix.js',
path.join('test', 'source-map', testFile),
'build/test-suffix.js'
],
filter: [
removeAmdefine,
makeNonRelative,
function (input, source) {
return input.replace('define(',
'define("'
+ path.join('test', 'source-map', testFile.replace(/\.js$/, ''))
+ '", ["require", "exports", "module"], ');
},
function (input, source) {
return input.replace('{THIS_MODULE}', function () {
return "test/source-map/" + testFile.replace(/\.js$/, '');
});
}
],
dest: path.join('dist', 'test', testFile.replace(/\-/g, '_'))
});
});
}
function ensureDir(name) {
var dirExists = false;
try {
dirExists = fs.statSync(name).isDirectory();
} catch (err) {}
if (!dirExists) {
fs.mkdirSync(name, 0777);
}
}
ensureDir("dist");
ensureDir("dist/test");
buildFirefox();
buildBrowser();
buildBrowserMin();

View File

@@ -0,0 +1,434 @@
# Source Map
This is a library to generate and consume the source map format
[described here][format].
This library is written in the Asynchronous Module Definition format, and works
in the following environments:
* Modern Browsers supporting ECMAScript 5 (either after the build, or with an
AMD loader such as RequireJS)
* Inside Firefox (as a JSM file, after the build)
* With NodeJS versions 0.8.X and higher
## Node
$ npm install source-map
## Building from Source (for everywhere else)
Install Node and then run
$ git clone https://fitzgen@github.com/mozilla/source-map.git
$ cd source-map
$ npm link .
Next, run
$ node Makefile.dryice.js
This should spew a bunch of stuff to stdout, and create the following files:
* `dist/source-map.js` - The unminified browser version.
* `dist/source-map.min.js` - The minified browser version.
* `dist/SourceMap.jsm` - The JavaScript Module for inclusion in Firefox source.
## Examples
### Consuming a source map
var rawSourceMap = {
version: 3,
file: 'min.js',
names: ['bar', 'baz', 'n'],
sources: ['one.js', 'two.js'],
sourceRoot: 'http://example.com/www/js/',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
var smc = new SourceMapConsumer(rawSourceMap);
console.log(smc.sources);
// [ 'http://example.com/www/js/one.js',
// 'http://example.com/www/js/two.js' ]
console.log(smc.originalPositionFor({
line: 2,
column: 28
}));
// { source: 'http://example.com/www/js/two.js',
// line: 2,
// column: 10,
// name: 'n' }
console.log(smc.generatedPositionFor({
source: 'http://example.com/www/js/two.js',
line: 2,
column: 10
}));
// { line: 2, column: 28 }
smc.eachMapping(function (m) {
// ...
});
### Generating a source map
In depth guide:
[**Compiling to JavaScript, and Debugging with Source Maps**](https://hacks.mozilla.org/2013/05/compiling-to-javascript-and-debugging-with-source-maps/)
#### With SourceNode (high level API)
function compile(ast) {
switch (ast.type) {
case 'BinaryExpression':
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
[compile(ast.left), " + ", compile(ast.right)]
);
case 'Literal':
return new SourceNode(
ast.location.line,
ast.location.column,
ast.location.source,
String(ast.value)
);
// ...
default:
throw new Error("Bad AST");
}
}
var ast = parse("40 + 2", "add.js");
console.log(compile(ast).toStringWithSourceMap({
file: 'add.js'
}));
// { code: '40 + 2',
// map: [object SourceMapGenerator] }
#### With SourceMapGenerator (low level API)
var map = new SourceMapGenerator({
file: "source-mapped.js"
});
map.addMapping({
generated: {
line: 10,
column: 35
},
source: "foo.js",
original: {
line: 33,
column: 2
},
name: "christopher"
});
console.log(map.toString());
// '{"version":3,"file":"source-mapped.js","sources":["foo.js"],"names":["christopher"],"mappings":";;;;;;;;;mCAgCEA"}'
## API
Get a reference to the module:
// NodeJS
var sourceMap = require('source-map');
// Browser builds
var sourceMap = window.sourceMap;
// Inside Firefox
let sourceMap = {};
Components.utils.import('resource:///modules/devtools/SourceMap.jsm', sourceMap);
### SourceMapConsumer
A SourceMapConsumer instance represents a parsed source map which we can query
for information about the original file positions by giving it a file position
in the generated source.
#### new SourceMapConsumer(rawSourceMap)
The only parameter is the raw source map (either as a string which can be
`JSON.parse`'d, or an object). According to the spec, source maps have the
following attributes:
* `version`: Which version of the source map spec this map is following.
* `sources`: An array of URLs to the original source files.
* `names`: An array of identifiers which can be referrenced by individual
mappings.
* `sourceRoot`: Optional. The URL root from which all sources are relative.
* `sourcesContent`: Optional. An array of contents of the original source files.
* `mappings`: A string of base64 VLQs which contain the actual mappings.
* `file`: The generated filename this source map is associated with.
#### SourceMapConsumer.prototype.originalPositionFor(generatedPosition)
Returns the original source, line, and column information for the generated
source's line and column positions provided. The only argument is an object with
the following properties:
* `line`: The line number in the generated source.
* `column`: The column number in the generated source.
and an object is returned with the following properties:
* `source`: The original source file, or null if this information is not
available.
* `line`: The line number in the original source, or null if this information is
not available.
* `column`: The column number in the original source, or null or null if this
information is not available.
* `name`: The original identifier, or null if this information is not available.
#### SourceMapConsumer.prototype.generatedPositionFor(originalPosition)
Returns the generated line and column information for the original source,
line, and column positions provided. The only argument is an object with
the following properties:
* `source`: The filename of the original source.
* `line`: The line number in the original source.
* `column`: The column number in the original source.
and an object is returned with the following properties:
* `line`: The line number in the generated source, or null.
* `column`: The column number in the generated source, or null.
#### SourceMapConsumer.prototype.sourceContentFor(source)
Returns the original source content for the source provided. The only
argument is the URL of the original source file.
#### SourceMapConsumer.prototype.eachMapping(callback, context, order)
Iterate over each mapping between an original source/line/column and a
generated line/column in this source map.
* `callback`: The function that is called with each mapping. Mappings have the
form `{ source, generatedLine, generatedColumn, originalLine, originalColumn,
name }`
* `context`: Optional. If specified, this object will be the value of `this`
every time that `callback` is called.
* `order`: Either `SourceMapConsumer.GENERATED_ORDER` or
`SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to iterate over
the mappings sorted by the generated file's line/column order or the
original's source/line/column order, respectively. Defaults to
`SourceMapConsumer.GENERATED_ORDER`.
### SourceMapGenerator
An instance of the SourceMapGenerator represents a source map which is being
built incrementally.
#### new SourceMapGenerator(startOfSourceMap)
To create a new one, you must pass an object with the following properties:
* `file`: The filename of the generated source that this source map is
associated with.
* `sourceRoot`: An optional root for all relative URLs in this source map.
#### SourceMapGenerator.fromSourceMap(sourceMapConsumer)
Creates a new SourceMapGenerator based on a SourceMapConsumer
* `sourceMapConsumer` The SourceMap.
#### SourceMapGenerator.prototype.addMapping(mapping)
Add a single mapping from original source line and column to the generated
source's line and column for this source map being created. The mapping object
should have the following properties:
* `generated`: An object with the generated line and column positions.
* `original`: An object with the original line and column positions.
* `source`: The original source file (relative to the sourceRoot).
* `name`: An optional original token name for this mapping.
#### SourceMapGenerator.prototype.setSourceContent(sourceFile, sourceContent)
Set the source content for an original source file.
* `sourceFile` the URL of the original source file.
* `sourceContent` the content of the source file.
#### SourceMapGenerator.prototype.applySourceMap(sourceMapConsumer[, sourceFile])
Applies a SourceMap for a source file to the SourceMap.
Each mapping to the supplied source file is rewritten using the
supplied SourceMap. Note: The resolution for the resulting mappings
is the minimium of this map and the supplied map.
* `sourceMapConsumer`: The SourceMap to be applied.
* `sourceFile`: Optional. The filename of the source file.
If omitted, sourceMapConsumer.file will be used.
#### SourceMapGenerator.prototype.toString()
Renders the source map being generated to a string.
### SourceNode
SourceNodes provide a way to abstract over interpolating and/or concatenating
snippets of generated JavaScript source code, while maintaining the line and
column information associated between those snippets and the original source
code. This is useful as the final intermediate representation a compiler might
use before outputting the generated JS and source map.
#### new SourceNode(line, column, source[, chunk[, name]])
* `line`: The original line number associated with this source node, or null if
it isn't associated with an original line.
* `column`: The original column number associated with this source node, or null
if it isn't associated with an original column.
* `source`: The original source's filename.
* `chunk`: Optional. Is immediately passed to `SourceNode.prototype.add`, see
below.
* `name`: Optional. The original identifier.
#### SourceNode.fromStringWithSourceMap(code, sourceMapConsumer)
Creates a SourceNode from generated code and a SourceMapConsumer.
* `code`: The generated code
* `sourceMapConsumer` The SourceMap for the generated code
#### SourceNode.prototype.add(chunk)
Add a chunk of generated JS to this source node.
* `chunk`: A string snippet of generated JS code, another instance of
`SourceNode`, or an array where each member is one of those things.
#### SourceNode.prototype.prepend(chunk)
Prepend a chunk of generated JS to this source node.
* `chunk`: A string snippet of generated JS code, another instance of
`SourceNode`, or an array where each member is one of those things.
#### SourceNode.prototype.setSourceContent(sourceFile, sourceContent)
Set the source content for a source file. This will be added to the
`SourceMap` in the `sourcesContent` field.
* `sourceFile`: The filename of the source file
* `sourceContent`: The content of the source file
#### SourceNode.prototype.walk(fn)
Walk over the tree of JS snippets in this node and its children. The walking
function is called once for each snippet of JS and is passed that snippet and
the its original associated source's line/column location.
* `fn`: The traversal function.
#### SourceNode.prototype.walkSourceContents(fn)
Walk over the tree of SourceNodes. The walking function is called for each
source file content and is passed the filename and source content.
* `fn`: The traversal function.
#### SourceNode.prototype.join(sep)
Like `Array.prototype.join` except for SourceNodes. Inserts the separator
between each of this source node's children.
* `sep`: The separator.
#### SourceNode.prototype.replaceRight(pattern, replacement)
Call `String.prototype.replace` on the very right-most source snippet. Useful
for trimming whitespace from the end of a source node, etc.
* `pattern`: The pattern to replace.
* `replacement`: The thing to replace the pattern with.
#### SourceNode.prototype.toString()
Return the string representation of this source node. Walks over the tree and
concatenates all the various snippets together to one string.
### SourceNode.prototype.toStringWithSourceMap(startOfSourceMap)
Returns the string representation of this tree of source nodes, plus a
SourceMapGenerator which contains all the mappings between the generated and
original sources.
The arguments are the same as those to `new SourceMapGenerator`.
## Tests
[![Build Status](https://travis-ci.org/mozilla/source-map.png?branch=master)](https://travis-ci.org/mozilla/source-map)
Install NodeJS version 0.8.0 or greater, then run `node test/run-tests.js`.
To add new tests, create a new file named `test/test-<your new test name>.js`
and export your test functions with names that start with "test", for example
exports["test doing the foo bar"] = function (assert, util) {
...
};
The new test will be located automatically when you run the suite.
The `util` argument is the test utility module located at `test/source-map/util`.
The `assert` argument is a cut down version of node's assert module. You have
access to the following assertion functions:
* `doesNotThrow`
* `equal`
* `ok`
* `strictEqual`
* `throws`
(The reason for the restricted set of test functions is because we need the
tests to run inside Firefox's test suite as well and so the assert module is
shimmed in that environment. See `build/assert-shim.js`.)
[format]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit
[feature]: https://wiki.mozilla.org/DevTools/Features/SourceMap
[Dryice]: https://github.com/mozilla/dryice

View File

@@ -0,0 +1,56 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
define('test/source-map/assert', ['exports'], function (exports) {
let do_throw = function (msg) {
throw new Error(msg);
};
exports.init = function (throw_fn) {
do_throw = throw_fn;
};
exports.doesNotThrow = function (fn) {
try {
fn();
}
catch (e) {
do_throw(e.message);
}
};
exports.equal = function (actual, expected, msg) {
msg = msg || String(actual) + ' != ' + String(expected);
if (actual != expected) {
do_throw(msg);
}
};
exports.ok = function (val, msg) {
msg = msg || String(val) + ' is falsey';
if (!Boolean(val)) {
do_throw(msg);
}
};
exports.strictEqual = function (actual, expected, msg) {
msg = msg || String(actual) + ' !== ' + String(expected);
if (actual !== expected) {
do_throw(msg);
}
};
exports.throws = function (fn) {
try {
fn();
do_throw('Expected an error to be thrown, but it wasn\'t.');
}
catch (e) {
}
};
});

View File

@@ -0,0 +1,152 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/**
* Define a module along with a payload.
* @param {string} moduleName Name for the payload
* @param {ignored} deps Ignored. For compatibility with CommonJS AMD Spec
* @param {function} payload Function with (require, exports, module) params
*/
function define(moduleName, deps, payload) {
if (typeof moduleName != "string") {
throw new TypeError('Expected string, got: ' + moduleName);
}
if (arguments.length == 2) {
payload = deps;
}
if (moduleName in define.modules) {
throw new Error("Module already defined: " + moduleName);
}
define.modules[moduleName] = payload;
};
/**
* The global store of un-instantiated modules
*/
define.modules = {};
/**
* We invoke require() in the context of a Domain so we can have multiple
* sets of modules running separate from each other.
* This contrasts with JSMs which are singletons, Domains allows us to
* optionally load a CommonJS module twice with separate data each time.
* Perhaps you want 2 command lines with a different set of commands in each,
* for example.
*/
function Domain() {
this.modules = {};
this._currentModule = null;
}
(function () {
/**
* Lookup module names and resolve them by calling the definition function if
* needed.
* There are 2 ways to call this, either with an array of dependencies and a
* callback to call when the dependencies are found (which can happen
* asynchronously in an in-page context) or with a single string an no callback
* where the dependency is resolved synchronously and returned.
* The API is designed to be compatible with the CommonJS AMD spec and
* RequireJS.
* @param {string[]|string} deps A name, or names for the payload
* @param {function|undefined} callback Function to call when the dependencies
* are resolved
* @return {undefined|object} The module required or undefined for
* array/callback method
*/
Domain.prototype.require = function(deps, callback) {
if (Array.isArray(deps)) {
var params = deps.map(function(dep) {
return this.lookup(dep);
}, this);
if (callback) {
callback.apply(null, params);
}
return undefined;
}
else {
return this.lookup(deps);
}
};
function normalize(path) {
var bits = path.split('/');
var i = 1;
while (i < bits.length) {
if (bits[i] === '..') {
bits.splice(i-1, 1);
} else if (bits[i] === '.') {
bits.splice(i, 1);
} else {
i++;
}
}
return bits.join('/');
}
function join(a, b) {
a = a.trim();
b = b.trim();
if (/^\//.test(b)) {
return b;
} else {
return a.replace(/\/*$/, '/') + b;
}
}
function dirname(path) {
var bits = path.split('/');
bits.pop();
return bits.join('/');
}
/**
* Lookup module names and resolve them by calling the definition function if
* needed.
* @param {string} moduleName A name for the payload to lookup
* @return {object} The module specified by aModuleName or null if not found.
*/
Domain.prototype.lookup = function(moduleName) {
if (/^\./.test(moduleName)) {
moduleName = normalize(join(dirname(this._currentModule), moduleName));
}
if (moduleName in this.modules) {
var module = this.modules[moduleName];
return module;
}
if (!(moduleName in define.modules)) {
throw new Error("Module not defined: " + moduleName);
}
var module = define.modules[moduleName];
if (typeof module == "function") {
var exports = {};
var previousModule = this._currentModule;
this._currentModule = moduleName;
module(this.require.bind(this), exports, { id: moduleName, uri: "" });
this._currentModule = previousModule;
module = exports;
}
// cache the resulting module object for next time
this.modules[moduleName] = module;
return module;
};
}());
define.Domain = Domain;
define.globalDomain = new Domain();
var require = define.globalDomain.require.bind(define.globalDomain);

View File

@@ -0,0 +1,20 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/*
* WARNING!
*
* Do not edit this file directly, it is built from the sources at
* https://github.com/mozilla/source-map/
*/
///////////////////////////////////////////////////////////////////////////////
this.EXPORTED_SYMBOLS = [ "SourceMapConsumer", "SourceMapGenerator", "SourceNode" ];
Components.utils.import('resource://gre/modules/devtools/Require.jsm');

View File

@@ -0,0 +1,18 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
/*
* WARNING!
*
* Do not edit this file directly, it is built from the sources at
* https://github.com/mozilla/source-map/
*/
Components.utils.import('resource://gre/modules/devtools/Require.jsm');
Components.utils.import('resource://gre/modules/devtools/SourceMap.jsm');
this.EXPORTED_SYMBOLS = [ "define", "runSourceMapTests" ];

View File

@@ -0,0 +1,8 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
///////////////////////////////////////////////////////////////////////////////
this.sourceMap = {
SourceMapConsumer: require('source-map/source-map-consumer').SourceMapConsumer,
SourceMapGenerator: require('source-map/source-map-generator').SourceMapGenerator,
SourceNode: require('source-map/source-node').SourceNode
};

View File

@@ -0,0 +1,6 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
///////////////////////////////////////////////////////////////////////////////
this.SourceMapConsumer = require('source-map/source-map-consumer').SourceMapConsumer;
this.SourceMapGenerator = require('source-map/source-map-generator').SourceMapGenerator;
this.SourceNode = require('source-map/source-node').SourceNode;

View File

@@ -0,0 +1,21 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
function runSourceMapTests(modName, do_throw) {
let mod = require(modName);
let assert = require('test/source-map/assert');
let util = require('test/source-map/util');
assert.init(do_throw);
for (let k in mod) {
if (/^test/.test(k)) {
mod[k](assert, util);
}
}
}
this.runSourceMapTests = runSourceMapTests;

View File

@@ -0,0 +1,8 @@
/*
* WARNING!
*
* Do not edit this file directly, it is built from the sources at
* https://github.com/mozilla/source-map/
*/
Components.utils.import('resource://test/Utils.jsm');

View File

@@ -0,0 +1,3 @@
function run_test() {
runSourceMapTests('{THIS_MODULE}', do_throw);
}

View File

@@ -0,0 +1,8 @@
/*
* Copyright 2009-2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE.txt or:
* http://opensource.org/licenses/BSD-3-Clause
*/
exports.SourceMapGenerator = require('./source-map/source-map-generator').SourceMapGenerator;
exports.SourceMapConsumer = require('./source-map/source-map-consumer').SourceMapConsumer;
exports.SourceNode = require('./source-map/source-node').SourceNode;

View File

@@ -0,0 +1,97 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var util = require('./util');
/**
* A data structure which is a combination of an array and a set. Adding a new
* member is O(1), testing for membership is O(1), and finding the index of an
* element is O(1). Removing elements from the set is not supported. Only
* strings are supported for membership.
*/
function ArraySet() {
this._array = [];
this._set = {};
}
/**
* Static method for creating ArraySet instances from an existing array.
*/
ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
var set = new ArraySet();
for (var i = 0, len = aArray.length; i < len; i++) {
set.add(aArray[i], aAllowDuplicates);
}
return set;
};
/**
* Add the given string to this set.
*
* @param String aStr
*/
ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
var isDuplicate = this.has(aStr);
var idx = this._array.length;
if (!isDuplicate || aAllowDuplicates) {
this._array.push(aStr);
}
if (!isDuplicate) {
this._set[util.toSetString(aStr)] = idx;
}
};
/**
* Is the given string a member of this set?
*
* @param String aStr
*/
ArraySet.prototype.has = function ArraySet_has(aStr) {
return Object.prototype.hasOwnProperty.call(this._set,
util.toSetString(aStr));
};
/**
* What is the index of the given string in the array?
*
* @param String aStr
*/
ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
if (this.has(aStr)) {
return this._set[util.toSetString(aStr)];
}
throw new Error('"' + aStr + '" is not in the set.');
};
/**
* What is the element at the given index?
*
* @param Number aIdx
*/
ArraySet.prototype.at = function ArraySet_at(aIdx) {
if (aIdx >= 0 && aIdx < this._array.length) {
return this._array[aIdx];
}
throw new Error('No element indexed by ' + aIdx);
};
/**
* Returns the array representation of this set (which has the proper indices
* indicated by indexOf). Note that this is a copy of the internal array used
* for storing the members so that no one can mess with internal state.
*/
ArraySet.prototype.toArray = function ArraySet_toArray() {
return this._array.slice();
};
exports.ArraySet = ArraySet;
});

View File

@@ -0,0 +1,144 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*
* Based on the Base 64 VLQ implementation in Closure Compiler:
* https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
*
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var base64 = require('./base64');
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
// length quantities we use in the source map spec, the first bit is the sign,
// the next four bits are the actual value, and the 6th bit is the
// continuation bit. The continuation bit tells us whether there are more
// digits in this value following this digit.
//
// Continuation
// | Sign
// | |
// V V
// 101011
var VLQ_BASE_SHIFT = 5;
// binary: 100000
var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
// binary: 011111
var VLQ_BASE_MASK = VLQ_BASE - 1;
// binary: 100000
var VLQ_CONTINUATION_BIT = VLQ_BASE;
/**
* Converts from a two-complement value to a value where the sign bit is
* is placed in the least significant bit. For example, as decimals:
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
*/
function toVLQSigned(aValue) {
return aValue < 0
? ((-aValue) << 1) + 1
: (aValue << 1) + 0;
}
/**
* Converts to a two-complement value from a value where the sign bit is
* is placed in the least significant bit. For example, as decimals:
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
*/
function fromVLQSigned(aValue) {
var isNegative = (aValue & 1) === 1;
var shifted = aValue >> 1;
return isNegative
? -shifted
: shifted;
}
/**
* Returns the base 64 VLQ encoded value.
*/
exports.encode = function base64VLQ_encode(aValue) {
var encoded = "";
var digit;
var vlq = toVLQSigned(aValue);
do {
digit = vlq & VLQ_BASE_MASK;
vlq >>>= VLQ_BASE_SHIFT;
if (vlq > 0) {
// There are still more digits in this value, so we must make sure the
// continuation bit is marked.
digit |= VLQ_CONTINUATION_BIT;
}
encoded += base64.encode(digit);
} while (vlq > 0);
return encoded;
};
/**
* Decodes the next base 64 VLQ value from the given string and returns the
* value and the rest of the string.
*/
exports.decode = function base64VLQ_decode(aStr) {
var i = 0;
var strLen = aStr.length;
var result = 0;
var shift = 0;
var continuation, digit;
do {
if (i >= strLen) {
throw new Error("Expected more digits in base 64 VLQ value.");
}
digit = base64.decode(aStr.charAt(i++));
continuation = !!(digit & VLQ_CONTINUATION_BIT);
digit &= VLQ_BASE_MASK;
result = result + (digit << shift);
shift += VLQ_BASE_SHIFT;
} while (continuation);
return {
value: fromVLQSigned(result),
rest: aStr.slice(i)
};
};
});

View File

@@ -0,0 +1,42 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var charToIntMap = {};
var intToCharMap = {};
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
.split('')
.forEach(function (ch, index) {
charToIntMap[ch] = index;
intToCharMap[index] = ch;
});
/**
* Encode an integer in the range of 0 to 63 to a single base 64 digit.
*/
exports.encode = function base64_encode(aNumber) {
if (aNumber in intToCharMap) {
return intToCharMap[aNumber];
}
throw new TypeError("Must be between 0 and 63: " + aNumber);
};
/**
* Decode a single base 64 digit to an integer.
*/
exports.decode = function base64_decode(aChar) {
if (aChar in charToIntMap) {
return charToIntMap[aChar];
}
throw new TypeError("Not a valid base 64 digit: " + aChar);
};
});

View File

@@ -0,0 +1,81 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
/**
* Recursive implementation of binary search.
*
* @param aLow Indices here and lower do not contain the needle.
* @param aHigh Indices here and higher do not contain the needle.
* @param aNeedle The element being searched for.
* @param aHaystack The non-empty array being searched.
* @param aCompare Function which takes two elements and returns -1, 0, or 1.
*/
function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare) {
// This function terminates when one of the following is true:
//
// 1. We find the exact element we are looking for.
//
// 2. We did not find the exact element, but we can return the next
// closest element that is less than that element.
//
// 3. We did not find the exact element, and there is no next-closest
// element which is less than the one we are searching for, so we
// return null.
var mid = Math.floor((aHigh - aLow) / 2) + aLow;
var cmp = aCompare(aNeedle, aHaystack[mid], true);
if (cmp === 0) {
// Found the element we are looking for.
return aHaystack[mid];
}
else if (cmp > 0) {
// aHaystack[mid] is greater than our needle.
if (aHigh - mid > 1) {
// The element is in the upper half.
return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare);
}
// We did not find an exact match, return the next closest one
// (termination case 2).
return aHaystack[mid];
}
else {
// aHaystack[mid] is less than our needle.
if (mid - aLow > 1) {
// The element is in the lower half.
return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare);
}
// The exact needle element was not found in this haystack. Determine if
// we are in termination case (2) or (3) and return the appropriate thing.
return aLow < 0
? null
: aHaystack[aLow];
}
}
/**
* This is an implementation of binary search which will always try and return
* the next lowest value checked if there is no exact hit. This is because
* mappings between original and generated line/col pairs are single points,
* and there is an implicit region between each of them, so a miss just means
* that you aren't on the very start of a region.
*
* @param aNeedle The element you are looking for.
* @param aHaystack The array that is being searched.
* @param aCompare A function which takes the needle and an element in the
* array and returns -1, 0, or 1 depending on whether the needle is less
* than, equal to, or greater than the element, respectively.
*/
exports.search = function search(aNeedle, aHaystack, aCompare) {
return aHaystack.length > 0
? recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, aCompare)
: null;
};
});

View File

@@ -0,0 +1,477 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var util = require('./util');
var binarySearch = require('./binary-search');
var ArraySet = require('./array-set').ArraySet;
var base64VLQ = require('./base64-vlq');
/**
* A SourceMapConsumer instance represents a parsed source map which we can
* query for information about the original file positions by giving it a file
* position in the generated source.
*
* The only parameter is the raw source map (either as a JSON string, or
* already parsed to an object). According to the spec, source maps have the
* following attributes:
*
* - version: Which version of the source map spec this map is following.
* - sources: An array of URLs to the original source files.
* - names: An array of identifiers which can be referrenced by individual mappings.
* - sourceRoot: Optional. The URL root from which all sources are relative.
* - sourcesContent: Optional. An array of contents of the original source files.
* - mappings: A string of base64 VLQs which contain the actual mappings.
* - file: The generated file this source map is associated with.
*
* Here is an example source map, taken from the source map spec[0]:
*
* {
* version : 3,
* file: "out.js",
* sourceRoot : "",
* sources: ["foo.js", "bar.js"],
* names: ["src", "maps", "are", "fun"],
* mappings: "AA,AB;;ABCDE;"
* }
*
* [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
*/
function SourceMapConsumer(aSourceMap) {
var sourceMap = aSourceMap;
if (typeof aSourceMap === 'string') {
sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
}
var version = util.getArg(sourceMap, 'version');
var sources = util.getArg(sourceMap, 'sources');
// Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
// requires the array) to play nice here.
var names = util.getArg(sourceMap, 'names', []);
var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
var mappings = util.getArg(sourceMap, 'mappings');
var file = util.getArg(sourceMap, 'file', null);
// Once again, Sass deviates from the spec and supplies the version as a
// string rather than a number, so we use loose equality checking here.
if (version != this._version) {
throw new Error('Unsupported version: ' + version);
}
// Pass `true` below to allow duplicate names and sources. While source maps
// are intended to be compressed and deduplicated, the TypeScript compiler
// sometimes generates source maps with duplicates in them. See Github issue
// #72 and bugzil.la/889492.
this._names = ArraySet.fromArray(names, true);
this._sources = ArraySet.fromArray(sources, true);
this.sourceRoot = sourceRoot;
this.sourcesContent = sourcesContent;
this._mappings = mappings;
this.file = file;
}
/**
* Create a SourceMapConsumer from a SourceMapGenerator.
*
* @param SourceMapGenerator aSourceMap
* The source map that will be consumed.
* @returns SourceMapConsumer
*/
SourceMapConsumer.fromSourceMap =
function SourceMapConsumer_fromSourceMap(aSourceMap) {
var smc = Object.create(SourceMapConsumer.prototype);
smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
smc.sourceRoot = aSourceMap._sourceRoot;
smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
smc.sourceRoot);
smc.file = aSourceMap._file;
smc.__generatedMappings = aSourceMap._mappings.slice()
.sort(util.compareByGeneratedPositions);
smc.__originalMappings = aSourceMap._mappings.slice()
.sort(util.compareByOriginalPositions);
return smc;
};
/**
* The version of the source mapping spec that we are consuming.
*/
SourceMapConsumer.prototype._version = 3;
/**
* The list of original sources.
*/
Object.defineProperty(SourceMapConsumer.prototype, 'sources', {
get: function () {
return this._sources.toArray().map(function (s) {
return this.sourceRoot ? util.join(this.sourceRoot, s) : s;
}, this);
}
});
// `__generatedMappings` and `__originalMappings` are arrays that hold the
// parsed mapping coordinates from the source map's "mappings" attribute. They
// are lazily instantiated, accessed via the `_generatedMappings` and
// `_originalMappings` getters respectively, and we only parse the mappings
// and create these arrays once queried for a source location. We jump through
// these hoops because there can be many thousands of mappings, and parsing
// them is expensive, so we only want to do it if we must.
//
// Each object in the arrays is of the form:
//
// {
// generatedLine: The line number in the generated code,
// generatedColumn: The column number in the generated code,
// source: The path to the original source file that generated this
// chunk of code,
// originalLine: The line number in the original source that
// corresponds to this chunk of generated code,
// originalColumn: The column number in the original source that
// corresponds to this chunk of generated code,
// name: The name of the original symbol which generated this chunk of
// code.
// }
//
// All properties except for `generatedLine` and `generatedColumn` can be
// `null`.
//
// `_generatedMappings` is ordered by the generated positions.
//
// `_originalMappings` is ordered by the original positions.
SourceMapConsumer.prototype.__generatedMappings = null;
Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
get: function () {
if (!this.__generatedMappings) {
this.__generatedMappings = [];
this.__originalMappings = [];
this._parseMappings(this._mappings, this.sourceRoot);
}
return this.__generatedMappings;
}
});
SourceMapConsumer.prototype.__originalMappings = null;
Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
get: function () {
if (!this.__originalMappings) {
this.__generatedMappings = [];
this.__originalMappings = [];
this._parseMappings(this._mappings, this.sourceRoot);
}
return this.__originalMappings;
}
});
/**
* Parse the mappings in a string in to a data structure which we can easily
* query (the ordered arrays in the `this.__generatedMappings` and
* `this.__originalMappings` properties).
*/
SourceMapConsumer.prototype._parseMappings =
function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
var generatedLine = 1;
var previousGeneratedColumn = 0;
var previousOriginalLine = 0;
var previousOriginalColumn = 0;
var previousSource = 0;
var previousName = 0;
var mappingSeparator = /^[,;]/;
var str = aStr;
var mapping;
var temp;
while (str.length > 0) {
if (str.charAt(0) === ';') {
generatedLine++;
str = str.slice(1);
previousGeneratedColumn = 0;
}
else if (str.charAt(0) === ',') {
str = str.slice(1);
}
else {
mapping = {};
mapping.generatedLine = generatedLine;
// Generated column.
temp = base64VLQ.decode(str);
mapping.generatedColumn = previousGeneratedColumn + temp.value;
previousGeneratedColumn = mapping.generatedColumn;
str = temp.rest;
if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
// Original source.
temp = base64VLQ.decode(str);
mapping.source = this._sources.at(previousSource + temp.value);
previousSource += temp.value;
str = temp.rest;
if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
throw new Error('Found a source, but no line and column');
}
// Original line.
temp = base64VLQ.decode(str);
mapping.originalLine = previousOriginalLine + temp.value;
previousOriginalLine = mapping.originalLine;
// Lines are stored 0-based
mapping.originalLine += 1;
str = temp.rest;
if (str.length === 0 || mappingSeparator.test(str.charAt(0))) {
throw new Error('Found a source and line, but no column');
}
// Original column.
temp = base64VLQ.decode(str);
mapping.originalColumn = previousOriginalColumn + temp.value;
previousOriginalColumn = mapping.originalColumn;
str = temp.rest;
if (str.length > 0 && !mappingSeparator.test(str.charAt(0))) {
// Original name.
temp = base64VLQ.decode(str);
mapping.name = this._names.at(previousName + temp.value);
previousName += temp.value;
str = temp.rest;
}
}
this.__generatedMappings.push(mapping);
if (typeof mapping.originalLine === 'number') {
this.__originalMappings.push(mapping);
}
}
}
this.__originalMappings.sort(util.compareByOriginalPositions);
};
/**
* Find the mapping that best matches the hypothetical "needle" mapping that
* we are searching for in the given "haystack" of mappings.
*/
SourceMapConsumer.prototype._findMapping =
function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
aColumnName, aComparator) {
// To return the position we are searching for, we must first find the
// mapping for the given position and then return the opposite position it
// points to. Because the mappings are sorted, we can use binary search to
// find the best mapping.
if (aNeedle[aLineName] <= 0) {
throw new TypeError('Line must be greater than or equal to 1, got '
+ aNeedle[aLineName]);
}
if (aNeedle[aColumnName] < 0) {
throw new TypeError('Column must be greater than or equal to 0, got '
+ aNeedle[aColumnName]);
}
return binarySearch.search(aNeedle, aMappings, aComparator);
};
/**
* Returns the original source, line, and column information for the generated
* source's line and column positions provided. The only argument is an object
* with the following properties:
*
* - line: The line number in the generated source.
* - column: The column number in the generated source.
*
* and an object is returned with the following properties:
*
* - source: The original source file, or null.
* - line: The line number in the original source, or null.
* - column: The column number in the original source, or null.
* - name: The original identifier, or null.
*/
SourceMapConsumer.prototype.originalPositionFor =
function SourceMapConsumer_originalPositionFor(aArgs) {
var needle = {
generatedLine: util.getArg(aArgs, 'line'),
generatedColumn: util.getArg(aArgs, 'column')
};
var mapping = this._findMapping(needle,
this._generatedMappings,
"generatedLine",
"generatedColumn",
util.compareByGeneratedPositions);
if (mapping) {
var source = util.getArg(mapping, 'source', null);
if (source && this.sourceRoot) {
source = util.join(this.sourceRoot, source);
}
return {
source: source,
line: util.getArg(mapping, 'originalLine', null),
column: util.getArg(mapping, 'originalColumn', null),
name: util.getArg(mapping, 'name', null)
};
}
return {
source: null,
line: null,
column: null,
name: null
};
};
/**
* Returns the original source content. The only argument is the url of the
* original source file. Returns null if no original source content is
* availible.
*/
SourceMapConsumer.prototype.sourceContentFor =
function SourceMapConsumer_sourceContentFor(aSource) {
if (!this.sourcesContent) {
return null;
}
if (this.sourceRoot) {
aSource = util.relative(this.sourceRoot, aSource);
}
if (this._sources.has(aSource)) {
return this.sourcesContent[this._sources.indexOf(aSource)];
}
var url;
if (this.sourceRoot
&& (url = util.urlParse(this.sourceRoot))) {
// XXX: file:// URIs and absolute paths lead to unexpected behavior for
// many users. We can help them out when they expect file:// URIs to
// behave like it would if they were running a local HTTP server. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
if (url.scheme == "file"
&& this._sources.has(fileUriAbsPath)) {
return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
}
if ((!url.path || url.path == "/")
&& this._sources.has("/" + aSource)) {
return this.sourcesContent[this._sources.indexOf("/" + aSource)];
}
}
throw new Error('"' + aSource + '" is not in the SourceMap.');
};
/**
* Returns the generated line and column information for the original source,
* line, and column positions provided. The only argument is an object with
* the following properties:
*
* - source: The filename of the original source.
* - line: The line number in the original source.
* - column: The column number in the original source.
*
* and an object is returned with the following properties:
*
* - line: The line number in the generated source, or null.
* - column: The column number in the generated source, or null.
*/
SourceMapConsumer.prototype.generatedPositionFor =
function SourceMapConsumer_generatedPositionFor(aArgs) {
var needle = {
source: util.getArg(aArgs, 'source'),
originalLine: util.getArg(aArgs, 'line'),
originalColumn: util.getArg(aArgs, 'column')
};
if (this.sourceRoot) {
needle.source = util.relative(this.sourceRoot, needle.source);
}
var mapping = this._findMapping(needle,
this._originalMappings,
"originalLine",
"originalColumn",
util.compareByOriginalPositions);
if (mapping) {
return {
line: util.getArg(mapping, 'generatedLine', null),
column: util.getArg(mapping, 'generatedColumn', null)
};
}
return {
line: null,
column: null
};
};
SourceMapConsumer.GENERATED_ORDER = 1;
SourceMapConsumer.ORIGINAL_ORDER = 2;
/**
* Iterate over each mapping between an original source/line/column and a
* generated line/column in this source map.
*
* @param Function aCallback
* The function that is called with each mapping.
* @param Object aContext
* Optional. If specified, this object will be the value of `this` every
* time that `aCallback` is called.
* @param aOrder
* Either `SourceMapConsumer.GENERATED_ORDER` or
* `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
* iterate over the mappings sorted by the generated file's line/column
* order or the original's source/line/column order, respectively. Defaults to
* `SourceMapConsumer.GENERATED_ORDER`.
*/
SourceMapConsumer.prototype.eachMapping =
function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
var context = aContext || null;
var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
var mappings;
switch (order) {
case SourceMapConsumer.GENERATED_ORDER:
mappings = this._generatedMappings;
break;
case SourceMapConsumer.ORIGINAL_ORDER:
mappings = this._originalMappings;
break;
default:
throw new Error("Unknown order of iteration.");
}
var sourceRoot = this.sourceRoot;
mappings.map(function (mapping) {
var source = mapping.source;
if (source && sourceRoot) {
source = util.join(sourceRoot, source);
}
return {
source: source,
generatedLine: mapping.generatedLine,
generatedColumn: mapping.generatedColumn,
originalLine: mapping.originalLine,
originalColumn: mapping.originalColumn,
name: mapping.name
};
}).forEach(aCallback, context);
};
exports.SourceMapConsumer = SourceMapConsumer;
});

View File

@@ -0,0 +1,380 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var base64VLQ = require('./base64-vlq');
var util = require('./util');
var ArraySet = require('./array-set').ArraySet;
/**
* An instance of the SourceMapGenerator represents a source map which is
* being built incrementally. To create a new one, you must pass an object
* with the following properties:
*
* - file: The filename of the generated source.
* - sourceRoot: An optional root for all URLs in this source map.
*/
function SourceMapGenerator(aArgs) {
this._file = util.getArg(aArgs, 'file');
this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
this._sources = new ArraySet();
this._names = new ArraySet();
this._mappings = [];
this._sourcesContents = null;
}
SourceMapGenerator.prototype._version = 3;
/**
* Creates a new SourceMapGenerator based on a SourceMapConsumer
*
* @param aSourceMapConsumer The SourceMap.
*/
SourceMapGenerator.fromSourceMap =
function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
var sourceRoot = aSourceMapConsumer.sourceRoot;
var generator = new SourceMapGenerator({
file: aSourceMapConsumer.file,
sourceRoot: sourceRoot
});
aSourceMapConsumer.eachMapping(function (mapping) {
var newMapping = {
generated: {
line: mapping.generatedLine,
column: mapping.generatedColumn
}
};
if (mapping.source) {
newMapping.source = mapping.source;
if (sourceRoot) {
newMapping.source = util.relative(sourceRoot, newMapping.source);
}
newMapping.original = {
line: mapping.originalLine,
column: mapping.originalColumn
};
if (mapping.name) {
newMapping.name = mapping.name;
}
}
generator.addMapping(newMapping);
});
aSourceMapConsumer.sources.forEach(function (sourceFile) {
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content) {
generator.setSourceContent(sourceFile, content);
}
});
return generator;
};
/**
* Add a single mapping from original source line and column to the generated
* source's line and column for this source map being created. The mapping
* object should have the following properties:
*
* - generated: An object with the generated line and column positions.
* - original: An object with the original line and column positions.
* - source: The original source file (relative to the sourceRoot).
* - name: An optional original token name for this mapping.
*/
SourceMapGenerator.prototype.addMapping =
function SourceMapGenerator_addMapping(aArgs) {
var generated = util.getArg(aArgs, 'generated');
var original = util.getArg(aArgs, 'original', null);
var source = util.getArg(aArgs, 'source', null);
var name = util.getArg(aArgs, 'name', null);
this._validateMapping(generated, original, source, name);
if (source && !this._sources.has(source)) {
this._sources.add(source);
}
if (name && !this._names.has(name)) {
this._names.add(name);
}
this._mappings.push({
generatedLine: generated.line,
generatedColumn: generated.column,
originalLine: original != null && original.line,
originalColumn: original != null && original.column,
source: source,
name: name
});
};
/**
* Set the source content for a source file.
*/
SourceMapGenerator.prototype.setSourceContent =
function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
var source = aSourceFile;
if (this._sourceRoot) {
source = util.relative(this._sourceRoot, source);
}
if (aSourceContent !== null) {
// Add the source content to the _sourcesContents map.
// Create a new _sourcesContents map if the property is null.
if (!this._sourcesContents) {
this._sourcesContents = {};
}
this._sourcesContents[util.toSetString(source)] = aSourceContent;
} else {
// Remove the source file from the _sourcesContents map.
// If the _sourcesContents map is empty, set the property to null.
delete this._sourcesContents[util.toSetString(source)];
if (Object.keys(this._sourcesContents).length === 0) {
this._sourcesContents = null;
}
}
};
/**
* Applies the mappings of a sub-source-map for a specific source file to the
* source map being generated. Each mapping to the supplied source file is
* rewritten using the supplied source map. Note: The resolution for the
* resulting mappings is the minimium of this map and the supplied map.
*
* @param aSourceMapConsumer The source map to be applied.
* @param aSourceFile Optional. The filename of the source file.
* If omitted, SourceMapConsumer's file property will be used.
*/
SourceMapGenerator.prototype.applySourceMap =
function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile) {
// If aSourceFile is omitted, we will use the file property of the SourceMap
if (!aSourceFile) {
aSourceFile = aSourceMapConsumer.file;
}
var sourceRoot = this._sourceRoot;
// Make "aSourceFile" relative if an absolute Url is passed.
if (sourceRoot) {
aSourceFile = util.relative(sourceRoot, aSourceFile);
}
// Applying the SourceMap can add and remove items from the sources and
// the names array.
var newSources = new ArraySet();
var newNames = new ArraySet();
// Find mappings for the "aSourceFile"
this._mappings.forEach(function (mapping) {
if (mapping.source === aSourceFile && mapping.originalLine) {
// Check if it can be mapped by the source map, then update the mapping.
var original = aSourceMapConsumer.originalPositionFor({
line: mapping.originalLine,
column: mapping.originalColumn
});
if (original.source !== null) {
// Copy mapping
if (sourceRoot) {
mapping.source = util.relative(sourceRoot, original.source);
} else {
mapping.source = original.source;
}
mapping.originalLine = original.line;
mapping.originalColumn = original.column;
if (original.name !== null && mapping.name !== null) {
// Only use the identifier name if it's an identifier
// in both SourceMaps
mapping.name = original.name;
}
}
}
var source = mapping.source;
if (source && !newSources.has(source)) {
newSources.add(source);
}
var name = mapping.name;
if (name && !newNames.has(name)) {
newNames.add(name);
}
}, this);
this._sources = newSources;
this._names = newNames;
// Copy sourcesContents of applied map.
aSourceMapConsumer.sources.forEach(function (sourceFile) {
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content) {
if (sourceRoot) {
sourceFile = util.relative(sourceRoot, sourceFile);
}
this.setSourceContent(sourceFile, content);
}
}, this);
};
/**
* A mapping can have one of the three levels of data:
*
* 1. Just the generated position.
* 2. The Generated position, original position, and original source.
* 3. Generated and original position, original source, as well as a name
* token.
*
* To maintain consistency, we validate that any new mapping being added falls
* in to one of these categories.
*/
SourceMapGenerator.prototype._validateMapping =
function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
aName) {
if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
&& aGenerated.line > 0 && aGenerated.column >= 0
&& !aOriginal && !aSource && !aName) {
// Case 1.
return;
}
else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
&& aOriginal && 'line' in aOriginal && 'column' in aOriginal
&& aGenerated.line > 0 && aGenerated.column >= 0
&& aOriginal.line > 0 && aOriginal.column >= 0
&& aSource) {
// Cases 2 and 3.
return;
}
else {
throw new Error('Invalid mapping: ' + JSON.stringify({
generated: aGenerated,
source: aSource,
orginal: aOriginal,
name: aName
}));
}
};
/**
* Serialize the accumulated mappings in to the stream of base 64 VLQs
* specified by the source map format.
*/
SourceMapGenerator.prototype._serializeMappings =
function SourceMapGenerator_serializeMappings() {
var previousGeneratedColumn = 0;
var previousGeneratedLine = 1;
var previousOriginalColumn = 0;
var previousOriginalLine = 0;
var previousName = 0;
var previousSource = 0;
var result = '';
var mapping;
// The mappings must be guaranteed to be in sorted order before we start
// serializing them or else the generated line numbers (which are defined
// via the ';' separators) will be all messed up. Note: it might be more
// performant to maintain the sorting as we insert them, rather than as we
// serialize them, but the big O is the same either way.
this._mappings.sort(util.compareByGeneratedPositions);
for (var i = 0, len = this._mappings.length; i < len; i++) {
mapping = this._mappings[i];
if (mapping.generatedLine !== previousGeneratedLine) {
previousGeneratedColumn = 0;
while (mapping.generatedLine !== previousGeneratedLine) {
result += ';';
previousGeneratedLine++;
}
}
else {
if (i > 0) {
if (!util.compareByGeneratedPositions(mapping, this._mappings[i - 1])) {
continue;
}
result += ',';
}
}
result += base64VLQ.encode(mapping.generatedColumn
- previousGeneratedColumn);
previousGeneratedColumn = mapping.generatedColumn;
if (mapping.source) {
result += base64VLQ.encode(this._sources.indexOf(mapping.source)
- previousSource);
previousSource = this._sources.indexOf(mapping.source);
// lines are stored 0-based in SourceMap spec version 3
result += base64VLQ.encode(mapping.originalLine - 1
- previousOriginalLine);
previousOriginalLine = mapping.originalLine - 1;
result += base64VLQ.encode(mapping.originalColumn
- previousOriginalColumn);
previousOriginalColumn = mapping.originalColumn;
if (mapping.name) {
result += base64VLQ.encode(this._names.indexOf(mapping.name)
- previousName);
previousName = this._names.indexOf(mapping.name);
}
}
}
return result;
};
SourceMapGenerator.prototype._generateSourcesContent =
function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
return aSources.map(function (source) {
if (!this._sourcesContents) {
return null;
}
if (aSourceRoot) {
source = util.relative(aSourceRoot, source);
}
var key = util.toSetString(source);
return Object.prototype.hasOwnProperty.call(this._sourcesContents,
key)
? this._sourcesContents[key]
: null;
}, this);
};
/**
* Externalize the source map.
*/
SourceMapGenerator.prototype.toJSON =
function SourceMapGenerator_toJSON() {
var map = {
version: this._version,
file: this._file,
sources: this._sources.toArray(),
names: this._names.toArray(),
mappings: this._serializeMappings()
};
if (this._sourceRoot) {
map.sourceRoot = this._sourceRoot;
}
if (this._sourcesContents) {
map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
}
return map;
};
/**
* Render the source map being generated to a string.
*/
SourceMapGenerator.prototype.toString =
function SourceMapGenerator_toString() {
return JSON.stringify(this);
};
exports.SourceMapGenerator = SourceMapGenerator;
});

View File

@@ -0,0 +1,371 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapGenerator = require('./source-map-generator').SourceMapGenerator;
var util = require('./util');
/**
* SourceNodes provide a way to abstract over interpolating/concatenating
* snippets of generated JavaScript source code while maintaining the line and
* column information associated with the original source code.
*
* @param aLine The original line number.
* @param aColumn The original column number.
* @param aSource The original source's filename.
* @param aChunks Optional. An array of strings which are snippets of
* generated JS, or other SourceNodes.
* @param aName The original identifier.
*/
function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
this.children = [];
this.sourceContents = {};
this.line = aLine === undefined ? null : aLine;
this.column = aColumn === undefined ? null : aColumn;
this.source = aSource === undefined ? null : aSource;
this.name = aName === undefined ? null : aName;
if (aChunks != null) this.add(aChunks);
}
/**
* Creates a SourceNode from generated code and a SourceMapConsumer.
*
* @param aGeneratedCode The generated code
* @param aSourceMapConsumer The SourceMap for the generated code
*/
SourceNode.fromStringWithSourceMap =
function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer) {
// The SourceNode we want to fill with the generated code
// and the SourceMap
var node = new SourceNode();
// The generated code
// Processed fragments are removed from this array.
var remainingLines = aGeneratedCode.split('\n');
// We need to remember the position of "remainingLines"
var lastGeneratedLine = 1, lastGeneratedColumn = 0;
// The generate SourceNodes we need a code range.
// To extract it current and last mapping is used.
// Here we store the last mapping.
var lastMapping = null;
aSourceMapConsumer.eachMapping(function (mapping) {
if (lastMapping === null) {
// We add the generated code until the first mapping
// to the SourceNode without any mapping.
// Each line is added as separate string.
while (lastGeneratedLine < mapping.generatedLine) {
node.add(remainingLines.shift() + "\n");
lastGeneratedLine++;
}
if (lastGeneratedColumn < mapping.generatedColumn) {
var nextLine = remainingLines[0];
node.add(nextLine.substr(0, mapping.generatedColumn));
remainingLines[0] = nextLine.substr(mapping.generatedColumn);
lastGeneratedColumn = mapping.generatedColumn;
}
} else {
// We add the code from "lastMapping" to "mapping":
// First check if there is a new line in between.
if (lastGeneratedLine < mapping.generatedLine) {
var code = "";
// Associate full lines with "lastMapping"
do {
code += remainingLines.shift() + "\n";
lastGeneratedLine++;
lastGeneratedColumn = 0;
} while (lastGeneratedLine < mapping.generatedLine);
// When we reached the correct line, we add code until we
// reach the correct column too.
if (lastGeneratedColumn < mapping.generatedColumn) {
var nextLine = remainingLines[0];
code += nextLine.substr(0, mapping.generatedColumn);
remainingLines[0] = nextLine.substr(mapping.generatedColumn);
lastGeneratedColumn = mapping.generatedColumn;
}
// Create the SourceNode.
addMappingWithCode(lastMapping, code);
} else {
// There is no new line in between.
// Associate the code between "lastGeneratedColumn" and
// "mapping.generatedColumn" with "lastMapping"
var nextLine = remainingLines[0];
var code = nextLine.substr(0, mapping.generatedColumn -
lastGeneratedColumn);
remainingLines[0] = nextLine.substr(mapping.generatedColumn -
lastGeneratedColumn);
lastGeneratedColumn = mapping.generatedColumn;
addMappingWithCode(lastMapping, code);
}
}
lastMapping = mapping;
}, this);
// We have processed all mappings.
// Associate the remaining code in the current line with "lastMapping"
// and add the remaining lines without any mapping
addMappingWithCode(lastMapping, remainingLines.join("\n"));
// Copy sourcesContent into SourceNode
aSourceMapConsumer.sources.forEach(function (sourceFile) {
var content = aSourceMapConsumer.sourceContentFor(sourceFile);
if (content) {
node.setSourceContent(sourceFile, content);
}
});
return node;
function addMappingWithCode(mapping, code) {
if (mapping === null || mapping.source === undefined) {
node.add(code);
} else {
node.add(new SourceNode(mapping.originalLine,
mapping.originalColumn,
mapping.source,
code,
mapping.name));
}
}
};
/**
* Add a chunk of generated JS to this source node.
*
* @param aChunk A string snippet of generated JS code, another instance of
* SourceNode, or an array where each member is one of those things.
*/
SourceNode.prototype.add = function SourceNode_add(aChunk) {
if (Array.isArray(aChunk)) {
aChunk.forEach(function (chunk) {
this.add(chunk);
}, this);
}
else if (aChunk instanceof SourceNode || typeof aChunk === "string") {
if (aChunk) {
this.children.push(aChunk);
}
}
else {
throw new TypeError(
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
);
}
return this;
};
/**
* Add a chunk of generated JS to the beginning of this source node.
*
* @param aChunk A string snippet of generated JS code, another instance of
* SourceNode, or an array where each member is one of those things.
*/
SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
if (Array.isArray(aChunk)) {
for (var i = aChunk.length-1; i >= 0; i--) {
this.prepend(aChunk[i]);
}
}
else if (aChunk instanceof SourceNode || typeof aChunk === "string") {
this.children.unshift(aChunk);
}
else {
throw new TypeError(
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
);
}
return this;
};
/**
* Walk over the tree of JS snippets in this node and its children. The
* walking function is called once for each snippet of JS and is passed that
* snippet and the its original associated source's line/column location.
*
* @param aFn The traversal function.
*/
SourceNode.prototype.walk = function SourceNode_walk(aFn) {
var chunk;
for (var i = 0, len = this.children.length; i < len; i++) {
chunk = this.children[i];
if (chunk instanceof SourceNode) {
chunk.walk(aFn);
}
else {
if (chunk !== '') {
aFn(chunk, { source: this.source,
line: this.line,
column: this.column,
name: this.name });
}
}
}
};
/**
* Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
* each of `this.children`.
*
* @param aSep The separator.
*/
SourceNode.prototype.join = function SourceNode_join(aSep) {
var newChildren;
var i;
var len = this.children.length;
if (len > 0) {
newChildren = [];
for (i = 0; i < len-1; i++) {
newChildren.push(this.children[i]);
newChildren.push(aSep);
}
newChildren.push(this.children[i]);
this.children = newChildren;
}
return this;
};
/**
* Call String.prototype.replace on the very right-most source snippet. Useful
* for trimming whitespace from the end of a source node, etc.
*
* @param aPattern The pattern to replace.
* @param aReplacement The thing to replace the pattern with.
*/
SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
var lastChild = this.children[this.children.length - 1];
if (lastChild instanceof SourceNode) {
lastChild.replaceRight(aPattern, aReplacement);
}
else if (typeof lastChild === 'string') {
this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
}
else {
this.children.push(''.replace(aPattern, aReplacement));
}
return this;
};
/**
* Set the source content for a source file. This will be added to the SourceMapGenerator
* in the sourcesContent field.
*
* @param aSourceFile The filename of the source file
* @param aSourceContent The content of the source file
*/
SourceNode.prototype.setSourceContent =
function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
};
/**
* Walk over the tree of SourceNodes. The walking function is called for each
* source file content and is passed the filename and source content.
*
* @param aFn The traversal function.
*/
SourceNode.prototype.walkSourceContents =
function SourceNode_walkSourceContents(aFn) {
for (var i = 0, len = this.children.length; i < len; i++) {
if (this.children[i] instanceof SourceNode) {
this.children[i].walkSourceContents(aFn);
}
}
var sources = Object.keys(this.sourceContents);
for (var i = 0, len = sources.length; i < len; i++) {
aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
}
};
/**
* Return the string representation of this source node. Walks over the tree
* and concatenates all the various snippets together to one string.
*/
SourceNode.prototype.toString = function SourceNode_toString() {
var str = "";
this.walk(function (chunk) {
str += chunk;
});
return str;
};
/**
* Returns the string representation of this source node along with a source
* map.
*/
SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
var generated = {
code: "",
line: 1,
column: 0
};
var map = new SourceMapGenerator(aArgs);
var sourceMappingActive = false;
var lastOriginalSource = null;
var lastOriginalLine = null;
var lastOriginalColumn = null;
var lastOriginalName = null;
this.walk(function (chunk, original) {
generated.code += chunk;
if (original.source !== null
&& original.line !== null
&& original.column !== null) {
if(lastOriginalSource !== original.source
|| lastOriginalLine !== original.line
|| lastOriginalColumn !== original.column
|| lastOriginalName !== original.name) {
map.addMapping({
source: original.source,
original: {
line: original.line,
column: original.column
},
generated: {
line: generated.line,
column: generated.column
},
name: original.name
});
}
lastOriginalSource = original.source;
lastOriginalLine = original.line;
lastOriginalColumn = original.column;
lastOriginalName = original.name;
sourceMappingActive = true;
} else if (sourceMappingActive) {
map.addMapping({
generated: {
line: generated.line,
column: generated.column
}
});
lastOriginalSource = null;
sourceMappingActive = false;
}
chunk.split('').forEach(function (ch) {
if (ch === '\n') {
generated.line++;
generated.column = 0;
} else {
generated.column++;
}
});
});
this.walkSourceContents(function (sourceFile, sourceContent) {
map.setSourceContent(sourceFile, sourceContent);
});
return { code: generated.code, map: map };
};
exports.SourceNode = SourceNode;
});

View File

@@ -0,0 +1,205 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
/**
* This is a helper function for getting values from parameter/options
* objects.
*
* @param args The object we are extracting values from
* @param name The name of the property we are getting.
* @param defaultValue An optional value to return if the property is missing
* from the object. If this is not specified and the property is missing, an
* error will be thrown.
*/
function getArg(aArgs, aName, aDefaultValue) {
if (aName in aArgs) {
return aArgs[aName];
} else if (arguments.length === 3) {
return aDefaultValue;
} else {
throw new Error('"' + aName + '" is a required argument.');
}
}
exports.getArg = getArg;
var urlRegexp = /([\w+\-.]+):\/\/((\w+:\w+)@)?([\w.]+)?(:(\d+))?(\S+)?/;
var dataUrlRegexp = /^data:.+\,.+/;
function urlParse(aUrl) {
var match = aUrl.match(urlRegexp);
if (!match) {
return null;
}
return {
scheme: match[1],
auth: match[3],
host: match[4],
port: match[6],
path: match[7]
};
}
exports.urlParse = urlParse;
function urlGenerate(aParsedUrl) {
var url = aParsedUrl.scheme + "://";
if (aParsedUrl.auth) {
url += aParsedUrl.auth + "@"
}
if (aParsedUrl.host) {
url += aParsedUrl.host;
}
if (aParsedUrl.port) {
url += ":" + aParsedUrl.port
}
if (aParsedUrl.path) {
url += aParsedUrl.path;
}
return url;
}
exports.urlGenerate = urlGenerate;
function join(aRoot, aPath) {
var url;
if (aPath.match(urlRegexp) || aPath.match(dataUrlRegexp)) {
return aPath;
}
if (aPath.charAt(0) === '/' && (url = urlParse(aRoot))) {
url.path = aPath;
return urlGenerate(url);
}
return aRoot.replace(/\/$/, '') + '/' + aPath;
}
exports.join = join;
/**
* Because behavior goes wacky when you set `__proto__` on objects, we
* have to prefix all the strings in our set with an arbitrary character.
*
* See https://github.com/mozilla/source-map/pull/31 and
* https://github.com/mozilla/source-map/issues/30
*
* @param String aStr
*/
function toSetString(aStr) {
return '$' + aStr;
}
exports.toSetString = toSetString;
function fromSetString(aStr) {
return aStr.substr(1);
}
exports.fromSetString = fromSetString;
function relative(aRoot, aPath) {
aRoot = aRoot.replace(/\/$/, '');
var url = urlParse(aRoot);
if (aPath.charAt(0) == "/" && url && url.path == "/") {
return aPath.slice(1);
}
return aPath.indexOf(aRoot + '/') === 0
? aPath.substr(aRoot.length + 1)
: aPath;
}
exports.relative = relative;
function strcmp(aStr1, aStr2) {
var s1 = aStr1 || "";
var s2 = aStr2 || "";
return (s1 > s2) - (s1 < s2);
}
/**
* Comparator between two mappings where the original positions are compared.
*
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
* mappings with the same original source/line/column, but different generated
* line and column the same. Useful when searching for a mapping with a
* stubbed out mapping.
*/
function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
var cmp;
cmp = strcmp(mappingA.source, mappingB.source);
if (cmp) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp || onlyCompareOriginal) {
return cmp;
}
cmp = strcmp(mappingA.name, mappingB.name);
if (cmp) {
return cmp;
}
cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp) {
return cmp;
}
return mappingA.generatedColumn - mappingB.generatedColumn;
};
exports.compareByOriginalPositions = compareByOriginalPositions;
/**
* Comparator between two mappings where the generated positions are
* compared.
*
* Optionally pass in `true` as `onlyCompareGenerated` to consider two
* mappings with the same generated line and column, but different
* source/name/original line and column the same. Useful when searching for a
* mapping with a stubbed out mapping.
*/
function compareByGeneratedPositions(mappingA, mappingB, onlyCompareGenerated) {
var cmp;
cmp = mappingA.generatedLine - mappingB.generatedLine;
if (cmp) {
return cmp;
}
cmp = mappingA.generatedColumn - mappingB.generatedColumn;
if (cmp || onlyCompareGenerated) {
return cmp;
}
cmp = strcmp(mappingA.source, mappingB.source);
if (cmp) {
return cmp;
}
cmp = mappingA.originalLine - mappingB.originalLine;
if (cmp) {
return cmp;
}
cmp = mappingA.originalColumn - mappingB.originalColumn;
if (cmp) {
return cmp;
}
return strcmp(mappingA.name, mappingB.name);
};
exports.compareByGeneratedPositions = compareByGeneratedPositions;
});

View File

@@ -0,0 +1,58 @@
amdefine is released under two licenses: new BSD, and MIT. You may pick the
license that best suits your development needs. The text of both licenses are
provided below.
The "New" BSD License:
----------------------
Copyright (c) 2011-2015, The Dojo Foundation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the Dojo Foundation nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
MIT License
-----------
Copyright (c) 2011-2015, The Dojo Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,171 @@
# amdefine
A module that can be used to implement AMD's define() in Node. This allows you
to code to the AMD API and have the module work in node programs without
requiring those other programs to use AMD.
## Usage
**1)** Update your package.json to indicate amdefine as a dependency:
```javascript
"dependencies": {
"amdefine": ">=0.1.0"
}
```
Then run `npm install` to get amdefine into your project.
**2)** At the top of each module that uses define(), place this code:
```javascript
if (typeof define !== 'function') { var define = require('amdefine')(module) }
```
**Only use these snippets** when loading amdefine. If you preserve the basic structure,
with the braces, it will be stripped out when using the [RequireJS optimizer](#optimizer).
You can add spaces, line breaks and even require amdefine with a local path, but
keep the rest of the structure to get the stripping behavior.
As you may know, because `if` statements in JavaScript don't have their own scope, the var
declaration in the above snippet is made whether the `if` expression is truthy or not. If
RequireJS is loaded then the declaration is superfluous because `define` is already already
declared in the same scope in RequireJS. Fortunately JavaScript handles multiple `var`
declarations of the same variable in the same scope gracefully.
If you want to deliver amdefine.js with your code rather than specifying it as a dependency
with npm, then just download the latest release and refer to it using a relative path:
[Latest Version](https://github.com/jrburke/amdefine/raw/latest/amdefine.js)
### amdefine/intercept
Consider this very experimental.
Instead of pasting the piece of text for the amdefine setup of a `define`
variable in each module you create or consume, you can use `amdefine/intercept`
instead. It will automatically insert the above snippet in each .js file loaded
by Node.
**Warning**: you should only use this if you are creating an application that
is consuming AMD style defined()'d modules that are distributed via npm and want
to run that code in Node.
For library code where you are not sure if it will be used by others in Node or
in the browser, then explicitly depending on amdefine and placing the code
snippet above is suggested path, instead of using `amdefine/intercept`. The
intercept module affects all .js files loaded in the Node app, and it is
inconsiderate to modify global state like that unless you are also controlling
the top level app.
#### Why distribute AMD-style modules via npm?
npm has a lot of weaknesses for front-end use (installed layout is not great,
should have better support for the `baseUrl + moduleID + '.js' style of loading,
single file JS installs), but some people want a JS package manager and are
willing to live with those constraints. If that is you, but still want to author
in AMD style modules to get dynamic require([]), better direct source usage and
powerful loader plugin support in the browser, then this tool can help.
#### amdefine/intercept usage
Just require it in your top level app module (for example index.js, server.js):
```javascript
require('amdefine/intercept');
```
The module does not return a value, so no need to assign the result to a local
variable.
Then just require() code as you normally would with Node's require(). Any .js
loaded after the intercept require will have the amdefine check injected in
the .js source as it is loaded. It does not modify the source on disk, just
prepends some content to the text of the module as it is loaded by Node.
#### How amdefine/intercept works
It overrides the `Module._extensions['.js']` in Node to automatically prepend
the amdefine snippet above. So, it will affect any .js file loaded by your
app.
## define() usage
It is best if you use the anonymous forms of define() in your module:
```javascript
define(function (require) {
var dependency = require('dependency');
});
```
or
```javascript
define(['dependency'], function (dependency) {
});
```
## RequireJS optimizer integration. <a name="optimizer"></name>
Version 1.0.3 of the [RequireJS optimizer](http://requirejs.org/docs/optimization.html)
will have support for stripping the `if (typeof define !== 'function')` check
mentioned above, so you can include this snippet for code that runs in the
browser, but avoid taking the cost of the if() statement once the code is
optimized for deployment.
## Node 0.4 Support
If you want to support Node 0.4, then add `require` as the second parameter to amdefine:
```javascript
//Only if you want Node 0.4. If using 0.5 or later, use the above snippet.
if (typeof define !== 'function') { var define = require('amdefine')(module, require) }
```
## Limitations
### Synchronous vs Asynchronous
amdefine creates a define() function that is callable by your code. It will
execute and trace dependencies and call the factory function *synchronously*,
to keep the behavior in line with Node's synchronous dependency tracing.
The exception: calling AMD's callback-style require() from inside a factory
function. The require callback is called on process.nextTick():
```javascript
define(function (require) {
require(['a'], function(a) {
//'a' is loaded synchronously, but
//this callback is called on process.nextTick().
});
});
```
### Loader Plugins
Loader plugins are supported as long as they call their load() callbacks
synchronously. So ones that do network requests will not work. However plugins
like [text](http://requirejs.org/docs/api.html#text) can load text files locally.
The plugin API's `load.fromText()` is **not supported** in amdefine, so this means
transpiler plugins like the [CoffeeScript loader plugin](https://github.com/jrburke/require-cs)
will not work. This may be fixable, but it is a bit complex, and I do not have
enough node-fu to figure it out yet. See the source for amdefine.js if you want
to get an idea of the issues involved.
## Tests
To run the tests, cd to **tests** and run:
```
node all.js
node all-intercept.js
```
## License
New BSD and MIT. Check the LICENSE file for all the details.

View File

@@ -0,0 +1,301 @@
/** vim: et:ts=4:sw=4:sts=4
* @license amdefine 1.0.0 Copyright (c) 2011-2015, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/amdefine for details
*/
/*jslint node: true */
/*global module, process */
'use strict';
/**
* Creates a define for node.
* @param {Object} module the "module" object that is defined by Node for the
* current module.
* @param {Function} [requireFn]. Node's require function for the current module.
* It only needs to be passed in Node versions before 0.5, when module.require
* did not exist.
* @returns {Function} a define function that is usable for the current node
* module.
*/
function amdefine(module, requireFn) {
'use strict';
var defineCache = {},
loaderCache = {},
alreadyCalled = false,
path = require('path'),
makeRequire, stringRequire;
/**
* Trims the . and .. from an array of path segments.
* It will keep a leading path segment if a .. will become
* the first path segment, to help with module name lookups,
* which act like paths, but can be remapped. But the end result,
* all paths that use this function should look normalized.
* NOTE: this method MODIFIES the input array.
* @param {Array} ary the array of path segments.
*/
function trimDots(ary) {
var i, part;
for (i = 0; ary[i]; i+= 1) {
part = ary[i];
if (part === '.') {
ary.splice(i, 1);
i -= 1;
} else if (part === '..') {
if (i === 1 && (ary[2] === '..' || ary[0] === '..')) {
//End of the line. Keep at least one non-dot
//path segment at the front so it can be mapped
//correctly to disk. Otherwise, there is likely
//no path mapping for a path starting with '..'.
//This can still fail, but catches the most reasonable
//uses of ..
break;
} else if (i > 0) {
ary.splice(i - 1, 2);
i -= 2;
}
}
}
}
function normalize(name, baseName) {
var baseParts;
//Adjust any relative paths.
if (name && name.charAt(0) === '.') {
//If have a base name, try to normalize against it,
//otherwise, assume it is a top-level require that will
//be relative to baseUrl in the end.
if (baseName) {
baseParts = baseName.split('/');
baseParts = baseParts.slice(0, baseParts.length - 1);
baseParts = baseParts.concat(name.split('/'));
trimDots(baseParts);
name = baseParts.join('/');
}
}
return name;
}
/**
* Create the normalize() function passed to a loader plugin's
* normalize method.
*/
function makeNormalize(relName) {
return function (name) {
return normalize(name, relName);
};
}
function makeLoad(id) {
function load(value) {
loaderCache[id] = value;
}
load.fromText = function (id, text) {
//This one is difficult because the text can/probably uses
//define, and any relative paths and requires should be relative
//to that id was it would be found on disk. But this would require
//bootstrapping a module/require fairly deeply from node core.
//Not sure how best to go about that yet.
throw new Error('amdefine does not implement load.fromText');
};
return load;
}
makeRequire = function (systemRequire, exports, module, relId) {
function amdRequire(deps, callback) {
if (typeof deps === 'string') {
//Synchronous, single module require('')
return stringRequire(systemRequire, exports, module, deps, relId);
} else {
//Array of dependencies with a callback.
//Convert the dependencies to modules.
deps = deps.map(function (depName) {
return stringRequire(systemRequire, exports, module, depName, relId);
});
//Wait for next tick to call back the require call.
if (callback) {
process.nextTick(function () {
callback.apply(null, deps);
});
}
}
}
amdRequire.toUrl = function (filePath) {
if (filePath.indexOf('.') === 0) {
return normalize(filePath, path.dirname(module.filename));
} else {
return filePath;
}
};
return amdRequire;
};
//Favor explicit value, passed in if the module wants to support Node 0.4.
requireFn = requireFn || function req() {
return module.require.apply(module, arguments);
};
function runFactory(id, deps, factory) {
var r, e, m, result;
if (id) {
e = loaderCache[id] = {};
m = {
id: id,
uri: __filename,
exports: e
};
r = makeRequire(requireFn, e, m, id);
} else {
//Only support one define call per file
if (alreadyCalled) {
throw new Error('amdefine with no module ID cannot be called more than once per file.');
}
alreadyCalled = true;
//Use the real variables from node
//Use module.exports for exports, since
//the exports in here is amdefine exports.
e = module.exports;
m = module;
r = makeRequire(requireFn, e, m, module.id);
}
//If there are dependencies, they are strings, so need
//to convert them to dependency values.
if (deps) {
deps = deps.map(function (depName) {
return r(depName);
});
}
//Call the factory with the right dependencies.
if (typeof factory === 'function') {
result = factory.apply(m.exports, deps);
} else {
result = factory;
}
if (result !== undefined) {
m.exports = result;
if (id) {
loaderCache[id] = m.exports;
}
}
}
stringRequire = function (systemRequire, exports, module, id, relId) {
//Split the ID by a ! so that
var index = id.indexOf('!'),
originalId = id,
prefix, plugin;
if (index === -1) {
id = normalize(id, relId);
//Straight module lookup. If it is one of the special dependencies,
//deal with it, otherwise, delegate to node.
if (id === 'require') {
return makeRequire(systemRequire, exports, module, relId);
} else if (id === 'exports') {
return exports;
} else if (id === 'module') {
return module;
} else if (loaderCache.hasOwnProperty(id)) {
return loaderCache[id];
} else if (defineCache[id]) {
runFactory.apply(null, defineCache[id]);
return loaderCache[id];
} else {
if(systemRequire) {
return systemRequire(originalId);
} else {
throw new Error('No module with ID: ' + id);
}
}
} else {
//There is a plugin in play.
prefix = id.substring(0, index);
id = id.substring(index + 1, id.length);
plugin = stringRequire(systemRequire, exports, module, prefix, relId);
if (plugin.normalize) {
id = plugin.normalize(id, makeNormalize(relId));
} else {
//Normalize the ID normally.
id = normalize(id, relId);
}
if (loaderCache[id]) {
return loaderCache[id];
} else {
plugin.load(id, makeRequire(systemRequire, exports, module, relId), makeLoad(id), {});
return loaderCache[id];
}
}
};
//Create a define function specific to the module asking for amdefine.
function define(id, deps, factory) {
if (Array.isArray(id)) {
factory = deps;
deps = id;
id = undefined;
} else if (typeof id !== 'string') {
factory = id;
id = deps = undefined;
}
if (deps && !Array.isArray(deps)) {
factory = deps;
deps = undefined;
}
if (!deps) {
deps = ['require', 'exports', 'module'];
}
//Set up properties for this module. If an ID, then use
//internal cache. If no ID, then use the external variables
//for this node module.
if (id) {
//Put the module in deep freeze until there is a
//require call for it.
defineCache[id] = [id, deps, factory];
} else {
runFactory(id, deps, factory);
}
}
//define.require, which has access to all the values in the
//cache. Useful for AMD modules that all have IDs in the file,
//but need to finally export a value to node based on one of those
//IDs.
define.require = function (id) {
if (loaderCache[id]) {
return loaderCache[id];
}
if (defineCache[id]) {
runFactory.apply(null, defineCache[id]);
return loaderCache[id];
}
};
define.amd = {};
return define;
}
module.exports = amdefine;

View File

@@ -0,0 +1,36 @@
/*jshint node: true */
var inserted,
Module = require('module'),
fs = require('fs'),
existingExtFn = Module._extensions['.js'],
amdefineRegExp = /amdefine\.js/;
inserted = "if (typeof define !== 'function') {var define = require('amdefine')(module)}";
//From the node/lib/module.js source:
function stripBOM(content) {
// Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
// because the buffer-to-string conversion in `fs.readFileSync()`
// translates it to FEFF, the UTF-16 BOM.
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
return content;
}
//Also adapted from the node/lib/module.js source:
function intercept(module, filename) {
var content = stripBOM(fs.readFileSync(filename, 'utf8'));
if (!amdefineRegExp.test(module.id)) {
content = inserted + content;
}
module._compile(content, filename);
}
intercept._id = 'amdefine/intercept';
if (!existingExtFn._id || existingExtFn._id !== intercept._id) {
Module._extensions['.js'] = intercept;
}

View File

@@ -0,0 +1,46 @@
{
"name": "amdefine",
"description": "Provide AMD's define() API for declaring modules in the AMD format",
"version": "1.0.0",
"homepage": "http://github.com/jrburke/amdefine",
"author": {
"name": "James Burke",
"email": "jrburke@gmail.com",
"url": "http://github.com/jrburke"
},
"license": "BSD-3-Clause AND MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/jrburke/amdefine.git"
},
"main": "./amdefine.js",
"engines": {
"node": ">=0.4.2"
},
"gitHead": "578bc4a3f7dede33f3f3e10edde0c1607005d761",
"bugs": {
"url": "https://github.com/jrburke/amdefine/issues"
},
"_id": "amdefine@1.0.0",
"scripts": {},
"_shasum": "fd17474700cb5cc9c2b709f0be9d23ce3c198c33",
"_from": "amdefine@>=0.0.4",
"_npmVersion": "2.12.1",
"_nodeVersion": "0.10.36",
"_npmUser": {
"name": "jrburke",
"email": "jrburke@gmail.com"
},
"maintainers": [
{
"name": "jrburke",
"email": "jrburke@gmail.com"
}
],
"dist": {
"shasum": "fd17474700cb5cc9c2b709f0be9d23ce3c198c33",
"tarball": "http://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
}

View File

@@ -0,0 +1,130 @@
{
"name": "source-map",
"description": "Generates and consumes source maps",
"version": "0.1.31",
"homepage": "https://github.com/mozilla/source-map",
"author": {
"name": "Nick Fitzgerald",
"email": "nfitzgerald@mozilla.com"
},
"contributors": [
{
"name": "Tobias Koppers",
"email": "tobias.koppers@googlemail.com"
},
{
"name": "Duncan Beevers",
"email": "duncan@dweebd.com"
},
{
"name": "Stephen Crane",
"email": "scrane@mozilla.com"
},
{
"name": "Ryan Seddon",
"email": "seddon.ryan@gmail.com"
},
{
"name": "Miles Elam",
"email": "miles.elam@deem.com"
},
{
"name": "Mihai Bazon",
"email": "mihai.bazon@gmail.com"
},
{
"name": "Michael Ficarra",
"email": "github.public.email@michael.ficarra.me"
},
{
"name": "Todd Wolfson",
"email": "todd@twolfson.com"
},
{
"name": "Alexander Solovyov",
"email": "alexander@solovyov.net"
},
{
"name": "Felix Gnass",
"email": "fgnass@gmail.com"
},
{
"name": "Conrad Irwin",
"email": "conrad.irwin@gmail.com"
},
{
"name": "usrbincc",
"email": "usrbincc@yahoo.com"
},
{
"name": "David Glasser",
"email": "glasser@davidglasser.net"
},
{
"name": "Chase Douglas",
"email": "chase@newrelic.com"
},
{
"name": "Evan Wallace",
"email": "evan.exe@gmail.com"
},
{
"name": "Heather Arthur",
"email": "fayearthur@gmail.com"
}
],
"repository": {
"type": "git",
"url": "http://github.com/mozilla/source-map.git"
},
"directories": {
"lib": "./lib"
},
"main": "./lib/source-map.js",
"engines": {
"node": ">=0.8.0"
},
"licenses": [
{
"type": "BSD",
"url": "http://opensource.org/licenses/BSD-3-Clause"
}
],
"dependencies": {
"amdefine": ">=0.0.4"
},
"devDependencies": {
"dryice": ">=0.4.8"
},
"scripts": {
"test": "node test/run-tests.js",
"build": "node Makefile.dryice.js"
},
"_id": "source-map@0.1.31",
"dist": {
"shasum": "9f704d0d69d9e138a81badf6ebb4fde33d151c61",
"tarball": "http://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz"
},
"_from": "source-map@0.1.31",
"_npmVersion": "1.2.18",
"_npmUser": {
"name": "nickfitzgerald",
"email": "fitzgen@gmail.com"
},
"maintainers": [
{
"name": "mozilla-devtools",
"email": "mozilla-developer-tools@googlegroups.com"
},
{
"name": "mozilla",
"email": "dherman@mozilla.com"
},
{
"name": "nickfitzgerald",
"email": "fitzgen@gmail.com"
}
],
"_shasum": "9f704d0d69d9e138a81badf6ebb4fde33d151c61",
"_resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz"
}

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env node
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var util = require('./source-map/util');
function run(tests) {
var failures = [];
var total = 0;
var passed = 0;
for (var i = 0; i < tests.length; i++) {
for (var k in tests[i].testCase) {
if (/^test/.test(k)) {
total++;
try {
tests[i].testCase[k](assert, util);
passed++;
}
catch (e) {
console.log('FAILED ' + tests[i].name + ': ' + k + '!');
console.log(e.stack);
}
}
}
}
console.log("");
console.log(passed + ' / ' + total + ' tests passed.');
console.log("");
failures.forEach(function (f) {
});
return failures.length;
}
var code;
process.stdout.on('close', function () {
process.exit(code);
});
function isTestFile(f) {
var testToRun = process.argv[2];
return testToRun
? path.basename(testToRun) === f
: /^test\-.*?\.js/.test(f);
}
function toModule(f) {
return './source-map/' + f.replace(/\.js$/, '');
}
var requires = fs.readdirSync(path.join(__dirname, 'source-map'))
.filter(isTestFile)
.map(toModule);
code = run(requires.map(require).map(function (mod, i) {
return {
name: requires[i],
testCase: mod
};
}));
process.exit(code);

View File

@@ -0,0 +1,26 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2012 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var sourceMap;
try {
sourceMap = require('../../lib/source-map');
} catch (e) {
sourceMap = {};
Components.utils.import('resource:///modules/devtools/SourceMap.jsm', sourceMap);
}
exports['test that the api is properly exposed in the top level'] = function (assert, util) {
assert.equal(typeof sourceMap.SourceMapGenerator, "function");
assert.equal(typeof sourceMap.SourceMapConsumer, "function");
assert.equal(typeof sourceMap.SourceNode, "function");
};
});

View File

@@ -0,0 +1,104 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var ArraySet = require('../../lib/source-map/array-set').ArraySet;
function makeTestSet() {
var set = new ArraySet();
for (var i = 0; i < 100; i++) {
set.add(String(i));
}
return set;
}
exports['test .has() membership'] = function (assert, util) {
var set = makeTestSet();
for (var i = 0; i < 100; i++) {
assert.ok(set.has(String(i)));
}
};
exports['test .indexOf() elements'] = function (assert, util) {
var set = makeTestSet();
for (var i = 0; i < 100; i++) {
assert.strictEqual(set.indexOf(String(i)), i);
}
};
exports['test .at() indexing'] = function (assert, util) {
var set = makeTestSet();
for (var i = 0; i < 100; i++) {
assert.strictEqual(set.at(i), String(i));
}
};
exports['test creating from an array'] = function (assert, util) {
var set = ArraySet.fromArray(['foo', 'bar', 'baz', 'quux', 'hasOwnProperty']);
assert.ok(set.has('foo'));
assert.ok(set.has('bar'));
assert.ok(set.has('baz'));
assert.ok(set.has('quux'));
assert.ok(set.has('hasOwnProperty'));
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.indexOf('bar'), 1);
assert.strictEqual(set.indexOf('baz'), 2);
assert.strictEqual(set.indexOf('quux'), 3);
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.at(1), 'bar');
assert.strictEqual(set.at(2), 'baz');
assert.strictEqual(set.at(3), 'quux');
};
exports['test that you can add __proto__; see github issue #30'] = function (assert, util) {
var set = new ArraySet();
set.add('__proto__');
assert.ok(set.has('__proto__'));
assert.strictEqual(set.at(0), '__proto__');
assert.strictEqual(set.indexOf('__proto__'), 0);
};
exports['test .fromArray() with duplicates'] = function (assert, util) {
var set = ArraySet.fromArray(['foo', 'foo']);
assert.ok(set.has('foo'));
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.toArray().length, 1);
set = ArraySet.fromArray(['foo', 'foo'], true);
assert.ok(set.has('foo'));
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.at(1), 'foo');
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.toArray().length, 2);
};
exports['test .add() with duplicates'] = function (assert, util) {
var set = new ArraySet();
set.add('foo');
set.add('foo');
assert.ok(set.has('foo'));
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.toArray().length, 1);
set.add('foo', true);
assert.ok(set.has('foo'));
assert.strictEqual(set.at(0), 'foo');
assert.strictEqual(set.at(1), 'foo');
assert.strictEqual(set.indexOf('foo'), 0);
assert.strictEqual(set.toArray().length, 2);
};
});

View File

@@ -0,0 +1,24 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var base64VLQ = require('../../lib/source-map/base64-vlq');
exports['test normal encoding and decoding'] = function (assert, util) {
var result;
for (var i = -255; i < 256; i++) {
result = base64VLQ.decode(base64VLQ.encode(i));
assert.ok(result);
assert.equal(result.value, i);
assert.equal(result.rest, "");
}
};
});

View File

@@ -0,0 +1,35 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var base64 = require('../../lib/source-map/base64');
exports['test out of range encoding'] = function (assert, util) {
assert.throws(function () {
base64.encode(-1);
});
assert.throws(function () {
base64.encode(64);
});
};
exports['test out of range decoding'] = function (assert, util) {
assert.throws(function () {
base64.decode('=');
});
};
exports['test normal encoding and decoding'] = function (assert, util) {
for (var i = 0; i < 64; i++) {
assert.equal(base64.decode(base64.encode(i)), i);
}
};
});

View File

@@ -0,0 +1,54 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var binarySearch = require('../../lib/source-map/binary-search');
function numberCompare(a, b) {
return a - b;
}
exports['test too high'] = function (assert, util) {
var needle = 30;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.doesNotThrow(function () {
binarySearch.search(needle, haystack, numberCompare);
});
assert.equal(binarySearch.search(needle, haystack, numberCompare), 20);
};
exports['test too low'] = function (assert, util) {
var needle = 1;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.doesNotThrow(function () {
binarySearch.search(needle, haystack, numberCompare);
});
assert.equal(binarySearch.search(needle, haystack, numberCompare), null);
};
exports['test exact search'] = function (assert, util) {
var needle = 4;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.equal(binarySearch.search(needle, haystack, numberCompare), 4);
};
exports['test fuzzy search'] = function (assert, util) {
var needle = 19;
var haystack = [2,4,6,8,10,12,14,16,18,20];
assert.equal(binarySearch.search(needle, haystack, numberCompare), 18);
};
});

View File

@@ -0,0 +1,72 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapConsumer = require('../../lib/source-map/source-map-consumer').SourceMapConsumer;
var SourceMapGenerator = require('../../lib/source-map/source-map-generator').SourceMapGenerator;
exports['test eating our own dog food'] = function (assert, util) {
var smg = new SourceMapGenerator({
file: 'testing.js',
sourceRoot: '/wu/tang'
});
smg.addMapping({
source: 'gza.coffee',
original: { line: 1, column: 0 },
generated: { line: 2, column: 2 }
});
smg.addMapping({
source: 'gza.coffee',
original: { line: 2, column: 0 },
generated: { line: 3, column: 2 }
});
smg.addMapping({
source: 'gza.coffee',
original: { line: 3, column: 0 },
generated: { line: 4, column: 2 }
});
smg.addMapping({
source: 'gza.coffee',
original: { line: 4, column: 0 },
generated: { line: 5, column: 2 }
});
var smc = new SourceMapConsumer(smg.toString());
// Exact
util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 0, null, smc, assert);
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 0, null, smc, assert);
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 0, null, smc, assert);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, smc, assert);
// Fuzzy
// Original to generated
util.assertMapping(2, 0, null, null, null, null, smc, assert, true);
util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true);
util.assertMapping(3, 0, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true);
util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true);
util.assertMapping(4, 0, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true);
util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true);
util.assertMapping(5, 0, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true);
util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, smc, assert, true);
// Generated to original
util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, smc, assert, null, true);
util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, smc, assert, null, true);
util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, smc, assert, null, true);
util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, smc, assert, null, true);
};
});

View File

@@ -0,0 +1,451 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapConsumer = require('../../lib/source-map/source-map-consumer').SourceMapConsumer;
var SourceMapGenerator = require('../../lib/source-map/source-map-generator').SourceMapGenerator;
exports['test that we can instantiate with a string or an objects'] = function (assert, util) {
assert.doesNotThrow(function () {
var map = new SourceMapConsumer(util.testMap);
});
assert.doesNotThrow(function () {
var map = new SourceMapConsumer(JSON.stringify(util.testMap));
});
};
exports['test that the `sources` field has the original sources'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var sources = map.sources;
assert.equal(sources[0], '/the/root/one.js');
assert.equal(sources[1], '/the/root/two.js');
assert.equal(sources.length, 2);
};
exports['test that the source root is reflected in a mapping\'s source field'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var mapping;
mapping = map.originalPositionFor({
line: 2,
column: 1
});
assert.equal(mapping.source, '/the/root/two.js');
mapping = map.originalPositionFor({
line: 1,
column: 1
});
assert.equal(mapping.source, '/the/root/one.js');
};
exports['test mapping tokens back exactly'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, map, assert);
util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, map, assert);
util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, map, assert);
util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', map, assert);
util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, map, assert);
util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', map, assert);
util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', map, assert);
util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, map, assert);
util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, map, assert);
util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, map, assert);
util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', map, assert);
util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, map, assert);
util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', map, assert);
};
exports['test mapping tokens fuzzy'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
// Finding original positions
util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', map, assert, true);
util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', map, assert, true);
util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, map, assert, true);
// Finding generated positions
util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', map, assert, null, true);
util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', map, assert, null, true);
util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, map, assert, null, true);
};
exports['test creating source map consumers with )]}\' prefix'] = function (assert, util) {
assert.doesNotThrow(function () {
var map = new SourceMapConsumer(")]}'" + JSON.stringify(util.testMap));
});
};
exports['test eachMapping'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var previousLine = -Infinity;
var previousColumn = -Infinity;
map.eachMapping(function (mapping) {
assert.ok(mapping.generatedLine >= previousLine);
if (mapping.source) {
assert.equal(mapping.source.indexOf(util.testMap.sourceRoot), 0);
}
if (mapping.generatedLine === previousLine) {
assert.ok(mapping.generatedColumn >= previousColumn);
previousColumn = mapping.generatedColumn;
}
else {
previousLine = mapping.generatedLine;
previousColumn = -Infinity;
}
});
};
exports['test iterating over mappings in a different order'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var previousLine = -Infinity;
var previousColumn = -Infinity;
var previousSource = "";
map.eachMapping(function (mapping) {
assert.ok(mapping.source >= previousSource);
if (mapping.source === previousSource) {
assert.ok(mapping.originalLine >= previousLine);
if (mapping.originalLine === previousLine) {
assert.ok(mapping.originalColumn >= previousColumn);
previousColumn = mapping.originalColumn;
}
else {
previousLine = mapping.originalLine;
previousColumn = -Infinity;
}
}
else {
previousSource = mapping.source;
previousLine = -Infinity;
previousColumn = -Infinity;
}
}, null, SourceMapConsumer.ORIGINAL_ORDER);
};
exports['test that we can set the context for `this` in eachMapping'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMap);
var context = {};
map.eachMapping(function () {
assert.equal(this, context);
}, context);
};
exports['test that the `sourcesContent` field has the original sources'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMapWithSourcesContent);
var sourcesContent = map.sourcesContent;
assert.equal(sourcesContent[0], ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(sourcesContent[1], ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.equal(sourcesContent.length, 2);
};
exports['test that we can get the original sources for the sources'] = function (assert, util) {
var map = new SourceMapConsumer(util.testMapWithSourcesContent);
var sources = map.sources;
assert.equal(map.sourceContentFor(sources[0]), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(map.sourceContentFor(sources[1]), ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.equal(map.sourceContentFor("one.js"), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
assert.equal(map.sourceContentFor("two.js"), ' TWO.inc = function (n) {\n return n + 1;\n };');
assert.throws(function () {
map.sourceContentFor("");
}, Error);
assert.throws(function () {
map.sourceContentFor("/the/root/three.js");
}, Error);
assert.throws(function () {
map.sourceContentFor("three.js");
}, Error);
};
exports['test sourceRoot + generatedPositionFor'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'foo/bar',
file: 'baz.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'bang.coffee'
});
map.addMapping({
original: { line: 5, column: 5 },
generated: { line: 6, column: 6 },
source: 'bang.coffee'
});
map = new SourceMapConsumer(map.toString());
// Should handle without sourceRoot.
var pos = map.generatedPositionFor({
line: 1,
column: 1,
source: 'bang.coffee'
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
// Should handle with sourceRoot.
var pos = map.generatedPositionFor({
line: 1,
column: 1,
source: 'foo/bar/bang.coffee'
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
};
exports['test sourceRoot + originalPositionFor'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'foo/bar',
file: 'baz.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'bang.coffee'
});
map = new SourceMapConsumer(map.toString());
var pos = map.originalPositionFor({
line: 2,
column: 2,
});
// Should always have the prepended source root
assert.equal(pos.source, 'foo/bar/bang.coffee');
assert.equal(pos.line, 1);
assert.equal(pos.column, 1);
};
exports['test github issue #56'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'http://',
file: 'www.example.com/foo.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'www.example.com/original.js'
});
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
assert.equal(sources.length, 1);
assert.equal(sources[0], 'http://www.example.com/original.js');
};
exports['test github issue #43'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'http://example.com',
file: 'foo.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'http://cdn.example.com/original.js'
});
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
assert.equal(sources.length, 1,
'Should only be one source.');
assert.equal(sources[0], 'http://cdn.example.com/original.js',
'Should not be joined with the sourceRoot.');
};
exports['test absolute path, but same host sources'] = function (assert, util) {
var map = new SourceMapGenerator({
sourceRoot: 'http://example.com/foo/bar',
file: 'foo.js'
});
map.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: '/original.js'
});
map = new SourceMapConsumer(map.toString());
var sources = map.sources;
assert.equal(sources.length, 1,
'Should only be one source.');
assert.equal(sources[0], 'http://example.com/original.js',
'Source should be relative the host of the source root.');
};
exports['test github issue #64'] = function (assert, util) {
var map = new SourceMapConsumer({
"version": 3,
"file": "foo.js",
"sourceRoot": "http://example.com/",
"sources": ["/a"],
"names": [],
"mappings": "AACA",
"sourcesContent": ["foo"]
});
assert.equal(map.sourceContentFor("a"), "foo");
assert.equal(map.sourceContentFor("/a"), "foo");
};
exports['test bug 885597'] = function (assert, util) {
var map = new SourceMapConsumer({
"version": 3,
"file": "foo.js",
"sourceRoot": "file:///Users/AlGore/Invented/The/Internet/",
"sources": ["/a"],
"names": [],
"mappings": "AACA",
"sourcesContent": ["foo"]
});
var s = map.sources[0];
assert.equal(map.sourceContentFor(s), "foo");
};
exports['test github issue #72, duplicate sources'] = function (assert, util) {
var map = new SourceMapConsumer({
"version": 3,
"file": "foo.js",
"sources": ["source1.js", "source1.js", "source3.js"],
"names": [],
"mappings": ";EAAC;;IAEE;;MEEE",
"sourceRoot": "http://example.com"
});
var pos = map.originalPositionFor({
line: 2,
column: 2
});
assert.equal(pos.source, 'http://example.com/source1.js');
assert.equal(pos.line, 1);
assert.equal(pos.column, 1);
var pos = map.originalPositionFor({
line: 4,
column: 4
});
assert.equal(pos.source, 'http://example.com/source1.js');
assert.equal(pos.line, 3);
assert.equal(pos.column, 3);
var pos = map.originalPositionFor({
line: 6,
column: 6
});
assert.equal(pos.source, 'http://example.com/source3.js');
assert.equal(pos.line, 5);
assert.equal(pos.column, 5);
};
exports['test github issue #72, duplicate names'] = function (assert, util) {
var map = new SourceMapConsumer({
"version": 3,
"file": "foo.js",
"sources": ["source.js"],
"names": ["name1", "name1", "name3"],
"mappings": ";EAACA;;IAEEA;;MAEEE",
"sourceRoot": "http://example.com"
});
var pos = map.originalPositionFor({
line: 2,
column: 2
});
assert.equal(pos.name, 'name1');
assert.equal(pos.line, 1);
assert.equal(pos.column, 1);
var pos = map.originalPositionFor({
line: 4,
column: 4
});
assert.equal(pos.name, 'name1');
assert.equal(pos.line, 3);
assert.equal(pos.column, 3);
var pos = map.originalPositionFor({
line: 6,
column: 6
});
assert.equal(pos.name, 'name3');
assert.equal(pos.line, 5);
assert.equal(pos.column, 5);
};
exports['test SourceMapConsumer.fromSourceMap'] = function (assert, util) {
var smg = new SourceMapGenerator({
sourceRoot: 'http://example.com/',
file: 'foo.js'
});
smg.addMapping({
original: { line: 1, column: 1 },
generated: { line: 2, column: 2 },
source: 'bar.js'
});
smg.addMapping({
original: { line: 2, column: 2 },
generated: { line: 4, column: 4 },
source: 'baz.js',
name: 'dirtMcGirt'
});
smg.setSourceContent('baz.js', 'baz.js content');
var smc = SourceMapConsumer.fromSourceMap(smg);
assert.equal(smc.file, 'foo.js');
assert.equal(smc.sourceRoot, 'http://example.com/');
assert.equal(smc.sources.length, 2);
assert.equal(smc.sources[0], 'http://example.com/bar.js');
assert.equal(smc.sources[1], 'http://example.com/baz.js');
assert.equal(smc.sourceContentFor('baz.js'), 'baz.js content');
var pos = smc.originalPositionFor({
line: 2,
column: 2
});
assert.equal(pos.line, 1);
assert.equal(pos.column, 1);
assert.equal(pos.source, 'http://example.com/bar.js');
assert.equal(pos.name, null);
pos = smc.generatedPositionFor({
line: 1,
column: 1,
source: 'http://example.com/bar.js'
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
pos = smc.originalPositionFor({
line: 4,
column: 4
});
assert.equal(pos.line, 2);
assert.equal(pos.column, 2);
assert.equal(pos.source, 'http://example.com/baz.js');
assert.equal(pos.name, 'dirtMcGirt');
pos = smc.generatedPositionFor({
line: 2,
column: 2,
source: 'http://example.com/baz.js'
});
assert.equal(pos.line, 4);
assert.equal(pos.column, 4);
};
});

View File

@@ -0,0 +1,417 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapGenerator = require('../../lib/source-map/source-map-generator').SourceMapGenerator;
var SourceMapConsumer = require('../../lib/source-map/source-map-consumer').SourceMapConsumer;
var SourceNode = require('../../lib/source-map/source-node').SourceNode;
var util = require('./util');
exports['test some simple stuff'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'foo.js',
sourceRoot: '.'
});
assert.ok(true);
};
exports['test JSON serialization'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'foo.js',
sourceRoot: '.'
});
assert.equal(map.toString(), JSON.stringify(map));
};
exports['test adding mappings (case 1)'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated-foo.js',
sourceRoot: '.'
});
assert.doesNotThrow(function () {
map.addMapping({
generated: { line: 1, column: 1 }
});
});
};
exports['test adding mappings (case 2)'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated-foo.js',
sourceRoot: '.'
});
assert.doesNotThrow(function () {
map.addMapping({
generated: { line: 1, column: 1 },
source: 'bar.js',
original: { line: 1, column: 1 }
});
});
};
exports['test adding mappings (case 3)'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated-foo.js',
sourceRoot: '.'
});
assert.doesNotThrow(function () {
map.addMapping({
generated: { line: 1, column: 1 },
source: 'bar.js',
original: { line: 1, column: 1 },
name: 'someToken'
});
});
};
exports['test adding mappings (invalid)'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'generated-foo.js',
sourceRoot: '.'
});
// Not enough info.
assert.throws(function () {
map.addMapping({});
});
// Original file position, but no source.
assert.throws(function () {
map.addMapping({
generated: { line: 1, column: 1 },
original: { line: 1, column: 1 }
});
});
};
exports['test that the correct mappings are being generated'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'min.js',
sourceRoot: '/the/root'
});
map.addMapping({
generated: { line: 1, column: 1 },
original: { line: 1, column: 1 },
source: 'one.js'
});
map.addMapping({
generated: { line: 1, column: 5 },
original: { line: 1, column: 5 },
source: 'one.js'
});
map.addMapping({
generated: { line: 1, column: 9 },
original: { line: 1, column: 11 },
source: 'one.js'
});
map.addMapping({
generated: { line: 1, column: 18 },
original: { line: 1, column: 21 },
source: 'one.js',
name: 'bar'
});
map.addMapping({
generated: { line: 1, column: 21 },
original: { line: 2, column: 3 },
source: 'one.js'
});
map.addMapping({
generated: { line: 1, column: 28 },
original: { line: 2, column: 10 },
source: 'one.js',
name: 'baz'
});
map.addMapping({
generated: { line: 1, column: 32 },
original: { line: 2, column: 14 },
source: 'one.js',
name: 'bar'
});
map.addMapping({
generated: { line: 2, column: 1 },
original: { line: 1, column: 1 },
source: 'two.js'
});
map.addMapping({
generated: { line: 2, column: 5 },
original: { line: 1, column: 5 },
source: 'two.js'
});
map.addMapping({
generated: { line: 2, column: 9 },
original: { line: 1, column: 11 },
source: 'two.js'
});
map.addMapping({
generated: { line: 2, column: 18 },
original: { line: 1, column: 21 },
source: 'two.js',
name: 'n'
});
map.addMapping({
generated: { line: 2, column: 21 },
original: { line: 2, column: 3 },
source: 'two.js'
});
map.addMapping({
generated: { line: 2, column: 28 },
original: { line: 2, column: 10 },
source: 'two.js',
name: 'n'
});
map = JSON.parse(map.toString());
util.assertEqualMaps(assert, map, util.testMap);
};
exports['test that source content can be set'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'min.js',
sourceRoot: '/the/root'
});
map.addMapping({
generated: { line: 1, column: 1 },
original: { line: 1, column: 1 },
source: 'one.js'
});
map.addMapping({
generated: { line: 2, column: 1 },
original: { line: 1, column: 1 },
source: 'two.js'
});
map.setSourceContent('one.js', 'one file content');
map = JSON.parse(map.toString());
assert.equal(map.sources[0], 'one.js');
assert.equal(map.sources[1], 'two.js');
assert.equal(map.sourcesContent[0], 'one file content');
assert.equal(map.sourcesContent[1], null);
};
exports['test .fromSourceMap'] = function (assert, util) {
var map = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(util.testMap));
util.assertEqualMaps(assert, map.toJSON(), util.testMap);
};
exports['test .fromSourceMap with sourcesContent'] = function (assert, util) {
var map = SourceMapGenerator.fromSourceMap(
new SourceMapConsumer(util.testMapWithSourcesContent));
util.assertEqualMaps(assert, map.toJSON(), util.testMapWithSourcesContent);
};
exports['test applySourceMap'] = function (assert, util) {
var node = new SourceNode(null, null, null, [
new SourceNode(2, 0, 'fileX', 'lineX2\n'),
'genA1\n',
new SourceNode(2, 0, 'fileY', 'lineY2\n'),
'genA2\n',
new SourceNode(1, 0, 'fileX', 'lineX1\n'),
'genA3\n',
new SourceNode(1, 0, 'fileY', 'lineY1\n')
]);
var mapStep1 = node.toStringWithSourceMap({
file: 'fileA'
}).map;
mapStep1.setSourceContent('fileX', 'lineX1\nlineX2\n');
mapStep1 = mapStep1.toJSON();
node = new SourceNode(null, null, null, [
'gen1\n',
new SourceNode(1, 0, 'fileA', 'lineA1\n'),
new SourceNode(2, 0, 'fileA', 'lineA2\n'),
new SourceNode(3, 0, 'fileA', 'lineA3\n'),
new SourceNode(4, 0, 'fileA', 'lineA4\n'),
new SourceNode(1, 0, 'fileB', 'lineB1\n'),
new SourceNode(2, 0, 'fileB', 'lineB2\n'),
'gen2\n'
]);
var mapStep2 = node.toStringWithSourceMap({
file: 'fileGen'
}).map;
mapStep2.setSourceContent('fileB', 'lineB1\nlineB2\n');
mapStep2 = mapStep2.toJSON();
node = new SourceNode(null, null, null, [
'gen1\n',
new SourceNode(2, 0, 'fileX', 'lineA1\n'),
new SourceNode(2, 0, 'fileA', 'lineA2\n'),
new SourceNode(2, 0, 'fileY', 'lineA3\n'),
new SourceNode(4, 0, 'fileA', 'lineA4\n'),
new SourceNode(1, 0, 'fileB', 'lineB1\n'),
new SourceNode(2, 0, 'fileB', 'lineB2\n'),
'gen2\n'
]);
var expectedMap = node.toStringWithSourceMap({
file: 'fileGen'
}).map;
expectedMap.setSourceContent('fileX', 'lineX1\nlineX2\n');
expectedMap.setSourceContent('fileB', 'lineB1\nlineB2\n');
expectedMap = expectedMap.toJSON();
// apply source map "mapStep1" to "mapStep2"
var generator = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(mapStep2));
generator.applySourceMap(new SourceMapConsumer(mapStep1));
var actualMap = generator.toJSON();
util.assertEqualMaps(assert, actualMap, expectedMap);
};
exports['test sorting with duplicate generated mappings'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'test.js'
});
map.addMapping({
generated: { line: 3, column: 0 },
original: { line: 2, column: 0 },
source: 'a.js'
});
map.addMapping({
generated: { line: 2, column: 0 }
});
map.addMapping({
generated: { line: 2, column: 0 }
});
map.addMapping({
generated: { line: 1, column: 0 },
original: { line: 1, column: 0 },
source: 'a.js'
});
util.assertEqualMaps(assert, map.toJSON(), {
version: 3,
file: 'test.js',
sources: ['a.js'],
names: [],
mappings: 'AAAA;A;AACA'
});
};
exports['test ignore duplicate mappings.'] = function (assert, util) {
var init = { file: 'min.js', sourceRoot: '/the/root' };
var map1, map2;
// null original source location
var nullMapping1 = {
generated: { line: 1, column: 0 }
};
var nullMapping2 = {
generated: { line: 2, column: 2 }
};
map1 = new SourceMapGenerator(init);
map2 = new SourceMapGenerator(init);
map1.addMapping(nullMapping1);
map1.addMapping(nullMapping1);
map2.addMapping(nullMapping1);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
map1.addMapping(nullMapping2);
map1.addMapping(nullMapping1);
map2.addMapping(nullMapping2);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
// original source location
var srcMapping1 = {
generated: { line: 1, column: 0 },
original: { line: 11, column: 0 },
source: 'srcMapping1.js'
};
var srcMapping2 = {
generated: { line: 2, column: 2 },
original: { line: 11, column: 0 },
source: 'srcMapping2.js'
};
map1 = new SourceMapGenerator(init);
map2 = new SourceMapGenerator(init);
map1.addMapping(srcMapping1);
map1.addMapping(srcMapping1);
map2.addMapping(srcMapping1);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
map1.addMapping(srcMapping2);
map1.addMapping(srcMapping1);
map2.addMapping(srcMapping2);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
// full original source and name information
var fullMapping1 = {
generated: { line: 1, column: 0 },
original: { line: 11, column: 0 },
source: 'fullMapping1.js',
name: 'fullMapping1'
};
var fullMapping2 = {
generated: { line: 2, column: 2 },
original: { line: 11, column: 0 },
source: 'fullMapping2.js',
name: 'fullMapping2'
};
map1 = new SourceMapGenerator(init);
map2 = new SourceMapGenerator(init);
map1.addMapping(fullMapping1);
map1.addMapping(fullMapping1);
map2.addMapping(fullMapping1);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
map1.addMapping(fullMapping2);
map1.addMapping(fullMapping1);
map2.addMapping(fullMapping2);
util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
};
exports['test github issue #72, check for duplicate names or sources'] = function (assert, util) {
var map = new SourceMapGenerator({
file: 'test.js'
});
map.addMapping({
generated: { line: 1, column: 1 },
original: { line: 2, column: 2 },
source: 'a.js',
name: 'foo'
});
map.addMapping({
generated: { line: 3, column: 3 },
original: { line: 4, column: 4 },
source: 'a.js',
name: 'foo'
});
util.assertEqualMaps(assert, map.toJSON(), {
version: 3,
file: 'test.js',
sources: ['a.js'],
names: ['foo'],
mappings: 'CACEA;;GAEEA'
});
};
});

View File

@@ -0,0 +1,365 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var SourceMapGenerator = require('../../lib/source-map/source-map-generator').SourceMapGenerator;
var SourceMapConsumer = require('../../lib/source-map/source-map-consumer').SourceMapConsumer;
var SourceNode = require('../../lib/source-map/source-node').SourceNode;
exports['test .add()'] = function (assert, util) {
var node = new SourceNode(null, null, null);
// Adding a string works.
node.add('function noop() {}');
// Adding another source node works.
node.add(new SourceNode(null, null, null));
// Adding an array works.
node.add(['function foo() {',
new SourceNode(null, null, null,
'return 10;'),
'}']);
// Adding other stuff doesn't.
assert.throws(function () {
node.add({});
});
assert.throws(function () {
node.add(function () {});
});
};
exports['test .prepend()'] = function (assert, util) {
var node = new SourceNode(null, null, null);
// Prepending a string works.
node.prepend('function noop() {}');
assert.equal(node.children[0], 'function noop() {}');
assert.equal(node.children.length, 1);
// Prepending another source node works.
node.prepend(new SourceNode(null, null, null));
assert.equal(node.children[0], '');
assert.equal(node.children[1], 'function noop() {}');
assert.equal(node.children.length, 2);
// Prepending an array works.
node.prepend(['function foo() {',
new SourceNode(null, null, null,
'return 10;'),
'}']);
assert.equal(node.children[0], 'function foo() {');
assert.equal(node.children[1], 'return 10;');
assert.equal(node.children[2], '}');
assert.equal(node.children[3], '');
assert.equal(node.children[4], 'function noop() {}');
assert.equal(node.children.length, 5);
// Prepending other stuff doesn't.
assert.throws(function () {
node.prepend({});
});
assert.throws(function () {
node.prepend(function () {});
});
};
exports['test .toString()'] = function (assert, util) {
assert.equal((new SourceNode(null, null, null,
['function foo() {',
new SourceNode(null, null, null, 'return 10;'),
'}'])).toString(),
'function foo() {return 10;}');
};
exports['test .join()'] = function (assert, util) {
assert.equal((new SourceNode(null, null, null,
['a', 'b', 'c', 'd'])).join(', ').toString(),
'a, b, c, d');
};
exports['test .walk()'] = function (assert, util) {
var node = new SourceNode(null, null, null,
['(function () {\n',
' ', new SourceNode(1, 0, 'a.js', ['someCall()']), ';\n',
' ', new SourceNode(2, 0, 'b.js', ['if (foo) bar()']), ';\n',
'}());']);
var expected = [
{ str: '(function () {\n', source: null, line: null, column: null },
{ str: ' ', source: null, line: null, column: null },
{ str: 'someCall()', source: 'a.js', line: 1, column: 0 },
{ str: ';\n', source: null, line: null, column: null },
{ str: ' ', source: null, line: null, column: null },
{ str: 'if (foo) bar()', source: 'b.js', line: 2, column: 0 },
{ str: ';\n', source: null, line: null, column: null },
{ str: '}());', source: null, line: null, column: null },
];
var i = 0;
node.walk(function (chunk, loc) {
assert.equal(expected[i].str, chunk);
assert.equal(expected[i].source, loc.source);
assert.equal(expected[i].line, loc.line);
assert.equal(expected[i].column, loc.column);
i++;
});
};
exports['test .replaceRight'] = function (assert, util) {
var node;
// Not nested
node = new SourceNode(null, null, null, 'hello world');
node.replaceRight(/world/, 'universe');
assert.equal(node.toString(), 'hello universe');
// Nested
node = new SourceNode(null, null, null,
[new SourceNode(null, null, null, 'hey sexy mama, '),
new SourceNode(null, null, null, 'want to kill all humans?')]);
node.replaceRight(/kill all humans/, 'watch Futurama');
assert.equal(node.toString(), 'hey sexy mama, want to watch Futurama?');
};
exports['test .toStringWithSourceMap()'] = function (assert, util) {
var node = new SourceNode(null, null, null,
['(function () {\n',
' ',
new SourceNode(1, 0, 'a.js', 'someCall', 'originalCall'),
new SourceNode(1, 8, 'a.js', '()'),
';\n',
' ', new SourceNode(2, 0, 'b.js', ['if (foo) bar()']), ';\n',
'}());']);
var map = node.toStringWithSourceMap({
file: 'foo.js'
}).map;
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = new SourceMapConsumer(map.toString());
var actual;
actual = map.originalPositionFor({
line: 1,
column: 4
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
actual = map.originalPositionFor({
line: 2,
column: 2
});
assert.equal(actual.source, 'a.js');
assert.equal(actual.line, 1);
assert.equal(actual.column, 0);
assert.equal(actual.name, 'originalCall');
actual = map.originalPositionFor({
line: 3,
column: 2
});
assert.equal(actual.source, 'b.js');
assert.equal(actual.line, 2);
assert.equal(actual.column, 0);
actual = map.originalPositionFor({
line: 3,
column: 16
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
actual = map.originalPositionFor({
line: 4,
column: 2
});
assert.equal(actual.source, null);
assert.equal(actual.line, null);
assert.equal(actual.column, null);
};
exports['test .fromStringWithSourceMap()'] = function (assert, util) {
var node = SourceNode.fromStringWithSourceMap(
util.testGeneratedCode,
new SourceMapConsumer(util.testMap));
var result = node.toStringWithSourceMap({
file: 'min.js'
});
var map = result.map;
var code = result.code;
assert.equal(code, util.testGeneratedCode);
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = map.toJSON();
assert.equal(map.version, util.testMap.version);
assert.equal(map.file, util.testMap.file);
assert.equal(map.mappings, util.testMap.mappings);
};
exports['test .fromStringWithSourceMap() empty map'] = function (assert, util) {
var node = SourceNode.fromStringWithSourceMap(
util.testGeneratedCode,
new SourceMapConsumer(util.emptyMap));
var result = node.toStringWithSourceMap({
file: 'min.js'
});
var map = result.map;
var code = result.code;
assert.equal(code, util.testGeneratedCode);
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = map.toJSON();
assert.equal(map.version, util.emptyMap.version);
assert.equal(map.file, util.emptyMap.file);
assert.equal(map.mappings.length, util.emptyMap.mappings.length);
assert.equal(map.mappings, util.emptyMap.mappings);
};
exports['test .fromStringWithSourceMap() complex version'] = function (assert, util) {
var input = new SourceNode(null, null, null, [
"(function() {\n",
" var Test = {};\n",
" ", new SourceNode(1, 0, "a.js", "Test.A = { value: 1234 };\n"),
" ", new SourceNode(2, 0, "a.js", "Test.A.x = 'xyz';"), "\n",
"}());\n",
"/* Generated Source */"]);
input = input.toStringWithSourceMap({
file: 'foo.js'
});
var node = SourceNode.fromStringWithSourceMap(
input.code,
new SourceMapConsumer(input.map.toString()));
var result = node.toStringWithSourceMap({
file: 'foo.js'
});
var map = result.map;
var code = result.code;
assert.equal(code, input.code);
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = map.toJSON();
var inputMap = input.map.toJSON();
util.assertEqualMaps(assert, map, inputMap);
};
exports['test .fromStringWithSourceMap() merging duplicate mappings'] = function (assert, util) {
var input = new SourceNode(null, null, null, [
new SourceNode(1, 0, "a.js", "(function"),
new SourceNode(1, 0, "a.js", "() {\n"),
" ",
new SourceNode(1, 0, "a.js", "var Test = "),
new SourceNode(1, 0, "b.js", "{};\n"),
new SourceNode(2, 0, "b.js", "Test"),
new SourceNode(2, 0, "b.js", ".A", "A"),
new SourceNode(2, 20, "b.js", " = { value: 1234 };\n", "A"),
"}());\n",
"/* Generated Source */"
]);
input = input.toStringWithSourceMap({
file: 'foo.js'
});
var correctMap = new SourceMapGenerator({
file: 'foo.js'
});
correctMap.addMapping({
generated: { line: 1, column: 0 },
source: 'a.js',
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 2, column: 0 }
});
correctMap.addMapping({
generated: { line: 2, column: 2 },
source: 'a.js',
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 2, column: 13 },
source: 'b.js',
original: { line: 1, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 0 },
source: 'b.js',
original: { line: 2, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 4 },
source: 'b.js',
name: 'A',
original: { line: 2, column: 0 }
});
correctMap.addMapping({
generated: { line: 3, column: 6 },
source: 'b.js',
name: 'A',
original: { line: 2, column: 20 }
});
correctMap.addMapping({
generated: { line: 4, column: 0 }
});
var inputMap = input.map.toJSON();
correctMap = correctMap.toJSON();
util.assertEqualMaps(assert, correctMap, inputMap);
};
exports['test setSourceContent with toStringWithSourceMap'] = function (assert, util) {
var aNode = new SourceNode(1, 1, 'a.js', 'a');
aNode.setSourceContent('a.js', 'someContent');
var node = new SourceNode(null, null, null,
['(function () {\n',
' ', aNode,
' ', new SourceNode(1, 1, 'b.js', 'b'),
'}());']);
node.setSourceContent('b.js', 'otherContent');
var map = node.toStringWithSourceMap({
file: 'foo.js'
}).map;
assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
map = new SourceMapConsumer(map.toString());
assert.equal(map.sources.length, 2);
assert.equal(map.sources[0], 'a.js');
assert.equal(map.sources[1], 'b.js');
assert.equal(map.sourcesContent.length, 2);
assert.equal(map.sourcesContent[0], 'someContent');
assert.equal(map.sourcesContent[1], 'otherContent');
};
exports['test walkSourceContents'] = function (assert, util) {
var aNode = new SourceNode(1, 1, 'a.js', 'a');
aNode.setSourceContent('a.js', 'someContent');
var node = new SourceNode(null, null, null,
['(function () {\n',
' ', aNode,
' ', new SourceNode(1, 1, 'b.js', 'b'),
'}());']);
node.setSourceContent('b.js', 'otherContent');
var results = [];
node.walkSourceContents(function (sourceFile, sourceContent) {
results.push([sourceFile, sourceContent]);
});
assert.equal(results.length, 2);
assert.equal(results[0][0], 'a.js');
assert.equal(results[0][1], 'someContent');
assert.equal(results[1][0], 'b.js');
assert.equal(results[1][1], 'otherContent');
};
});

View File

@@ -0,0 +1,161 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
if (typeof define !== 'function') {
var define = require('amdefine')(module, require);
}
define(function (require, exports, module) {
var util = require('../../lib/source-map/util');
// This is a test mapping which maps functions from two different files
// (one.js and two.js) to a minified generated source.
//
// Here is one.js:
//
// ONE.foo = function (bar) {
// return baz(bar);
// };
//
// Here is two.js:
//
// TWO.inc = function (n) {
// return n + 1;
// };
//
// And here is the generated code (min.js):
//
// ONE.foo=function(a){return baz(a);};
// TWO.inc=function(a){return a+1;};
exports.testGeneratedCode = " ONE.foo=function(a){return baz(a);};\n"+
" TWO.inc=function(a){return a+1;};";
exports.testMap = {
version: 3,
file: 'min.js',
names: ['bar', 'baz', 'n'],
sources: ['one.js', 'two.js'],
sourceRoot: '/the/root',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
exports.testMapWithSourcesContent = {
version: 3,
file: 'min.js',
names: ['bar', 'baz', 'n'],
sources: ['one.js', 'two.js'],
sourcesContent: [
' ONE.foo = function (bar) {\n' +
' return baz(bar);\n' +
' };',
' TWO.inc = function (n) {\n' +
' return n + 1;\n' +
' };'
],
sourceRoot: '/the/root',
mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
};
exports.emptyMap = {
version: 3,
file: 'min.js',
names: [],
sources: [],
mappings: ''
};
function assertMapping(generatedLine, generatedColumn, originalSource,
originalLine, originalColumn, name, map, assert,
dontTestGenerated, dontTestOriginal) {
if (!dontTestOriginal) {
var origMapping = map.originalPositionFor({
line: generatedLine,
column: generatedColumn
});
assert.equal(origMapping.name, name,
'Incorrect name, expected ' + JSON.stringify(name)
+ ', got ' + JSON.stringify(origMapping.name));
assert.equal(origMapping.line, originalLine,
'Incorrect line, expected ' + JSON.stringify(originalLine)
+ ', got ' + JSON.stringify(origMapping.line));
assert.equal(origMapping.column, originalColumn,
'Incorrect column, expected ' + JSON.stringify(originalColumn)
+ ', got ' + JSON.stringify(origMapping.column));
var expectedSource;
if (originalSource && map.sourceRoot && originalSource.indexOf(map.sourceRoot) === 0) {
expectedSource = originalSource;
} else if (originalSource) {
expectedSource = map.sourceRoot
? util.join(map.sourceRoot, originalSource)
: originalSource;
} else {
expectedSource = null;
}
assert.equal(origMapping.source, expectedSource,
'Incorrect source, expected ' + JSON.stringify(expectedSource)
+ ', got ' + JSON.stringify(origMapping.source));
}
if (!dontTestGenerated) {
var genMapping = map.generatedPositionFor({
source: originalSource,
line: originalLine,
column: originalColumn
});
assert.equal(genMapping.line, generatedLine,
'Incorrect line, expected ' + JSON.stringify(generatedLine)
+ ', got ' + JSON.stringify(genMapping.line));
assert.equal(genMapping.column, generatedColumn,
'Incorrect column, expected ' + JSON.stringify(generatedColumn)
+ ', got ' + JSON.stringify(genMapping.column));
}
}
exports.assertMapping = assertMapping;
function assertEqualMaps(assert, actualMap, expectedMap) {
assert.equal(actualMap.version, expectedMap.version, "version mismatch");
assert.equal(actualMap.file, expectedMap.file, "file mismatch");
assert.equal(actualMap.names.length,
expectedMap.names.length,
"names length mismatch: " +
actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
for (var i = 0; i < actualMap.names.length; i++) {
assert.equal(actualMap.names[i],
expectedMap.names[i],
"names[" + i + "] mismatch: " +
actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
}
assert.equal(actualMap.sources.length,
expectedMap.sources.length,
"sources length mismatch: " +
actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
for (var i = 0; i < actualMap.sources.length; i++) {
assert.equal(actualMap.sources[i],
expectedMap.sources[i],
"sources[" + i + "] length mismatch: " +
actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
}
assert.equal(actualMap.sourceRoot,
expectedMap.sourceRoot,
"sourceRoot mismatch: " +
actualMap.sourceRoot + " != " + expectedMap.sourceRoot);
assert.equal(actualMap.mappings, expectedMap.mappings,
"mappings mismatch:\nActual: " + actualMap.mappings + "\nExpected: " + expectedMap.mappings);
if (actualMap.sourcesContent) {
assert.equal(actualMap.sourcesContent.length,
expectedMap.sourcesContent.length,
"sourcesContent length mismatch");
for (var i = 0; i < actualMap.sourcesContent.length; i++) {
assert.equal(actualMap.sourcesContent[i],
expectedMap.sourcesContent[i],
"sourcesContent[" + i + "] mismatch");
}
}
}
exports.assertEqualMaps = assertEqualMaps;
});

View File

@@ -0,0 +1,90 @@
{
"name": "jstransform",
"version": "10.1.0",
"description": "A simple AST visitor-based JS transformer",
"contributors": [
{
"name": "Jeff Morrison",
"email": "jeffmo@fb.com"
}
],
"main": "src/jstransform",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/facebook/jstransform.git"
},
"keywords": [
"transformer",
"compiler",
"syntax",
"visitor"
],
"dependencies": {
"base62": "0.1.1",
"esprima-fb": "13001.1001.0-dev-harmony-fb",
"source-map": "0.1.31"
},
"licenses": [
{
"type": "Apache-2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0"
}
],
"engines": {
"node": ">=0.8.8"
},
"devDependencies": {
"jest-cli": "^0.2",
"jshint": "^2.5.10"
},
"jest": {
"scriptPreprocessor": "<rootDir>/jestPreprocessor.js",
"setupEnvScriptFile": "<rootDir>/jestEnvironment.js",
"testPathIgnorePatterns": [
"/node_modules/",
"/__tests__/[^/]*/.+"
]
},
"scripts": {
"prepublish": "jest && jshint --config=.jshintrc --exclude=node_modules .",
"test": "jest && jshint --config=.jshintrc --exclude=node_modules ."
},
"gitHead": "90201cf6e40549bacf0efe8915600658a9564471",
"bugs": {
"url": "https://github.com/facebook/jstransform/issues"
},
"homepage": "https://github.com/facebook/jstransform",
"_id": "jstransform@10.1.0",
"_shasum": "b4c49bf63f162c108b0348399a8737c713b0a83a",
"_from": "jstransform@>=10.0.1 <11.0.0",
"_npmVersion": "1.4.16",
"_npmUser": {
"name": "gabelevi",
"email": "gabelevi@gmail.com"
},
"maintainers": [
{
"name": "jeffmo",
"email": "jeff@anafx.com"
},
{
"name": "zpao",
"email": "paul@oshannessy.com"
},
{
"name": "amasad",
"email": "amjad.masad@gmail.com"
},
{
"name": "gabelevi",
"email": "gabelevi@gmail.com"
}
],
"dist": {
"shasum": "b4c49bf63f162c108b0348399a8737c713b0a83a",
"tarball": "http://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz",
"readme": "ERROR: No README data found!"
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright 2014 Facebook, Inc.
*
* 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.
*
* @provides Object.es6
* @polyfill
*/
// https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign
if (!Object.assign) {
Object.assign = function(target, sources) {
if (target === null || target === undefined) {
throw new TypeError('Object.assign target cannot be null or undefined');
}
var to = Object(target);
var hasOwnProperty = Object.prototype.hasOwnProperty;
for (var nextIndex = 1; nextIndex < arguments.length; nextIndex++) {
var nextSource = arguments[nextIndex];
if (nextSource === null || nextSource === undefined) {
continue;
}
var from = Object(nextSource);
// We don't currently support accessors nor proxies. Therefore this
// copy cannot throw. If we ever supported this then we must handle
// exceptions and side-effects.
for (var key in from) {
if (hasOwnProperty.call(from, key)) {
to[key] = from[key];
}
}
}
return to;
};
}

View File

@@ -0,0 +1,481 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*
* @emails jeffmo@fb.com
*/
require('mock-modules').autoMockOff();
describe('jstransform', function() {
var transformFn;
var Syntax = require('esprima-fb').Syntax;
beforeEach(function() {
require('mock-modules').dumpCache();
transformFn = require('../jstransform').transform;
});
function _runVisitor(source, nodeCount, visitor) {
var actualVisitationCount = 0;
function shimVisitor(traverse, node, path, state) {
actualVisitationCount++;
return visitor(traverse, node, path, state);
}
shimVisitor.test = visitor.test;
transformFn([shimVisitor], source);
expect(actualVisitationCount).toBe(nodeCount);
}
function testScopeBoundary(source, localIdents, nodeCount, visitorTest) {
function visitor(traverse, node, path, state) {
var actualLocalIdents = Object.keys(state.localScope.identifiers);
expect(actualLocalIdents.sort()).toEqual(localIdents.sort());
}
visitor.test = visitorTest;
_runVisitor(source, nodeCount, visitor);
}
function testParentScope(source, parentIdents, nodeCount, visitorTest) {
function visitor(traverse, node, path, state) {
parentIdents = parentIdents && parentIdents.sort();
var parentScope = state.localScope.parentScope;
var actualParentIdents =
parentScope && Object.keys(parentScope.identifiers).sort();
expect(actualParentIdents).toEqual(parentIdents);
}
visitor.test = visitorTest;
_runVisitor(source, nodeCount, visitor);
}
describe('closure scope boundaries', function() {
it('creates a scope boundary around Program scope', function() {
var source =
'var foo;' +
'var bar, baz;' +
'function blah() {}';
var idents = ['foo', 'bar', 'baz', 'blah'];
testScopeBoundary(source, idents, 3, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
});
it('creates a scope boundary around FunctionDeclarations', function() {
var source =
'var foo;' +
'function blah() {' +
' var bar;' +
' function nested() {' +
' var baz;' +
' }' +
'}';
var programIdents = ['foo', 'blah'];
var blahIdents = ['arguments', 'bar', 'nested'];
var nestedIdents = ['arguments', 'baz'];
testScopeBoundary(source, programIdents, 2, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
testScopeBoundary(source, blahIdents, 2, function(node, path) {
// All direct children of blah()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionDeclaration &&
path[1].id.name === 'blah';
});
testScopeBoundary(source, nestedIdents, 1, function(node, path) {
// All direct children of nested()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionDeclaration &&
path[1].id.name === 'nested';
});
});
it('creates a scope boundary around MethodDefinitions', function() {
var source =
'var foo;' +
'class ClassA {' +
' blah() {' +
' var bar;' +
' }' +
' another() {' +
' var baz;' +
' }' +
'}';
var programIdents = ['foo', 'ClassA'];
var blahIdents = ['arguments', 'bar'];
var anotherIdents = ['arguments', 'baz'];
testScopeBoundary(source, programIdents, 2, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
testScopeBoundary(source, blahIdents, 1, function(node, path) {
// All direct children of blah()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionExpression &&
path[2] && path[2].type === Syntax.MethodDefinition &&
path[2].key.name === 'blah';
});
testScopeBoundary(source, anotherIdents, 1, function(node, path) {
// All direct children of another()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionExpression &&
path[2] && path[2].type === Syntax.MethodDefinition &&
path[2].key.name === 'another';
});
});
it('creates a scope boundary around concise ArrowFunc exprs', function() {
var source =
'var foo;' +
'var bar = baz => baz;';
var programIdents = ['foo', 'bar'];
var barIdents = ['arguments', 'baz'];
testScopeBoundary(source, programIdents, 2, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
testScopeBoundary(source, barIdents, 1, function(node, path) {
return path[0] && path[0].type === Syntax.ArrowFunctionExpression
&& path[0].body === node;
});
});
it('uses VariableDeclarations to determine scope boundary', function() {
var source =
'var foo = 1;' +
'function bar() {' +
' foo++;' +
' function baz() {' +
' var foo = 2;' +
' }' +
'}';
var programIdents = ['foo', 'bar'];
var barIdents = ['arguments', 'baz'];
var bazIdents = ['arguments', 'foo'];
testScopeBoundary(source, programIdents, 2, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
testScopeBoundary(source, barIdents, 2, function(node, path) {
// All direct children of blah()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionDeclaration &&
path[1].id.name === 'bar';
});
testScopeBoundary(source, bazIdents, 1, function(node, path) {
// All direct children of baz()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionDeclaration &&
path[1].id.name === 'baz';
});
});
it('includes function args in functions scope boundary', function() {
var source =
'var foo;' +
'function blah(bar) {' +
' var baz;' +
'}' +
'var blah2 = bar2 => {var baz;};' +
'var blah3 = bar3 => bar3;';
var programIdents = ['foo', 'blah', 'blah2', 'blah3'];
var blahIdents = ['arguments', 'bar', 'baz'];
var blah2Idents = ['arguments', 'bar2', 'baz'];
var blah3Idents = ['arguments', 'bar3'];
testScopeBoundary(source, programIdents, 4, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
testScopeBoundary(source, blahIdents, 1, function(node, path) {
// All direct children of blah()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionDeclaration &&
path[1].id.name === 'blah';
});
testScopeBoundary(source, blah2Idents, 1, function(node, path) {
// All direct children of blah2()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.ArrowFunctionExpression &&
path[2].id.name === 'blah2';
});
testScopeBoundary(source, blah3Idents, 1, function(node, path) {
// All direct children of blah3()
return path[0] && path[0].type === Syntax.ArrowFunctionExpression &&
path[0].body === node &&
path[1].id.name === 'blah3';
});
});
it('includes rest param args in function scope boundaries', function() {
var source =
'var foo;' +
'function blah(...bar) {' +
' var baz;' +
'}' +
'var blah2 = (...bar2) => {var baz;};' +
'var blah3 = (...bar3) => bar3;';
var programIdents = ['foo', 'blah', 'blah2', 'blah3'];
var blahIdents = ['arguments', 'bar', 'baz'];
var blah2Idents = ['arguments', 'bar2', 'baz'];
var blah3Idents = ['arguments', 'bar3'];
testScopeBoundary(source, programIdents, 4, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
testScopeBoundary(source, blahIdents, 1, function(node, path) {
// All direct children of blah()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionDeclaration &&
path[1].id.name === 'blah';
});
testScopeBoundary(source, blah2Idents, 1, function(node, path) {
// All direct children of blah2()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.ArrowFunctionExpression &&
path[2].id.name === 'blah2';
});
testScopeBoundary(source, blah3Idents, 1, function(node, path) {
// All direct children of blah3()
return path[0] && path[0].type === Syntax.ArrowFunctionExpression &&
path[0].body === node &&
path[1].id.name === 'blah3';
});
});
it('puts FunctionExpression names within function scope', function() {
var source =
'var foo;' +
'var bar = function baz() {' +
' var blah;' +
'};';
var programIdents = ['foo', 'bar'];
var bazIdents = ['arguments', 'baz', 'blah'];
testScopeBoundary(source, programIdents, 2, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
testScopeBoundary(source, bazIdents, 1, function(node, path) {
// All direct children of baz()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionExpression &&
path[1].id.name === 'baz';
});
});
});
describe('block scope boundaries', function() {
it('creates a scope boundary around CatchClauses with params', function() {
var source =
'var blah = 0;' +
'try {' +
'} catch (e) {' +
' blah++;' +
'}';
var programIdents = ['blah'];
var catchIdents = ['e'];
testScopeBoundary(source, programIdents, 2, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
testScopeBoundary(source, catchIdents, 1, function(node, path) {
// All direct children of catch(e) block
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.CatchClause;
});
});
it('includes vars defined in CatchClauses in the parent scope', function() {
var source =
'try {' +
'} catch (e) {' +
' var blah;' +
'}';
var programIdents = ['blah'];
var catchIdents = ['e'];
testScopeBoundary(source, programIdents, 1, function(node, path) {
return path[0] && path[0].type === Syntax.Program;
});
testScopeBoundary(source, catchIdents, 1, function(node, path) {
// All direct children of catch(e) block
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.CatchClause;
});
});
});
describe('scope chain linking', function() {
it('links parent scope boundaries', function() {
var source =
'var foo;' +
'function blah() {' +
' var bar;' +
' function nested() {' +
' var baz;' +
' }' +
'}';
var programIdents = ['foo', 'blah'];
var blahIdents = ['arguments', 'bar', 'nested'];
testParentScope(source, programIdents, 2, function(node, path) {
// All direct children of blah()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionDeclaration &&
path[1].id.name === 'blah';
});
testParentScope(source, blahIdents, 1, function(node, path) {
// All direct children of nested()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionDeclaration &&
path[1].id.name === 'nested';
});
});
it('nests MethodDefinition boundaries under parent scope', function() {
var source =
'var foo;' +
'class ClassA {' +
' blah() {' +
' var bar;' +
' }' +
'}';
var programIdents = ['foo', 'ClassA'];
testParentScope(source, programIdents, 1, function(node, path) {
// All direct children of blah()
return path[0] && path[0].type === Syntax.BlockStatement &&
path[1] && path[1].type === Syntax.FunctionExpression &&
path[2] && path[2].type === Syntax.MethodDefinition &&
path[2].key.name === 'blah';
});
});
});
describe('"use strict" tracking', function() {
function testStrictness(expectedStrict, source) {
var visitedNodes = 0;
function visitor(traverse, node, path, state) {
visitedNodes++;
expect(state.scopeIsStrict).toBe(expectedStrict);
}
visitor.test = function(node, path, state) {
return node.type === Syntax.Literal
&& node.value === 'testStr';
};
transformFn([visitor], source);
expect(visitedNodes).toBe(1);
}
it('detects program-level strictness', function() {
testStrictness(false, '"testStr";');
testStrictness(true, '"use strict"; "testStr";');
});
it('detects non-inherited strictness', function() {
testStrictness(true, [
'function foo() {',
' "use strict";',
' "testStr";',
'}'
].join('\n'));
});
it('detects program-inherited strictness', function() {
testStrictness(true, [
'"use strict";',
'function foo() {',
' "testStr";',
'}'
].join('\n'));
});
it('detects function-inherited strictness', function() {
testStrictness(true, [
'function foo() {',
' "use strict";',
' function bar() {',
' "testStr";',
' }',
'}'
].join('\n'));
});
it('does not detect sibling strictness', function() {
testStrictness(false, [
'function foo() {',
' "use strict";',
'}',
'function bar() {',
' "testStr";',
'}'
].join('\n'));
});
});
describe('visitors', function() {
it('should visit nodes in order', function() {
var source = [
'// Foo comment',
'function foo() {}',
'',
'// Bar comment',
'function bar() {}'
].join('\n');
var actualNodes = [];
function visitFunction(traverse, node, path, state) {
actualNodes.push([node.id.name, node.range[0]]);
}
visitFunction.test = function(node, path, state) {
return node.type === Syntax.FunctionDeclaration;
};
function visitComments(traverse, node, path, state) {
actualNodes.push([node.value, node.range[0]]);
}
visitComments.test = function(node, path, state) {
return node.type === 'Line';
};
transformFn([visitComments, visitFunction], source);
expect(actualNodes).toEqual([
[' Foo comment', 0],
['foo', 15],
[' Bar comment', 34],
['bar', 49]
]);
});
});
});

View File

@@ -0,0 +1,66 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*
* @emails jeffmo@fb.com
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('jstransform-utils', function() {
var transform, utils;
var Syntax = require('esprima-fb').Syntax;
beforeEach(function() {
require('mock-modules').dumpCache();
transform = require('../jstransform').transform;
utils = require('../utils');
});
describe('temporary variables', function() {
it('should inject temporary variables at the start of functions', function() {
function visitFunctionBlock(traverse, node, path, state) {
utils.catchup(node.range[0] + 1, state);
var x = utils.injectTempVar(state);
var y = utils.injectTempVar(state);
traverse(node.body, path, state);
utils.append('return ' + x + ' + ' + y + ';', state);
utils.catchup(node.range[1], state);
return false;
}
visitFunctionBlock.test = function(node, path, state) {
var parentType = path.length && path[0].type;
return node.type === Syntax.BlockStatement && (
parentType === Syntax.FunctionDeclaration ||
parentType === Syntax.FunctionExpression
);
};
expect(transform(
[visitFunctionBlock],
'var x = function() {};'
).code).toEqual(
'var x = function() {var $__0, $__1;return $__0 + $__1;};'
);
expect(eval(transform(
[visitFunctionBlock],
'2 + (function sum(x, y)\t{ $__0 = x; $__1 = y; }(3, 5))'
).code)).toEqual(10);
});
});
});

View File

@@ -0,0 +1,86 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/;
var ltrimRe = /^\s*/;
/**
* @param {String} contents
* @return {String}
*/
function extract(contents) {
var match = contents.match(docblockRe);
if (match) {
return match[0].replace(ltrimRe, '') || '';
}
return '';
}
var commentStartRe = /^\/\*\*?/;
var commentEndRe = /\*+\/$/;
var wsRe = /[\t ]+/g;
var stringStartRe = /(\r?\n|^) *\*/g;
var multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g;
var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g;
/**
* @param {String} contents
* @return {Array}
*/
function parse(docblock) {
docblock = docblock
.replace(commentStartRe, '')
.replace(commentEndRe, '')
.replace(wsRe, ' ')
.replace(stringStartRe, '$1');
// Normalize multi-line directives
var prev = '';
while (prev != docblock) {
prev = docblock;
docblock = docblock.replace(multilineRe, "\n$1 $2\n");
}
docblock = docblock.trim();
var result = [];
var match;
while (match = propertyRe.exec(docblock)) {
result.push([match[1], match[2]]);
}
return result;
}
/**
* Same as parse but returns an object of prop: value instead of array of paris
* If a property appers more than once the last one will be returned
*
* @param {String} contents
* @return {Object}
*/
function parseAsObject(docblock) {
var pairs = parse(docblock);
var result = {};
for (var i = 0; i < pairs.length; i++) {
result[pairs[i][0]] = pairs[i][1];
}
return result;
}
exports.extract = extract;
exports.parse = parse;
exports.parseAsObject = parseAsObject;

View File

@@ -0,0 +1,299 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/*jslint node: true*/
"use strict";
var esprima = require('esprima-fb');
var utils = require('./utils');
var getBoundaryNode = utils.getBoundaryNode;
var declareIdentInScope = utils.declareIdentInLocalScope;
var initScopeMetadata = utils.initScopeMetadata;
var Syntax = esprima.Syntax;
/**
* @param {object} node
* @param {object} parentNode
* @return {boolean}
*/
function _nodeIsClosureScopeBoundary(node, parentNode) {
if (node.type === Syntax.Program) {
return true;
}
var parentIsFunction =
parentNode.type === Syntax.FunctionDeclaration
|| parentNode.type === Syntax.FunctionExpression
|| parentNode.type === Syntax.ArrowFunctionExpression;
var parentIsCurlylessArrowFunc =
parentNode.type === Syntax.ArrowFunctionExpression
&& node === parentNode.body;
return parentIsFunction
&& (node.type === Syntax.BlockStatement || parentIsCurlylessArrowFunc);
}
function _nodeIsBlockScopeBoundary(node, parentNode) {
if (node.type === Syntax.Program) {
return false;
}
return node.type === Syntax.BlockStatement
&& parentNode.type === Syntax.CatchClause;
}
/**
* @param {object} node
* @param {array} path
* @param {object} state
*/
function traverse(node, path, state) {
/*jshint -W004*/
// Create a scope stack entry if this is the first node we've encountered in
// its local scope
var startIndex = null;
var parentNode = path[0];
if (!Array.isArray(node) && state.localScope.parentNode !== parentNode) {
if (_nodeIsClosureScopeBoundary(node, parentNode)) {
var scopeIsStrict = state.scopeIsStrict;
if (!scopeIsStrict
&& (node.type === Syntax.BlockStatement
|| node.type === Syntax.Program)) {
scopeIsStrict =
node.body.length > 0
&& node.body[0].type === Syntax.ExpressionStatement
&& node.body[0].expression.type === Syntax.Literal
&& node.body[0].expression.value === 'use strict';
}
if (node.type === Syntax.Program) {
startIndex = state.g.buffer.length;
state = utils.updateState(state, {
scopeIsStrict: scopeIsStrict
});
} else {
startIndex = state.g.buffer.length + 1;
state = utils.updateState(state, {
localScope: {
parentNode: parentNode,
parentScope: state.localScope,
identifiers: {},
tempVarIndex: 0,
tempVars: []
},
scopeIsStrict: scopeIsStrict
});
// All functions have an implicit 'arguments' object in scope
declareIdentInScope('arguments', initScopeMetadata(node), state);
// Include function arg identifiers in the scope boundaries of the
// function
if (parentNode.params.length > 0) {
var param;
var metadata = initScopeMetadata(parentNode, path.slice(1), path[0]);
for (var i = 0; i < parentNode.params.length; i++) {
param = parentNode.params[i];
if (param.type === Syntax.Identifier) {
declareIdentInScope(param.name, metadata, state);
}
}
}
// Include rest arg identifiers in the scope boundaries of their
// functions
if (parentNode.rest) {
var metadata = initScopeMetadata(
parentNode,
path.slice(1),
path[0]
);
declareIdentInScope(parentNode.rest.name, metadata, state);
}
// Named FunctionExpressions scope their name within the body block of
// themselves only
if (parentNode.type === Syntax.FunctionExpression && parentNode.id) {
var metaData =
initScopeMetadata(parentNode, path.parentNodeslice, parentNode);
declareIdentInScope(parentNode.id.name, metaData, state);
}
}
// Traverse and find all local identifiers in this closure first to
// account for function/variable declaration hoisting
collectClosureIdentsAndTraverse(node, path, state);
}
if (_nodeIsBlockScopeBoundary(node, parentNode)) {
startIndex = state.g.buffer.length;
state = utils.updateState(state, {
localScope: {
parentNode: parentNode,
parentScope: state.localScope,
identifiers: {},
tempVarIndex: 0,
tempVars: []
}
});
if (parentNode.type === Syntax.CatchClause) {
var metadata = initScopeMetadata(
parentNode,
path.slice(1),
parentNode
);
declareIdentInScope(parentNode.param.name, metadata, state);
}
collectBlockIdentsAndTraverse(node, path, state);
}
}
// Only catchup() before and after traversing a child node
function traverser(node, path, state) {
node.range && utils.catchup(node.range[0], state);
traverse(node, path, state);
node.range && utils.catchup(node.range[1], state);
}
utils.analyzeAndTraverse(walker, traverser, node, path, state);
// Inject temp variables into the scope.
if (startIndex !== null) {
utils.injectTempVarDeclarations(state, startIndex);
}
}
function collectClosureIdentsAndTraverse(node, path, state) {
utils.analyzeAndTraverse(
visitLocalClosureIdentifiers,
collectClosureIdentsAndTraverse,
node,
path,
state
);
}
function collectBlockIdentsAndTraverse(node, path, state) {
utils.analyzeAndTraverse(
visitLocalBlockIdentifiers,
collectBlockIdentsAndTraverse,
node,
path,
state
);
}
function visitLocalClosureIdentifiers(node, path, state) {
var metaData;
switch (node.type) {
case Syntax.ArrowFunctionExpression:
case Syntax.FunctionExpression:
// Function expressions don't get their names (if there is one) added to
// the closure scope they're defined in
return false;
case Syntax.ClassDeclaration:
case Syntax.ClassExpression:
case Syntax.FunctionDeclaration:
if (node.id) {
metaData = initScopeMetadata(getBoundaryNode(path), path.slice(), node);
declareIdentInScope(node.id.name, metaData, state);
}
return false;
case Syntax.VariableDeclarator:
// Variables have function-local scope
if (path[0].kind === 'var') {
metaData = initScopeMetadata(getBoundaryNode(path), path.slice(), node);
declareIdentInScope(node.id.name, metaData, state);
}
break;
}
}
function visitLocalBlockIdentifiers(node, path, state) {
// TODO: Support 'let' here...maybe...one day...or something...
if (node.type === Syntax.CatchClause) {
return false;
}
}
function walker(node, path, state) {
var visitors = state.g.visitors;
for (var i = 0; i < visitors.length; i++) {
if (visitors[i].test(node, path, state)) {
return visitors[i](traverse, node, path, state);
}
}
}
var _astCache = {};
function getAstForSource(source, options) {
if (_astCache[source] && !options.disableAstCache) {
return _astCache[source];
}
var ast = esprima.parse(source, {
comment: true,
loc: true,
range: true,
sourceType: options.sourceType
});
if (!options.disableAstCache) {
_astCache[source] = ast;
}
return ast;
}
/**
* Applies all available transformations to the source
* @param {array} visitors
* @param {string} source
* @param {?object} options
* @return {object}
*/
function transform(visitors, source, options) {
options = options || {};
var ast;
try {
ast = getAstForSource(source, options);
} catch (e) {
e.message = 'Parse Error: ' + e.message;
throw e;
}
var state = utils.createState(source, ast, options);
state.g.visitors = visitors;
if (options.sourceMap) {
var SourceMapGenerator = require('source-map').SourceMapGenerator;
state.g.sourceMap = new SourceMapGenerator({file: options.filename || 'transformed.js'});
}
traverse(ast, [], state);
utils.catchup(source.length, state);
var ret = {code: state.g.buffer, extra: state.g.extra};
if (options.sourceMap) {
ret.sourceMap = state.g.sourceMap;
ret.sourceMapFilename = options.filename || 'source.js';
}
return ret;
}
exports.transform = transform;
exports.Syntax = Syntax;

View File

@@ -0,0 +1,708 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/*jslint node: true*/
var Syntax = require('esprima-fb').Syntax;
var leadingIndentRegexp = /(^|\n)( {2}|\t)/g;
var nonWhiteRegexp = /(\S)/g;
/**
* A `state` object represents the state of the parser. It has "local" and
* "global" parts. Global contains parser position, source, etc. Local contains
* scope based properties like current class name. State should contain all the
* info required for transformation. It's the only mandatory object that is
* being passed to every function in transform chain.
*
* @param {string} source
* @param {object} transformOptions
* @return {object}
*/
function createState(source, rootNode, transformOptions) {
return {
/**
* A tree representing the current local scope (and its lexical scope chain)
* Useful for tracking identifiers from parent scopes, etc.
* @type {Object}
*/
localScope: {
parentNode: rootNode,
parentScope: null,
identifiers: {},
tempVarIndex: 0,
tempVars: []
},
/**
* The name (and, if applicable, expression) of the super class
* @type {Object}
*/
superClass: null,
/**
* The namespace to use when munging identifiers
* @type {String}
*/
mungeNamespace: '',
/**
* Ref to the node for the current MethodDefinition
* @type {Object}
*/
methodNode: null,
/**
* Ref to the node for the FunctionExpression of the enclosing
* MethodDefinition
* @type {Object}
*/
methodFuncNode: null,
/**
* Name of the enclosing class
* @type {String}
*/
className: null,
/**
* Whether we're currently within a `strict` scope
* @type {Bool}
*/
scopeIsStrict: null,
/**
* Indentation offset
* @type {Number}
*/
indentBy: 0,
/**
* Global state (not affected by updateState)
* @type {Object}
*/
g: {
/**
* A set of general options that transformations can consider while doing
* a transformation:
*
* - minify
* Specifies that transformation steps should do their best to minify
* the output source when possible. This is useful for places where
* minification optimizations are possible with higher-level context
* info than what jsxmin can provide.
*
* For example, the ES6 class transform will minify munged private
* variables if this flag is set.
*/
opts: transformOptions,
/**
* Current position in the source code
* @type {Number}
*/
position: 0,
/**
* Auxiliary data to be returned by transforms
* @type {Object}
*/
extra: {},
/**
* Buffer containing the result
* @type {String}
*/
buffer: '',
/**
* Source that is being transformed
* @type {String}
*/
source: source,
/**
* Cached parsed docblock (see getDocblock)
* @type {object}
*/
docblock: null,
/**
* Whether the thing was used
* @type {Boolean}
*/
tagNamespaceUsed: false,
/**
* If using bolt xjs transformation
* @type {Boolean}
*/
isBolt: undefined,
/**
* Whether to record source map (expensive) or not
* @type {SourceMapGenerator|null}
*/
sourceMap: null,
/**
* Filename of the file being processed. Will be returned as a source
* attribute in the source map
*/
sourceMapFilename: 'source.js',
/**
* Only when source map is used: last line in the source for which
* source map was generated
* @type {Number}
*/
sourceLine: 1,
/**
* Only when source map is used: last line in the buffer for which
* source map was generated
* @type {Number}
*/
bufferLine: 1,
/**
* The top-level Program AST for the original file.
*/
originalProgramAST: null,
sourceColumn: 0,
bufferColumn: 0
}
};
}
/**
* Updates a copy of a given state with "update" and returns an updated state.
*
* @param {object} state
* @param {object} update
* @return {object}
*/
function updateState(state, update) {
var ret = Object.create(state);
Object.keys(update).forEach(function(updatedKey) {
ret[updatedKey] = update[updatedKey];
});
return ret;
}
/**
* Given a state fill the resulting buffer from the original source up to
* the end
*
* @param {number} end
* @param {object} state
* @param {?function} contentTransformer Optional callback to transform newly
* added content.
*/
function catchup(end, state, contentTransformer) {
if (end < state.g.position) {
// cannot move backwards
return;
}
var source = state.g.source.substring(state.g.position, end);
var transformed = updateIndent(source, state);
if (state.g.sourceMap && transformed) {
// record where we are
state.g.sourceMap.addMapping({
generated: { line: state.g.bufferLine, column: state.g.bufferColumn },
original: { line: state.g.sourceLine, column: state.g.sourceColumn },
source: state.g.sourceMapFilename
});
// record line breaks in transformed source
var sourceLines = source.split('\n');
var transformedLines = transformed.split('\n');
// Add line break mappings between last known mapping and the end of the
// added piece. So for the code piece
// (foo, bar);
// > var x = 2;
// > var b = 3;
// var c =
// only add lines marked with ">": 2, 3.
for (var i = 1; i < sourceLines.length - 1; i++) {
state.g.sourceMap.addMapping({
generated: { line: state.g.bufferLine, column: 0 },
original: { line: state.g.sourceLine, column: 0 },
source: state.g.sourceMapFilename
});
state.g.sourceLine++;
state.g.bufferLine++;
}
// offset for the last piece
if (sourceLines.length > 1) {
state.g.sourceLine++;
state.g.bufferLine++;
state.g.sourceColumn = 0;
state.g.bufferColumn = 0;
}
state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;
state.g.bufferColumn +=
transformedLines[transformedLines.length - 1].length;
}
state.g.buffer +=
contentTransformer ? contentTransformer(transformed) : transformed;
state.g.position = end;
}
/**
* Returns original source for an AST node.
* @param {object} node
* @param {object} state
* @return {string}
*/
function getNodeSourceText(node, state) {
return state.g.source.substring(node.range[0], node.range[1]);
}
function _replaceNonWhite(value) {
return value.replace(nonWhiteRegexp, ' ');
}
/**
* Removes all non-whitespace characters
*/
function _stripNonWhite(value) {
return value.replace(nonWhiteRegexp, '');
}
/**
* Finds the position of the next instance of the specified syntactic char in
* the pending source.
*
* NOTE: This will skip instances of the specified char if they sit inside a
* comment body.
*
* NOTE: This function also assumes that the buffer's current position is not
* already within a comment or a string. This is rarely the case since all
* of the buffer-advancement utility methods tend to be used on syntactic
* nodes' range values -- but it's a small gotcha that's worth mentioning.
*/
function getNextSyntacticCharOffset(char, state) {
var pendingSource = state.g.source.substring(state.g.position);
var pendingSourceLines = pendingSource.split('\n');
var charOffset = 0;
var line;
var withinBlockComment = false;
var withinString = false;
lineLoop: while ((line = pendingSourceLines.shift()) !== undefined) {
var lineEndPos = charOffset + line.length;
charLoop: for (; charOffset < lineEndPos; charOffset++) {
var currChar = pendingSource[charOffset];
if (currChar === '"' || currChar === '\'') {
withinString = !withinString;
continue charLoop;
} else if (withinString) {
continue charLoop;
} else if (charOffset + 1 < lineEndPos) {
var nextTwoChars = currChar + line[charOffset + 1];
if (nextTwoChars === '//') {
charOffset = lineEndPos + 1;
continue lineLoop;
} else if (nextTwoChars === '/*') {
withinBlockComment = true;
charOffset += 1;
continue charLoop;
} else if (nextTwoChars === '*/') {
withinBlockComment = false;
charOffset += 1;
continue charLoop;
}
}
if (!withinBlockComment && currChar === char) {
return charOffset + state.g.position;
}
}
// Account for '\n'
charOffset++;
withinString = false;
}
throw new Error('`' + char + '` not found!');
}
/**
* Catches up as `catchup` but replaces non-whitespace chars with spaces.
*/
function catchupWhiteOut(end, state) {
catchup(end, state, _replaceNonWhite);
}
/**
* Catches up as `catchup` but removes all non-whitespace characters.
*/
function catchupWhiteSpace(end, state) {
catchup(end, state, _stripNonWhite);
}
/**
* Removes all non-newline characters
*/
var reNonNewline = /[^\n]/g;
function stripNonNewline(value) {
return value.replace(reNonNewline, function() {
return '';
});
}
/**
* Catches up as `catchup` but removes all non-newline characters.
*
* Equivalent to appending as many newlines as there are in the original source
* between the current position and `end`.
*/
function catchupNewlines(end, state) {
catchup(end, state, stripNonNewline);
}
/**
* Same as catchup but does not touch the buffer
*
* @param {number} end
* @param {object} state
*/
function move(end, state) {
// move the internal cursors
if (state.g.sourceMap) {
if (end < state.g.position) {
state.g.position = 0;
state.g.sourceLine = 1;
state.g.sourceColumn = 0;
}
var source = state.g.source.substring(state.g.position, end);
var sourceLines = source.split('\n');
if (sourceLines.length > 1) {
state.g.sourceLine += sourceLines.length - 1;
state.g.sourceColumn = 0;
}
state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;
}
state.g.position = end;
}
/**
* Appends a string of text to the buffer
*
* @param {string} str
* @param {object} state
*/
function append(str, state) {
if (state.g.sourceMap && str) {
state.g.sourceMap.addMapping({
generated: { line: state.g.bufferLine, column: state.g.bufferColumn },
original: { line: state.g.sourceLine, column: state.g.sourceColumn },
source: state.g.sourceMapFilename
});
var transformedLines = str.split('\n');
if (transformedLines.length > 1) {
state.g.bufferLine += transformedLines.length - 1;
state.g.bufferColumn = 0;
}
state.g.bufferColumn +=
transformedLines[transformedLines.length - 1].length;
}
state.g.buffer += str;
}
/**
* Update indent using state.indentBy property. Indent is measured in
* double spaces. Updates a single line only.
*
* @param {string} str
* @param {object} state
* @return {string}
*/
function updateIndent(str, state) {
/*jshint -W004*/
var indentBy = state.indentBy;
if (indentBy < 0) {
for (var i = 0; i < -indentBy; i++) {
str = str.replace(leadingIndentRegexp, '$1');
}
} else {
for (var i = 0; i < indentBy; i++) {
str = str.replace(leadingIndentRegexp, '$1$2$2');
}
}
return str;
}
/**
* Calculates indent from the beginning of the line until "start" or the first
* character before start.
* @example
* " foo.bar()"
* ^
* start
* indent will be " "
*
* @param {number} start
* @param {object} state
* @return {string}
*/
function indentBefore(start, state) {
var end = start;
start = start - 1;
while (start > 0 && state.g.source[start] != '\n') {
if (!state.g.source[start].match(/[ \t]/)) {
end = start;
}
start--;
}
return state.g.source.substring(start + 1, end);
}
function getDocblock(state) {
if (!state.g.docblock) {
var docblock = require('./docblock');
state.g.docblock =
docblock.parseAsObject(docblock.extract(state.g.source));
}
return state.g.docblock;
}
function identWithinLexicalScope(identName, state, stopBeforeNode) {
var currScope = state.localScope;
while (currScope) {
if (currScope.identifiers[identName] !== undefined) {
return true;
}
if (stopBeforeNode && currScope.parentNode === stopBeforeNode) {
break;
}
currScope = currScope.parentScope;
}
return false;
}
function identInLocalScope(identName, state) {
return state.localScope.identifiers[identName] !== undefined;
}
/**
* @param {object} boundaryNode
* @param {?array} path
* @return {?object} node
*/
function initScopeMetadata(boundaryNode, path, node) {
return {
boundaryNode: boundaryNode,
bindingPath: path,
bindingNode: node
};
}
function declareIdentInLocalScope(identName, metaData, state) {
state.localScope.identifiers[identName] = {
boundaryNode: metaData.boundaryNode,
path: metaData.bindingPath,
node: metaData.bindingNode,
state: Object.create(state)
};
}
function getLexicalBindingMetadata(identName, state) {
var currScope = state.localScope;
while (currScope) {
if (currScope.identifiers[identName] !== undefined) {
return currScope.identifiers[identName];
}
currScope = currScope.parentScope;
}
}
function getLocalBindingMetadata(identName, state) {
return state.localScope.identifiers[identName];
}
/**
* Apply the given analyzer function to the current node. If the analyzer
* doesn't return false, traverse each child of the current node using the given
* traverser function.
*
* @param {function} analyzer
* @param {function} traverser
* @param {object} node
* @param {array} path
* @param {object} state
*/
function analyzeAndTraverse(analyzer, traverser, node, path, state) {
if (node.type) {
if (analyzer(node, path, state) === false) {
return;
}
path.unshift(node);
}
getOrderedChildren(node).forEach(function(child) {
traverser(child, path, state);
});
node.type && path.shift();
}
/**
* It is crucial that we traverse in order, or else catchup() on a later
* node that is processed out of order can move the buffer past a node
* that we haven't handled yet, preventing us from modifying that node.
*
* This can happen when a node has multiple properties containing children.
* For example, XJSElement nodes have `openingElement`, `closingElement` and
* `children`. If we traverse `openingElement`, then `closingElement`, then
* when we get to `children`, the buffer has already caught up to the end of
* the closing element, after the children.
*
* This is basically a Schwartzian transform. Collects an array of children,
* each one represented as [child, startIndex]; sorts the array by start
* index; then traverses the children in that order.
*/
function getOrderedChildren(node) {
var queue = [];
for (var key in node) {
if (node.hasOwnProperty(key)) {
enqueueNodeWithStartIndex(queue, node[key]);
}
}
queue.sort(function(a, b) { return a[1] - b[1]; });
return queue.map(function(pair) { return pair[0]; });
}
/**
* Helper function for analyzeAndTraverse which queues up all of the children
* of the given node.
*
* Children can also be found in arrays, so we basically want to merge all of
* those arrays together so we can sort them and then traverse the children
* in order.
*
* One example is the Program node. It contains `body` and `comments`, both
* arrays. Lexographically, comments are interspersed throughout the body
* nodes, but esprima's AST groups them together.
*/
function enqueueNodeWithStartIndex(queue, node) {
if (typeof node !== 'object' || node === null) {
return;
}
if (node.range) {
queue.push([node, node.range[0]]);
} else if (Array.isArray(node)) {
for (var ii = 0; ii < node.length; ii++) {
enqueueNodeWithStartIndex(queue, node[ii]);
}
}
}
/**
* Checks whether a node or any of its sub-nodes contains
* a syntactic construct of the passed type.
* @param {object} node - AST node to test.
* @param {string} type - node type to lookup.
*/
function containsChildOfType(node, type) {
return containsChildMatching(node, function(node) {
return node.type === type;
});
}
function containsChildMatching(node, matcher) {
var foundMatchingChild = false;
function nodeTypeAnalyzer(node) {
if (matcher(node) === true) {
foundMatchingChild = true;
return false;
}
}
function nodeTypeTraverser(child, path, state) {
if (!foundMatchingChild) {
foundMatchingChild = containsChildMatching(child, matcher);
}
}
analyzeAndTraverse(
nodeTypeAnalyzer,
nodeTypeTraverser,
node,
[]
);
return foundMatchingChild;
}
var scopeTypes = {};
scopeTypes[Syntax.ArrowFunctionExpression] = true;
scopeTypes[Syntax.FunctionExpression] = true;
scopeTypes[Syntax.FunctionDeclaration] = true;
scopeTypes[Syntax.Program] = true;
function getBoundaryNode(path) {
for (var ii = 0; ii < path.length; ++ii) {
if (scopeTypes[path[ii].type]) {
return path[ii];
}
}
throw new Error(
'Expected to find a node with one of the following types in path:\n' +
JSON.stringify(Object.keys(scopeTypes))
);
}
function getTempVar(tempVarIndex) {
return '$__' + tempVarIndex;
}
function injectTempVar(state) {
var tempVar = '$__' + (state.localScope.tempVarIndex++);
state.localScope.tempVars.push(tempVar);
return tempVar;
}
function injectTempVarDeclarations(state, index) {
if (state.localScope.tempVars.length) {
state.g.buffer =
state.g.buffer.slice(0, index) +
'var ' + state.localScope.tempVars.join(', ') + ';' +
state.g.buffer.slice(index);
state.localScope.tempVars = [];
}
}
exports.analyzeAndTraverse = analyzeAndTraverse;
exports.append = append;
exports.catchup = catchup;
exports.catchupNewlines = catchupNewlines;
exports.catchupWhiteOut = catchupWhiteOut;
exports.catchupWhiteSpace = catchupWhiteSpace;
exports.containsChildMatching = containsChildMatching;
exports.containsChildOfType = containsChildOfType;
exports.createState = createState;
exports.declareIdentInLocalScope = declareIdentInLocalScope;
exports.getBoundaryNode = getBoundaryNode;
exports.getDocblock = getDocblock;
exports.getLexicalBindingMetadata = getLexicalBindingMetadata;
exports.getLocalBindingMetadata = getLocalBindingMetadata;
exports.getNextSyntacticCharOffset = getNextSyntacticCharOffset;
exports.getNodeSourceText = getNodeSourceText;
exports.getOrderedChildren = getOrderedChildren;
exports.getTempVar = getTempVar;
exports.identInLocalScope = identInLocalScope;
exports.identWithinLexicalScope = identWithinLexicalScope;
exports.indentBefore = indentBefore;
exports.initScopeMetadata = initScopeMetadata;
exports.injectTempVar = injectTempVar;
exports.injectTempVarDeclarations = injectTempVarDeclarations;
exports.move = move;
exports.scopeTypes = scopeTypes;
exports.updateIndent = updateIndent;
exports.updateState = updateState;

View File

@@ -0,0 +1,239 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/**
* @emails dmitrys@fb.com
*/
/*jshint evil:true*/
/*jshint -W117*/
require('mock-modules').autoMockOff();
describe('es6ArrowFunctionsTransform', function() {
var transformFn;
var visitors;
beforeEach(function() {
require('mock-modules').dumpCache();
visitors = require('../es6-arrow-function-visitors').visitorList;
transformFn = require('../../src/jstransform').transform;
});
function transform(code) {
return transformFn(visitors, code).code;
}
function expectTransform(code, result) {
expect(transform(code)).toEqual(result);
}
it('should capture correct this value at different levels', function() {
var code = transform([
'var foo = {',
' createFooGetter: function() {',
' return (x) => [x, this];', // captures foo
' },',
' getParentThis: () => this', // captures parent this
'};'
].join('\n'));
eval(code);
expect(typeof foo.createFooGetter).toBe('function');
expect(typeof foo.createFooGetter()).toBe('function');
expect(typeof foo.getParentThis).toBe('function');
expect(foo.getParentThis()).toEqual(this);
expect(foo.createFooGetter()(10)).toEqual([10, foo]);
});
it('should map an array using arrow capturing this value', function() {
this.factor = 10;
var code = transform(
'[1, 2, 3].map(x => x * x * this.factor);'
);
expect(eval(code)).toEqual([10, 40, 90]);
});
it('binds if any `super` keyword is referenced', function() {
var code = transform(
'var fn=x=>super;'
);
// We have to do a source text comparison here because `super` is a reserved
// keyword (so we can't eval it).
expect(code).toEqual('var fn=function(x){return super;}.bind(this);');
});
it('should filter an array using arrow with two params', function() {
this.factor = 0;
var code = transform([
'[1, 2, 3].filter((v, idx) => {',
' if (idx > 1 && this.factor > 0) {',
' return true;',
' }',
' this.factor++;',
' return false;',
'});'
].join('\n'));
expect(eval(code)).toEqual([3]);
});
it('should fetch this value data from nested arrow', function() {
var code = transform([
'({',
' bird: 22,',
' run: function() {',
' return () => () => this.bird;',
' }',
'}).run()()();'
].join('\n'));
expect(eval(code)).toEqual(22);
});
// Syntax tests.
it('should correctly transform arrows', function() {
// 0 params, expression.
expectTransform(
'() => this.value;',
'(function() {return this.value;}.bind(this));'
);
// 0 params, expression wrapped in parens
expectTransform(
'() => (this.value);',
'(function() {return this.value;}.bind(this));'
);
// 1 param, no-parens, expression, no this.
expectTransform(
'x => x * x;',
'(function(x) {return x * x;});'
);
// 1 param, parens, expression, as argument, no this.
expectTransform(
'map((x) => x * x);',
'map(function(x) {return x * x;});'
);
// 2 params, block, as argument, nested.
expectTransform(
'makeRequest((response, error) => {'.concat(
' return this.update(data => this.onData(data), response);',
'});'),
'makeRequest(function(response, error) {'.concat(
' return this.update(function(data) {return this.onData(data);}.bind(this), response);',
'}.bind(this));')
);
// Assignment to a var, simple, 1 param.
expectTransform(
'var action = (value) => this.performAction(value);',
'var action = function(value) {return this.performAction(value);}.bind(this);'
);
// Preserve lines transforming ugly code.
expectTransform([
'(',
'',
'',
' x,',
' y',
'',
')',
'',
' =>',
'',
' {',
' return x + y;',
'};'
].join('\n'), [
'(function(',
'',
'',
' x,',
' y)',
'',
'',
'',
' ',
'',
' {',
' return x + y;',
'});'
].join('\n'));
// Preserve line numbers with single parens-free param ugly code.
expectTransform([
'x',
'',
' =>',
' x;'
].join('\n'), [
'(function(x)',
'',
' ',
' {return x;});'
].join('\n'));
// Preserve line numbers with single parens param ugly code.
expectTransform([
'(',
'',
' x',
'',
')',
'',
' =>',
' x;'
].join('\n'), [
'(function(',
'',
' x)',
'',
'',
'',
' ',
' {return x;});'
].join('\n'));
// Preserve line numbers with parens around expression.
expectTransform([
'(x) => (',
' x',
');'
].join('\n'), [
'(function(x) ',
' {return x;}',
');'
].join('\n'));
// Preserve typechecker annotation.
expectTransform(
'(/*string*/foo, /*bool*/bar) => foo;',
'(function(/*string*/foo, /*bool*/bar) {return foo;});'
);
});
});

View File

@@ -0,0 +1,96 @@
/**
* @emails sema@fb.com
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('es6-call-spread-visitors', function() {
var transformFn;
var visitors;
beforeEach(function() {
require('mock-modules').dumpCache();
transformFn = require('../../src/jstransform').transform;
visitors = require('../es6-call-spread-visitors').visitorList;
});
function transform(code, options) {
return transformFn(visitors, code, options).code;
}
it('should spread given data with context', function() {
expect(transform('Math.max(1,\t[2], 3, ...[4, 5, 6])'))
.toEqual('var $__0;($__0 = Math).max.apply($__0, [1,\t[2], 3].concat([4, 5, 6]))');
});
it('should avoid unnecessary concat call', function() {
expect(transform('window.Math.max(...list)'))
.toEqual('var $__0;($__0 = window.Math).max.apply($__0, list)');
});
it('should default to null context', function() {
expect(transform('max(1, 2, ...list)'))
.toEqual('max.apply(null, [1, 2].concat(list))');
});
it('should handle computed method names', function() {
expect(transform('Math["m" + (0 ? "in" : "ax")](1, 2, ...list)'))
.toEqual('var $__0;($__0 = Math)["m" + (0 ? "in" : "ax")].apply($__0, [1, 2].concat(list))');
});
it('should handle immediately invoked function expressions', function() {
expect(transform('(function(a, b, c) { return a+b+c; })(1, 2, ...more)'))
.toEqual('(function(a, b, c) { return a+b+c; }).apply(null, [1, 2].concat(more))');
});
it('should spread while creating new instances', function() {
expect(transform('new Set(1, 2, ...list)'))
.toEqual('new (Function.prototype.bind.apply(Set, [null, 1, 2].concat(list)))');
});
it('should create temporary variables when necessary in program scope', function() {
expect(transform('foo().bar(arg1, arg2, ...more)'))
.toEqual('var $__0;($__0 = foo()).bar.apply($__0, [arg1, arg2].concat(more))');
});
it('should create temporary variables when necessary in function scope', function() {
expect(transform('function fn(){ return foo().bar(arg1, arg2, ...more); }'))
.toEqual('function fn(){var $__0; return ($__0 = foo()).bar.apply($__0, [arg1, arg2].concat(more)); }');
});
it('should not evaluate context more than once', function() {
var code = transform([
'var obj = {',
' calls: 0,',
' get context() {',
' this.calls++;',
' return {',
' add: function(a, b) { return a + b; }',
' };',
' }',
'};',
'var nums = [1, 2];',
'obj.context.add(...nums);',
'obj.calls === 1;',
].join('\n'));
expect(eval(code)).toEqual(true);
});
it('should transform nested spread expressions', function() {
var code = transform([
'function getBase() {',
' return {',
' getParams: function(a, b) {',
' return [a, b];',
' }',
' };',
'}',
'[].concat(...getBase().getParams(...[1, 2, 3])).join(" ");',
].join('\n'));
expect(eval(code)).toEqual("1 2");
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,279 @@
/**
* @emails dmitrys@fb.com
*/
/*jshint evil:true*/
/*jshint -W117*/
require('mock-modules').autoMockOff();
describe('es6-destructuring-visitors', function() {
var transformFn;
var destructuringVisitors;
var conciseMethodVisitors;
var shortObjectsVisitors;
var reservedWordsVisitors;
var restParamVisitors;
var classVisitorsVisitors;
var arrowFunctionVisitors;
var visitors;
beforeEach(function() {
require('mock-modules').dumpCache();
transformFn = require('../../src/jstransform').transform;
destructuringVisitors = require('../es6-destructuring-visitors').visitorList;
conciseMethodVisitors = require('../es6-object-concise-method-visitors').visitorList;
shortObjectsVisitors = require('../es6-object-short-notation-visitors').visitorList;
reservedWordsVisitors = require('../reserved-words-visitors').visitorList;
restParamVisitors = require('../es6-rest-param-visitors').visitorList;
classVisitorsVisitors = require('../es6-class-visitors').visitorList;
arrowFunctionVisitors = require('../es6-arrow-function-visitors').visitorList;
visitors = destructuringVisitors.concat(
conciseMethodVisitors,
shortObjectsVisitors,
restParamVisitors,
classVisitorsVisitors,
arrowFunctionVisitors,
reservedWordsVisitors
);
});
function transform(code) {
return transformFn(visitors, code).code;
}
function expectTransform(code, result) {
expect(transform(code)).toEqual(result);
}
it('should handle simple object pattern', function() {
var code = transform([
'var {x, y} = {x: 10, y: 20};',
'(x + y);'
].join('\n'));
expect(eval(code)).toEqual(30);
});
it('should handle literal property names', function() {
var code = transform([
'var {x, "y y": yy, 0: z} = {x: 10, "y y": 20, 0: 30};',
'([x, yy, z]);'
].join('\n'));
expect(eval(code)).toEqual([10, 20, 30]);
});
it('should handle array pattern assignment expression', function() {
var code = transform([
'var x = 10, y = 20;',
'[x, y] = [y, x];',
'([x, y]);'
].join('\n'));
expect(eval(code)).toEqual([20, 10]);
});
it('should should not redeclare vars with assignment expression', function() {
var code = transform([
'var x = 10, y = 20;',
'(function() {',
' [x, y] = [y, x];',
'})();',
'([x, y]);'
].join('\n'));
expect(eval(code)).toEqual([20, 10]);
});
it('should handle object pattern assignment expression', function() {
var code = transform([
'var x = 10, y = 20;',
'({x, y} = {y, x});',
'({x, y});'
].join('\n'));
expect(eval(code)).toEqual({x: 10, y: 20});
});
it('should destructure result of a function', function() {
var code = transform([
'var [x, y] = (function({x, y}) { return [x, y]; })({x: 1, y: 2});',
'([x, y]);'
].join('\n'));
expect(eval(code)).toEqual([1, 2]);
});
it('should handle skipped array elements', function() {
var code = transform([
'var [x, , y] = [1, 2, 3];',
'([x, y]);'
].join('\n'));
expect(eval(code)).toEqual([1, 3]);
});
it('should handle rest elements of an array', function() {
var code = transform([
'var [x, ...xs] = [1, 2, 3];'
].join('\n'));
eval(code);
expect(x).toEqual(1);
expect(xs).toEqual([2, 3]);
});
it('should swap two variables w/o third using pattern', function() {
var code = transform([
'var x = 10, y = 20;',
'var [x, y] = [y, x];',
'([x, y]);'
].join('\n'));
expect(eval(code)).toEqual([20, 10]);
});
it('should transform complex pattern argument', function() {
var code = transform([
'function init(user, {ip, coords: [x, y], port}) {',
' return [user, ip, x, y, port].join(", ");',
'}'
].join('\n'));
eval(code);
expect(init(
'John Doe', {
ip: '127.0.0.1',
coords: [1, 2],
port: 8080
}
)).toBe('John Doe, 127.0.0.1, 1, 2, 8080');
});
it('should work with rest params', function() {
var code = transform([
'function foo({bar, baz}, ...rest) {',
' return {bar, baz, qux: rest[0]};',
'}'
].join('\n'));
eval(code);
expect(foo({bar: 10, baz: 20}, 30))
.toEqual({bar: 10, baz: 20, qux: 30});
});
it('should work with class methods', function() {
var code = transform([
'class Point {',
' constructor({x, y}) {',
' this._x = x;',
' this._y = y;',
' }',
'',
' getData([deltaX, deltaY]) {',
' return this._x + deltaX + this._y + deltaY',
' }',
'}'
].join('\n'));
eval(code);
var x = 10, y = 20;
var foo = new Point({x: x, y: y});
var data = foo.getData([30, 40]);
expect(data).toBe(100);
});
it('should work with object concise methods', function() {
var code = transform([
'var foo = {',
' bar({x, y}) {',
' return {x, y};',
' }',
'}'
].join('\n'));
eval(code);
expect(foo.bar({x: 10, y: 20}))
.toEqual({x: 10, y: 20});
});
it('should work with arrows', function() {
var code = transform([
'var foo = ({x, y}, z) => x + y + z;'
].join('\n'));
eval(code);
expect(foo({x: 10, y: 20}, 30))
.toEqual(60);
});
// Auto-generated temp vars test.
it('should allocate correct temp index', function() {
var code = transform([
'function foo(x, {y}, {z}) {',
' var {q} = {q: 30};',
' return [$__0, $__1, $__2];',
'}'
].join('\n'));
eval(code);
expect(foo(1, {y: 10}, {z: 20}))
.toEqual([{y: 10}, {z: 20}, {q: 30}]);
});
it('should allocate correct temp nested index', function() {
var code = transform([
'var foo = function(x, {y}, {z}) {',
' var {q, m: {v}} = {q: 30, m: {v: 40}};',
' var {a} = (function({a}) { return $__0; })({a: 50});',
' return [$__0, $__1, $__2, $__3, a];',
'}'
].join('\n'));
eval(code);
expect(foo(1, {y: 10}, {z: 20}))
.toEqual([{y: 10}, {z: 20}, {q: 30, m: {v: 40}}, {v: 40}, 50]);
});
// Syntax tests.
it('should correctly transform structured patterns', function() {
// Variable declaration.
expectTransform(
'var a, {x, data: [y, , z]} = {x: 10, data: [1, 2, 3]};',
'var a, $__0= {x: 10, data: [1, 2, 3]},x=$__0.x,$__1=$__0.data,y=$__1[0],z=$__1[2];'
);
// Function parameters.
expectTransform(
'function f(a, {x, data: [y, z]}, b) {}',
'function f(a, $__0 , b) {var x=$__0.x,$__1=$__0.data,y=$__1[0],z=$__1[1];}'
);
});
it('should handle reserved words', function() {
expectTransform(
'var {delete: x} = {delete: 1};',
'var $__0= {"delete": 1},x=$__0["delete"];'
);
});
});

View File

@@ -0,0 +1,62 @@
/**
* @emails sema@fb.com javascript@lists.facebook.com
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('es6-es7-object-integration-test', function() {
var transformFn;
var visitors;
// These are placeholder variables in scope that we can use to assert that a
// specific variable reference was passed, rather than an object clone of it.
var x = 123456;
var z = 345678;
beforeEach(function() {
require('mock-modules').dumpCache();
transformFn = require('../../src/jstransform').transform;
var conciseMethodVisitors = require('../es6-object-concise-method-visitors').visitorList;
var shortObjectsVisitors = require('../es6-object-short-notation-visitors').visitorList;
var spreadPropertyVisitors = require('../es7-spread-property-visitors').visitorList;
visitors = spreadPropertyVisitors.concat(
shortObjectsVisitors,
conciseMethodVisitors
);
});
function transform(code) {
return transformFn(visitors, code).code;
}
it('handles spread with concise methods and short notation', function() {
var code = 'var xyz = { ...x, y() { return 42; }, z }';
var objectAssignMock = jest.genMockFunction();
Object.assign = objectAssignMock;
eval(transform(code));
var assignCalls = objectAssignMock.mock.calls;
expect(assignCalls.length).toBe(1);
expect(assignCalls[0].length).toBe(3);
expect(assignCalls[0][0]).toEqual({});
expect(assignCalls[0][1]).toEqual(x);
var trailingObject = assignCalls[0][2];
expect(trailingObject.y()).toEqual(42);
expect(trailingObject.z).toEqual(z);
});
it('does not call assign when there are no spread properties', function() {
var code = 'var xyz = { x, y() { return 42 }, z }';
var objectAssignMock = jest.genMockFunction();
Object.assign = objectAssignMock;
eval(transform(code));
expect(objectAssignMock).not.toBeCalled();
});
});

View File

@@ -0,0 +1,133 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/**
* @emails dmitrys@fb.com
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('es6-object-concise-method-visitors', function() {
var transformFn;
var conciseMethodVisitors;
var restParamVisitors;
var visitors;
beforeEach(function() {
require('mock-modules').dumpCache();
conciseMethodVisitors = require('../es6-object-concise-method-visitors').visitorList;
restParamVisitors = require('../es6-rest-param-visitors').visitorList;
transformFn = require('../../src/jstransform').transform;
visitors = conciseMethodVisitors.concat(restParamVisitors);
});
function transform(code) {
return transformFn(visitors, code).code;
}
function expectTransform(code, result) {
expect(transform(code)).toEqual(result);
}
// Functional tests.
it('should transform concise method and return 42', function() {
/*global foo*/
var code = transform([
'var foo = {',
' bar(x) {',
' return x;',
' }',
'};'
].join('\n'));
eval(code);
expect(foo.bar(42)).toEqual(42);
});
it('should transform concise method with literal property', function() {
var code = transform([
'var foo = {',
' "bar 1"(x) {',
' return x;',
' }',
'};'
].join('\n'));
eval(code);
expect(foo['bar 1'](42)).toEqual(42);
});
it('should work with rest params', function() {
var code = transform([
'({',
' init(x, ...rest) {',
' return rest.concat(x);',
' }',
'}).init(1, 2, 3);'
].join('\n'));
expect(eval(code)).toEqual([2, 3, 1]);
});
// Source code tests.
it('should transform concise methods', function() {
// Should transform simple concise method.
expectTransform(
'var foo = {bar() {}};',
'var foo = {bar:function() {}};'
);
// Should transform inner objects.
expectTransform(
'({bar(x) { return {baz(y) {}}; }});',
'({bar:function(x) { return {baz:function(y) {}}; }});'
);
});
it('should preserve generators', function() {
// Identifier properties
expectTransform(
'var foo = {*bar(x) {yield x;}};',
'var foo = {bar:function*(x) {yield x;}};'
);
// Literal properties
expectTransform(
'var foo = {*"abc"(x) {yield x;}, *42(x) {yield x;}};',
'var foo = {"abc":function*(x) {yield x;}, 42:function*(x) {yield x;}};'
);
// Dynamic properties
expectTransform(
'var foo = {*[a+b](x) {yield x;}}',
'var foo = {[a+b]:function*(x) {yield x;}}'
);
});
it('should handle reserved words', function() {
expectTransform(
'({delete(x) {}})',
'({"delete":function(x) {}})'
);
});
});

View File

@@ -0,0 +1,119 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/**
* @emails dmitrys@fb.com
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('es6-object-short-notation-visitors', function() {
var transformFn;
var destructuringVisitors;
var shortObjectsVisitors;
var visitors;
beforeEach(function() {
require('mock-modules').dumpCache();
transformFn = require('../../src/jstransform').transform;
shortObjectsVisitors = require('../es6-object-short-notation-visitors').visitorList;
destructuringVisitors = require('../es6-destructuring-visitors').visitorList;
visitors = shortObjectsVisitors.concat(destructuringVisitors);
});
function transform(code) {
return transformFn(visitors, code).code;
}
function expectTransform(code, result) {
expect(transform(code)).toEqual(result);
}
// Functional tests.
it('should transform short notation and return 5', function() {
var code = transform([
'(function(x, y) {',
' var data = {x, y};',
' return data.x + data.y;',
'})(2, 3);'
].join('\n'));
expect(eval(code)).toEqual(5);
});
it('should transform work with destructuring and return 10', function() {
var code = transform([
'var x = 5, y = 5;',
'(function({x, y}) {',
' var data = {x, y};',
' return data.x + data.y;',
'})({x, y});'
].join('\n'));
expect(eval(code)).toEqual(10);
});
// Source code tests.
it('should transform simple short notation', function() {
// Should transform simple short notation.
expectTransform(
'function foo(x, y) { return {x, y}; }',
'function foo(x, y) { return {x:x, y:y}; }'
);
// Should preserve lines transforming ugly code.
expectTransform([
'function',
'',
'foo (',
' x,',
' y',
'',
')',
'',
' {',
' return {',
' x,',
' y};',
'}'
].join('\n'), [
'function',
'',
'foo (',
' x,',
' y',
'',
')',
'',
' {',
' return {',
' x:x,',
' y:y};',
'}'
].join('\n'));
});
});

View File

@@ -0,0 +1,328 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/**
* @emails dmitrys@fb.com
*/
/*jshint evil:true*/
/*jshint -W117*/
require('mock-modules').autoMockOff();
describe('es6-rest-param-visitors', () => {
var transformFn;
var visitorSet;
var arrowFuncVisitors;
var classVisitors;
var restParamVisitors;
beforeEach(() => {
require('mock-modules').dumpCache();
arrowFuncVisitors = require('../es6-arrow-function-visitors').visitorList;
classVisitors = require('../es6-class-visitors').visitorList;
restParamVisitors = require('../es6-rest-param-visitors').visitorList;
transformFn = require('../../src/jstransform').transform;
visitorSet =
arrowFuncVisitors
.concat(classVisitors)
.concat(restParamVisitors);
});
function transform(code) {
return transformFn(visitorSet, code).code;
}
function expectTransform(code, result) {
expect(transform(code)).toEqual(result);
}
describe('function expressions', () => {
it('should capture 2 rest params, having 2 args', () => {
var code = transform([
'(function(x, y, ...args) {',
' return [x, y, args.length, args[0], args[1]];',
'})(1, 2, 3, 4);'
].join('\n'));
expect(eval(code)).toEqual([1, 2, 2, 3, 4]);
});
it('should transform rest parameters in nested functions', () => {
var code = transform([
'(function(x, ...args) {',
' return function(...params) {',
' return args.concat(params);',
' };',
'})(1, 2, 3)(4, 5);'
].join('\n'));
expect(eval(code)).toEqual([2, 3, 4, 5]);
});
it('should supply an array object', () => {
var code = transform([
'(function(...args) {',
' return Array.isArray(args);',
'})()'
].join('\n'));
expect(eval(code)).toBe(true);
});
});
describe('function declarations', () => {
it('should capture 2 rest params, having 2 args', () => {
var code = transform([
'function test(x, y, ...args) {',
' return [x, y, args.length, args[0], args[1]];',
'}'
].join('\n'));
eval(code);
expect(test(1, 2, 3, 4)).toEqual([1, 2, 2, 3, 4]);
});
it('should transform rest parameters in nested functions', () => {
var code = transform([
'function testOuter(x, ...args) {',
' function testInner(...params) {',
' return args.concat(params);',
' }',
' return testInner;',
'}'
].join('\n'));
eval(code);
expect(testOuter(1, 2, 3)(4, 5)).toEqual([2, 3, 4, 5]);
});
it('should supply an array object', () => {
var code = transform([
'function test(...args) {',
' return Array.isArray(args);',
'}'
].join('\n'));
eval(code);
expect(test()).toBe(true);
});
});
describe('arrow functions', () => {
it('should transform non-block bodied arrow functions', () => {
var code = transform([
'var test = (...args) => args;'
].join('\n'));
eval(code);
expect(test('foo', 'bar')).toEqual(['foo', 'bar']);
});
it('should capture 2 rest params, having 2 args', () => {
var code = transform([
'var test = (x, y, ...args) => {',
' return [x, y, args.length, args[0], args[1]];',
'}'
].join('\n'));
eval(code);
expect(test(1, 2, 3, 4)).toEqual([1, 2, 2, 3, 4]);
});
it('should transform rest parameters in nested arrow functions', () => {
var code = transform([
'var testOuter = (x, ...args) => {',
' var testInner = (...params) => {',
' return args.concat(params);',
' };',
' return testInner;',
'};'
].join('\n'));
eval(code);
expect(testOuter(1, 2, 3)(4, 5)).toEqual([2, 3, 4, 5]);
});
it('should supply an array object', () => {
var code = transform([
'var test = (...args) => {',
' return Array.isArray(args);',
'};'
].join('\n'));
eval(code);
expect(test()).toBe(true);
});
});
describe('class methods', () => {
it('should capture 2 rest params, having 2 args', () => {
var code = transform([
'class Foo {',
' constructor(x, y, ...args) {',
' this.ctor = [x, y, args.length, args[0], args[1]];',
' }',
' testMethod(x, y, ...args) {',
' return [x, y, args.length, args[0], args[1]];',
' }',
' static testMethod(x, y, ...args) {',
' return [x, y, args.length, args[0], args[1]];',
' }',
'}'
].join('\n'));
eval(code);
var fooInst = new Foo(1, 2, 3, 4);
expect(fooInst.ctor).toEqual([1, 2, 2, 3, 4]);
expect(fooInst.testMethod(1, 2, 3, 4)).toEqual([1, 2, 2, 3, 4]);
expect(Foo.testMethod(1, 2, 3, 4)).toEqual([1, 2, 2, 3, 4]);
});
it('should transform rest parameters in nested functions', () => {
var code = transform([
'class Foo {',
' constructor(x, ...args) {',
' function inner(...params) {',
' return args.concat(params);',
' }',
' this.ctor = inner;',
' }',
' testMethod(x, ...args) {',
' function inner(...params) {',
' return args.concat(params);',
' }',
' return inner;',
' }',
' static testMethod(x, ...args) {',
' function inner(...params) {',
' return args.concat(params);',
' }',
' return inner;',
' }',
'}'
].join('\n'));
eval(code);
var fooInst = new Foo(1, 2, 3);
expect(fooInst.ctor(4, 5)).toEqual([2, 3, 4, 5]);
expect(fooInst.testMethod(1, 2, 3)(4, 5)).toEqual([2, 3, 4, 5]);
expect(Foo.testMethod(1, 2, 3)(4, 5)).toEqual([2, 3, 4, 5]);
});
it('should supply an array object', () => {
var code = transform([
'class Foo {',
' constructor(...args) {',
' this.ctor = Array.isArray(args);',
' }',
' testMethod(...args) {',
' return Array.isArray(args);',
' }',
' static testMethod(...args) {',
' return Array.isArray(args);',
' }',
'}'
].join('\n'));
eval(code);
var fooInst = new Foo();
expect(fooInst.ctor).toBe(true);
expect(fooInst.testMethod()).toBe(true);
expect(Foo.testMethod()).toBe(true);
});
});
describe('whitespace preservation', () => {
it('1-line function decl with 2 args', () => {
expectTransform(
'function foo(x, y, ...args) { return x + y + args[0]; }',
'function foo(x, y ) {for (var args=[],$__0=2,$__1=arguments.length;' +
'$__0<$__1;$__0++) args.push(arguments[$__0]); return x + y + ' +
'args[0]; }'
);
});
it('1-line function expression with 1 arg', () => {
expectTransform(
'(function(x, ...args) { return args;});',
'(function(x ) {for (var args=[],$__0=1,$__1=arguments.length;' +
'$__0<$__1;$__0++) args.push(arguments[$__0]); return args;});'
);
});
it('1-line function expression with no args', () => {
expectTransform(
'map(function(...args) { return args.map(log); });',
'map(function() {for (var args=[],$__0=0,$__1=arguments.length;' +
'$__0<$__1;$__0++) args.push(arguments[$__0]); ' +
'return args.map(log); });'
);
});
it('preserves lines for ugly code', () => {
expectTransform([
'function',
'',
'foo (',
' x,',
' ...args',
'',
')',
'',
' {',
' return args;',
'}'
].join('\n'), [
'function',
'',
'foo (',
' x',
' ',
'',
')',
'',
' {for (var args=[],$__0=1,$__1=arguments.length;$__0<$__1;' +
'$__0++) args.push(arguments[$__0]);',
' return args;',
'}'
].join('\n'));
});
it('preserves inline comments', () => {
expectTransform(
'function foo(/*string*/foo, /*bool*/bar, ...args) { return args; }',
'function foo(/*string*/foo, /*bool*/bar ) {' +
'for (var args=[],$__0=2,$__1=arguments.length;$__0<$__1;$__0++) ' +
'args.push(arguments[$__0]); ' +
'return args; ' +
'}'
);
});
});
});

View File

@@ -0,0 +1,208 @@
/**
* @emails mroch@fb.com
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('ES6 Template Visitor', function() {
var transformFn;
var visitors;
beforeEach(function() {
require('mock-modules').dumpCache();
visitors = require('../es6-template-visitors').visitorList;
transformFn = require('../../src/jstransform').transform;
});
function transform(code) {
return transformFn(visitors, code).code;
}
function expectTransform(code, result) {
expect(transform(code)).toEqual(result);
}
function expectEval(code, result, setupFn) {
var actual;
if (setupFn) {
eval(setupFn);
}
eval('actual = ' + transform(code));
expect(actual).toEqual(result);
}
function expectEvalTag(code, tagFn, scope) {
/*jshint unused:false*/
if (scope) {
Object.keys(scope).forEach((key) => this[key] = scope[key]);
}
var tagCalls = 0;
var tag = function(...args) {
tagCalls++;
return tagFn.apply(this, args);
};
var result = transform(code);
expect(result.split('\n').length).toBe(code.split('\n').length);
eval(result);
expect(tagCalls).toBe(1);
}
function expectSiteObj(siteObj, cooked, raw) {
expect(Array.isArray(siteObj)).toBe(true);
expect(Object.isFrozen(siteObj)).toBe(true);
expect(Array.isArray(siteObj.raw)).toBe(true);
expect(Object.isFrozen(siteObj.raw)).toBe(true);
expect(siteObj.length).toBe(cooked.length);
expect(siteObj.raw.length).toBe(raw.length);
for (var ii = 0; ii < cooked.length; ii++) {
expect(siteObj[ii]).toEqual(cooked[ii]);
}
expect(siteObj.raw).toEqual(raw);
}
it('should transform simple literals', function() {
expectTransform('`foo bar`', '("foo bar")');
expectEval('`foo bar`', 'foo bar');
expectEval('`$`', '$');
expectEval('`$foo`', '$foo');
});
it('should properly escape templates containing quotes', function() {
expectTransform('`foo "bar"`', '("foo \\"bar\\"")');
expectEval('`foo "bar"`', 'foo "bar"');
expectTransform("`foo 'bar'`", '("foo \'bar\'")');
expectEval("`foo 'bar'`", "foo 'bar'");
// `foo \\"bar\\"` (foo, literal slash, "bar", literal slash)
expectTransform('`foo \\\\"bar\\\\"`', '("foo \\\\\\"bar\\\\\\"")');
expectEval('`foo \\\\\\"bar\\\\\\"`', 'foo \\"bar\\"');
});
it('should transform simple substitutions', function() {
expectTransform('`foo ${bar}`', '("foo " + bar)');
expectTransform('`${foo} bar`', '(foo + " bar")');
expectTransform('`${foo} ${bar}`', '(foo + " " + bar)');
expectTransform('`${foo}${bar}`', '(foo + bar)');
});
it('should transform expressions', function() {
expectTransform('`foo ${bar()}`', '("foo " + bar())');
expectTransform('`foo ${bar.baz}`', '("foo " + bar.baz)');
expectTransform('`foo ${bar + 5}`', '("foo " + (bar + 5))');
expectTransform('`${foo + 5} bar`', '((foo + 5) + " bar")');
expectTransform('`${foo + 5} ${bar}`', '((foo + 5) + " " + bar)');
expectTransform(
'`${(function(b) {alert(4);})(a)}`',
'((function(b) {alert(4);})(a))');
});
it('should transform tags with simple templates', function() {
/*jshint unused:false*/
var tag = function(elements) {
expectSiteObj(elements, ['foo bar'], ['foo bar']);
};
var result = transform("tag`foo bar`");
expect(result.split('\n').length).toBe(1);
eval(result);
var a = { b: tag };
eval(transform("a.b`foo bar`"));
eval(transform("a['b']`foo bar`"));
var getTag = function() { return tag; };
eval(transform("getTag()`foo bar`"));
eval(transform("(getTag())`foo bar`"));
});
it('should transform tags with substitutions', function() {
expectTransform(
"tag`foo ${bar} baz`",
'tag(function() { var siteObj = ["foo ", " baz"]; ' +
'siteObj.raw = ["foo ", " baz"]; Object.freeze(siteObj.raw); ' +
'Object.freeze(siteObj); return siteObj; }(), bar)'
);
expectEvalTag(
"tag`foo ${bar + 'abc'} baz`",
function(elements, ...args) {
expectSiteObj(elements, ['foo ', ' baz'], ['foo ', ' baz']);
expect(args.length).toBe(1);
expect(args[0]).toBe('barabc');
},
{bar: 'bar'}
);
expectEvalTag(
"tag`foo ${bar + 'abc'}`",
function(elements, ...args) {
expectSiteObj(elements, ['foo ', ''], ['foo ', '']);
expect(args.length).toBe(1);
expect(args[0]).toBe('barabc');
},
{bar: 'bar'}
);
expectEvalTag(
"tag`foo\n\n\nbar`",
(elements) => {
expectSiteObj(elements, ['foo\n\n\nbar'], ['foo\n\n\nbar']);
}
);
expectEvalTag(
"tag`a\nb\n${c}\nd`",
(elements, ...args) => {
expectSiteObj(elements, ['a\nb\n', '\nd'], ['a\nb\n', '\nd']);
expect(args.length).toBe(1);
expect(args[0]).toBe('c');
},
{c: 'c'}
);
});
it('should maintain line numbers', function() {
expectTransform("`foo\n\nbar`", '("foo\\n\\nbar"\n\n)');
expectTransform("`foo\n${bar}\nbaz`", '("foo\\n" + \nbar + "\\nbaz"\n)');
expectTransform("`foo\\nbar`", '("foo\\nbar")');
expectTransform(
"tag`a\nb\n${c}${d}\ne`",
'tag(function() { var siteObj = ["a\\nb\\n", "", "\\ne"]; ' +
'siteObj.raw = ["a\\nb\\n", "", "\\ne"]; Object.freeze(siteObj.raw); ' +
'Object.freeze(siteObj); return siteObj; }(), \n\nc, d\n)'
);
});
it('should handle multiple lines', function() {
expectEval("`foo\n\nbar`", 'foo\n\nbar');
expectEval("`foo\\nbar`", 'foo\nbar');
expectEval("`foo\\\\nbar`", 'foo\\nbar');
expectEval("`foo\n${bar}\nbaz`", 'foo\nabc\nbaz', 'var bar = "abc";');
});
it('should canonicalize line endings', function() {
// TODO: should this be '("foo\\nbar"\r\n)' to maintain the number of lines
// for editors that break on \r\n? I don't think we care in the transformed
// code.
expectTransform("`foo\r\nbar`", '("foo\\nbar"\n)');
// TODO: same as above but with trailing \r
expectTransform("`foo\rbar`", '("foo\\nbar")');
expectEval("`foo\r\nbar`", 'foo\nbar');
expectEval("`foo\rbar`", 'foo\nbar');
expectEval("`foo\r\n${bar}\r\nbaz`", 'foo\nabc\nbaz', 'var bar = "abc";');
expectEvalTag(
"tag`foo\rbar`",
(elements) => {
expectSiteObj(elements, ['foo\nbar'], ['foo\rbar']);
}
);
});
});

View File

@@ -0,0 +1,97 @@
/**
* @emails sema@fb.com
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('es7-rest-property-visitors', function() {
var transformFn;
var visitors;
beforeEach(function() {
require('mock-modules').dumpCache();
transformFn = require('../../src/jstransform').transform;
visitors = require('../es6-destructuring-visitors').visitorList;
});
function transform(code) {
var lines = Array.prototype.join.call(arguments, '\n');
return transformFn(visitors, lines).code;
}
// Semantic tests.
it('picks off remaining properties from an object', function() {
var code = transform(
'({ x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 });',
'([ x, y, z ]);'
);
expect(eval(code)).toEqual([1, 2, { a: 3, b: 4 }]);
});
it('picks off remaining properties from a nested object', function() {
var code = transform(
'var complex = {',
' x: { a: 1, b: 2, c: 3 },',
' y: [4, 5, 6]',
'};',
'var {',
' x: { a: xa, ...xbc },',
' y: [y0, ...y12]',
'} = complex;',
'([ xa, xbc, y0, y12 ]);'
);
expect(eval(code)).toEqual([ 1, { b: 2, c: 3 }, 4, [5, 6] ]);
});
it('only extracts own properties', function() {
var code = transform(
'var obj = Object.create({ x: 1 });',
'obj.y = 2;',
'({ ...y } = obj);',
'(y);'
);
expect(eval(code)).toEqual({ y: 2 });
});
it('only extracts own properties, except when they are explicit', function() {
var code = transform(
'var obj = Object.create({ x: 1, y: 2 });',
'obj.z = 3;',
'({ y, ...z } = obj);',
'([ y, z ]);'
);
expect(eval(code)).toEqual([ 2, { z: 3 } ]);
});
it('avoids passing extra properties when they are picked off', function() {
var code = transform(
'function base({ a, b, x }) { return [ a, b, x ]; }',
'function wrapper({ x, y, ...restConfig }) {',
' return base(restConfig);',
'}',
'wrapper({ x: 1, y: 2, a: 3, b: 4 });'
);
expect(eval(code)).toEqual([ 3, 4, undefined ]);
});
// Syntax tests.
it('throws on leading rest properties', function () {
expect(() => transform('({ ...x, y, z } = obj)')).toThrow();
});
it('throws on multiple rest properties', function () {
expect(() => transform('({ x, ...y, ...z } = obj)')).toThrow();
});
// TODO: Ideally identifier reuse should fail to transform
// it('throws on identifier reuse', function () {
// expect(() => transform('({ x: { ...z }, y: { ...z } } = obj)')).toThrow();
// });
});

View File

@@ -0,0 +1,160 @@
/**
* @emails sema@fb.com
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('es7-spread-property-visitors', function() {
var transformFn;
var originalAssign = Object.assign;
var visitors;
// These are placeholder variables in scope that we can use to assert that a
// specific variable reference was passed, rather than an object clone of it.
var x = 123456;
var y = 789012;
var z = 345678;
beforeEach(function() {
require('mock-modules').dumpCache();
transformFn = require('../../src/jstransform').transform;
visitors = require('../es7-spread-property-visitors').visitorList;
});
function transform(code) {
return transformFn(visitors, code).code;
}
function expectTransform(code, result) {
expect(transform(code)).toEqual(result);
}
function expectObjectAssign(code) {
var objectAssignMock = jest.genMockFunction();
Object.assign = objectAssignMock;
eval(transform(code));
return expect(objectAssignMock);
}
afterEach(function() {
Object.assign = originalAssign;
});
// Polyfill sanity checks
it('has access to a working Object.assign implementation', function() {
expect(typeof Object.assign).toBe('function');
expect(Object.assign({ b: 2 }, null, { a: 1 })).toEqual({ a: 1, b: 2 });
});
// Semantic tests.
it('uses Object.assign with an empty new target object', function() {
expectObjectAssign(
'var xy = { ...x, y: 2 }'
).toBeCalledWith({}, x, { y: 2 });
});
it('coalesces consecutive properties into a single object', function() {
expectObjectAssign(
'var xyz = { ...x, y: 2, z: z }'
).toBeCalledWith({}, x, { y: 2, z: z });
});
it('avoids an unnecessary empty object when spread is not first', function() {
expectObjectAssign(
'var xy = { x: 1, ...y }'
).toBeCalledWith({ x: 1}, y);
});
it('passes the same value multiple times to Object.assign', function() {
expectObjectAssign(
'var xyz = { x: 1, y: 2, ...z, ...z }'
).toBeCalledWith({ x: 1, y: 2}, z, z);
});
it('keeps object literals as separate arguments to assign', function() {
expectObjectAssign(
'var xyz = { x: 1, ...({ y: 2 }), z: 3 }'
).toBeCalledWith({ x: 1}, { y: 2 }, {z: 3 });
});
it('does not call assign when there are no spread properties', function() {
expectObjectAssign(
'var xy = { x: 1, y: 2 }'
).not.toBeCalled();
});
// Syntax tests.
it('should preserve extra whitespace', function() {
expectTransform(
'let xyz = { x: 1, y : \n 2, ... \nz, ... z }',
'let xyz = Object.assign({ x: 1, y : \n 2}, \nz, z )'
);
});
it('should preserve parenthesis', function() {
expectTransform(
'let xyz = { x: 1, ...({ y: 2 }), z: 3 }',
'let xyz = Object.assign({ x: 1}, ({ y: 2 }), {z: 3 })'
);
});
it('should remove trailing commas after properties', function() {
expectTransform(
'let xyz = { ...x, y: 1, }',
'let xyz = Object.assign({}, x, {y: 1 })'
);
});
it('should remove trailing commas after spread', function() {
expectTransform(
'let xyz = { x: 1, ...y, }',
'let xyz = Object.assign({ x: 1}, y )'
);
});
// Don't transform
it('should not transform destructuring assignment', function() {
expectTransform(
'let { x, ...y } = z',
'let { x, ...y } = z'
);
});
// Accessors are unsupported. We leave it unprocessed so that other,
// chained transforms, can take over.
it('should not transform when there are getters', function() {
expectTransform(
'let xy = { ...x, get x() { } }',
'let xy = { ...x, get x() { } }'
);
});
it('should not transform when there are setters', function() {
expectTransform(
'let xy = { set x(v) { }, ...y }',
'let xy = { set x(v) { }, ...y }'
);
});
it('should silently ignore falsy values', function() {
/*globals obj*/
var code = transform([
'var x = null;',
'var y = { y: "y" };',
'var obj = { ...x, ...y, ...{ ...false, z: "z", ...y } };'
].join('\n'));
eval(code);
expect(obj).toEqual({ y: 'y', z: 'z' });
});
});

View File

@@ -0,0 +1,52 @@
/*jshint evil:true*/
var tests = require("./../type-syntax-test");
var jstransform = require('../../../src/jstransform');
var visitors = require('../../type-syntax').visitorList;
var fs = require("fs");
var out = "/*\n";
out += "* WARNING: This file is autogenerated by visitors/__tests__/gen/generate-type-syntax-test.js\n";
out += "* Do NOT modify this file directly! Instead, add your tests to \n";
out += "* visitors/__tests__/type-syntax-test.js and run visitors/__tests__/gen/generate-type-syntax-test.js\n";
out += "*/\n\n";
function escape_content(content) {
return content
.replace(/[\\]/g, '\\\\')
.replace(/[\b]/g, '\\b')
.replace(/[\f]/g, '\\f')
.replace(/[\n]/g, '\\n')
.replace(/[\r]/g, '\\r')
.replace(/[\t]/g, '\\t')
.replace(/[']/g, "\\'");
}
out += "module.exports = {\n";
for (var section in tests) {
out += " '"+section+"': {\n";
for (var test in tests[section]) {
test = tests[section][test];
out += " '"+escape_content(test)+"': {\n";
try {
var transformed = jstransform.transform(visitors, test).code;
out += " raworiginal: '"+escape_content(test)+"',\n";
out += " transformed: '"+escape_content(transformed)+"',\n";
try {
eval(transformed);
out += " eval: 'No error',\n";
} catch (e) {
out += " eval: '"+e.message+"',\n";
}
} catch (e) {
out += " error: '"+e.message+"',\n";
}
out += "\n";
out += " },\n";
}
out += " },\n";
}
out += "};\n";
fs.writeFileSync(__dirname+'/type-syntax-test.rec.js', out);
console.log("Recorded type-syntax-test.js output into type-syntax-test.rec.js");

View File

@@ -0,0 +1,846 @@
/*
* WARNING: This file is autogenerated by visitors/__tests__/gen/generate-type-syntax-test.js
* Do NOT modify this file directly! Instead, add your tests to
* visitors/__tests__/type-syntax-test.js and run visitors/__tests__/gen/generate-type-syntax-test.js
*/
module.exports = {
'TypeAnnotations': {
'function foo(numVal: any){}': {
raworiginal: 'function foo(numVal: any){}',
transformed: 'function foo(numVal ){}',
eval: 'No error',
},
'function foo(numVal: number){}': {
raworiginal: 'function foo(numVal: number){}',
transformed: 'function foo(numVal ){}',
eval: 'No error',
},
'function foo(numVal: number, strVal: string){}': {
raworiginal: 'function foo(numVal: number, strVal: string){}',
transformed: 'function foo(numVal , strVal ){}',
eval: 'No error',
},
'function foo(numVal: number, untypedVal){}': {
raworiginal: 'function foo(numVal: number, untypedVal){}',
transformed: 'function foo(numVal , untypedVal){}',
eval: 'No error',
},
'function foo(untypedVal, numVal: number){}': {
raworiginal: 'function foo(untypedVal, numVal: number){}',
transformed: 'function foo(untypedVal, numVal ){}',
eval: 'No error',
},
'function foo(nullableNum: ?number){}': {
raworiginal: 'function foo(nullableNum: ?number){}',
transformed: 'function foo(nullableNum ){}',
eval: 'No error',
},
'function foo(callback: () => void){}': {
raworiginal: 'function foo(callback: () => void){}',
transformed: 'function foo(callback ){}',
eval: 'No error',
},
'function foo(callback: () => number){}': {
raworiginal: 'function foo(callback: () => number){}',
transformed: 'function foo(callback ){}',
eval: 'No error',
},
'function foo(callback: (_:bool) => number){}': {
raworiginal: 'function foo(callback: (_:bool) => number){}',
transformed: 'function foo(callback ){}',
eval: 'No error',
},
'function foo(callback: (_1:bool, _2:string) => number){}': {
raworiginal: 'function foo(callback: (_1:bool, _2:string) => number){}',
transformed: 'function foo(callback ){}',
eval: 'No error',
},
'function foo(callback: (_1:bool, ...foo:Array<number>) => number){}': {
raworiginal: 'function foo(callback: (_1:bool, ...foo:Array<number>) => number){}',
transformed: 'function foo(callback ){}',
eval: 'No error',
},
'function foo():number{}': {
raworiginal: 'function foo():number{}',
transformed: 'function foo() {}',
eval: 'No error',
},
'function foo():() => void{}': {
raworiginal: 'function foo():() => void{}',
transformed: 'function foo() {}',
eval: 'No error',
},
'function foo():(_:bool) => number{}': {
raworiginal: 'function foo():(_:bool) => number{}',
transformed: 'function foo() {}',
eval: 'No error',
},
'function foo():(_?:bool) => number{}': {
raworiginal: 'function foo():(_?:bool) => number{}',
transformed: 'function foo() {}',
eval: 'No error',
},
'function foo(): {} {}': {
raworiginal: 'function foo(): {} {}',
transformed: 'function foo() {}',
eval: 'No error',
},
'function foo<T>() {}': {
raworiginal: 'function foo<T>() {}',
transformed: 'function foo () {}',
eval: 'No error',
},
'function foo<T: Foo>() {}': {
raworiginal: 'function foo<T: Foo>() {}',
transformed: 'function foo () {}',
eval: 'No error',
},
'function foo<T,S>() {}': {
raworiginal: 'function foo<T,S>() {}',
transformed: 'function foo () {}',
eval: 'No error',
},
'a=function<T,S>() {}': {
raworiginal: 'a=function<T,S>() {}',
transformed: 'a=function () {}',
eval: 'No error',
},
'a={set fooProp(value:number){}}': {
raworiginal: 'a={set fooProp(value:number){}}',
transformed: 'a={set fooProp(value ){}}',
eval: 'No error',
},
'a={set fooProp(value:number): void{}}': {
raworiginal: 'a={set fooProp(value:number): void{}}',
transformed: 'a={set fooProp(value ) {}}',
eval: 'No error',
},
'a={get fooProp(): number {}}': {
raworiginal: 'a={get fooProp(): number {}}',
transformed: 'a={get fooProp() {}}',
eval: 'No error',
},
'class Foo {set fooProp(value:number){}}': {
raworiginal: 'class Foo {set fooProp(value:number){}}',
transformed: 'class Foo {set fooProp(value ){}}',
eval: 'Unexpected reserved word',
},
'class Foo {set fooProp(value:number): void{}}': {
raworiginal: 'class Foo {set fooProp(value:number): void{}}',
transformed: 'class Foo {set fooProp(value ) {}}',
eval: 'Unexpected reserved word',
},
'class Foo {get fooProp(): number{}}': {
raworiginal: 'class Foo {get fooProp(): number{}}',
transformed: 'class Foo {get fooProp() {}}',
eval: 'Unexpected reserved word',
},
'var numVal:number;': {
raworiginal: 'var numVal:number;',
transformed: 'var numVal ;',
eval: 'No error',
},
'var numVal:number = otherNumVal;': {
raworiginal: 'var numVal:number = otherNumVal;',
transformed: 'var numVal = otherNumVal;',
eval: 'otherNumVal is not defined',
},
'var a: {numVal: number};': {
raworiginal: 'var a: {numVal: number};',
transformed: 'var a ;',
eval: 'No error',
},
'var a: {numVal: number;};': {
raworiginal: 'var a: {numVal: number;};',
transformed: 'var a ;',
eval: 'No error',
},
'var a: {numVal: number; [indexer: string]: number};': {
raworiginal: 'var a: {numVal: number; [indexer: string]: number};',
transformed: 'var a ;',
eval: 'No error',
},
'var a: ?{numVal: number};': {
raworiginal: 'var a: ?{numVal: number};',
transformed: 'var a ;',
eval: 'No error',
},
'var a: {numVal: number; strVal: string}': {
raworiginal: 'var a: {numVal: number; strVal: string}',
transformed: 'var a ',
eval: 'No error',
},
'var a: {subObj: {strVal: string}}': {
raworiginal: 'var a: {subObj: {strVal: string}}',
transformed: 'var a ',
eval: 'No error',
},
'var a: {subObj: ?{strVal: string}}': {
raworiginal: 'var a: {subObj: ?{strVal: string}}',
transformed: 'var a ',
eval: 'No error',
},
'var a: {param1: number; param2: string}': {
raworiginal: 'var a: {param1: number; param2: string}',
transformed: 'var a ',
eval: 'No error',
},
'var a: {param1: number; param2?: string}': {
raworiginal: 'var a: {param1: number; param2?: string}',
transformed: 'var a ',
eval: 'No error',
},
'var a: {add(x:number, ...y:Array<string>): void}': {
raworiginal: 'var a: {add(x:number, ...y:Array<string>): void}',
transformed: 'var a ',
eval: 'No error',
},
'var a: { id<T>(x: T): T; }': {
raworiginal: 'var a: { id<T>(x: T): T; }',
transformed: 'var a ',
eval: 'No error',
},
'var a:Array<number> = [1, 2, 3]': {
raworiginal: 'var a:Array<number> = [1, 2, 3]',
transformed: 'var a = [1, 2, 3]',
eval: 'No error',
},
'a = class Foo<T> { }': {
raworiginal: 'a = class Foo<T> { }',
transformed: 'a = class Foo<T> { }',
eval: 'Unexpected reserved word',
},
'a = class Foo<T> extends Bar<T> { }': {
raworiginal: 'a = class Foo<T> extends Bar<T> { }',
transformed: 'a = class Foo<T> extends Bar<T> { }',
eval: 'Unexpected reserved word',
},
'class Foo<T> {}': {
raworiginal: 'class Foo<T> {}',
transformed: 'class Foo<T> {}',
eval: 'Unexpected reserved word',
},
'class Foo<T: Bar> {}': {
raworiginal: 'class Foo<T: Bar> {}',
transformed: 'class Foo<T > {}',
eval: 'Unexpected reserved word',
},
'class Foo<T> extends Bar<T> { }': {
raworiginal: 'class Foo<T> extends Bar<T> { }',
transformed: 'class Foo<T> extends Bar<T> { }',
eval: 'Unexpected reserved word',
},
'class Foo<T> extends mixin(Bar) { }': {
raworiginal: 'class Foo<T> extends mixin(Bar) { }',
transformed: 'class Foo<T> extends mixin(Bar) { }',
eval: 'Unexpected reserved word',
},
'class Foo<T> { bar<U>():number { return 42; }}': {
raworiginal: 'class Foo<T> { bar<U>():number { return 42; }}',
transformed: 'class Foo<T> { bar<U>() { return 42; }}',
eval: 'Unexpected reserved word',
},
'class Foo { "bar"<T>() { } }': {
raworiginal: 'class Foo { "bar"<T>() { } }',
transformed: 'class Foo { "bar"<T>() { } }',
eval: 'Unexpected reserved word',
},
'function foo(requiredParam, optParam?) {}': {
raworiginal: 'function foo(requiredParam, optParam?) {}',
transformed: 'function foo(requiredParam, optParam ) {}',
eval: 'No error',
},
'class Foo { prop1:string; prop2:number; }': {
raworiginal: 'class Foo { prop1:string; prop2:number; }',
transformed: 'class Foo { }',
eval: 'Unexpected reserved word',
},
'var x : number | string = 4;': {
raworiginal: 'var x : number | string = 4;',
transformed: 'var x = 4;',
eval: 'No error',
},
'class Array { concat(items:number | string) {}; }': {
raworiginal: 'class Array { concat(items:number | string) {}; }',
transformed: 'class Array { concat(items ) {}; }',
eval: 'Unexpected reserved word',
},
'var x : () => number | () => string = fn;': {
raworiginal: 'var x : () => number | () => string = fn;',
transformed: 'var x = fn;',
eval: 'fn is not defined',
},
'var x: typeof Y = Y;': {
raworiginal: 'var x: typeof Y = Y;',
transformed: 'var x = Y;',
eval: 'Y is not defined',
},
'var x: typeof Y | number = Y;': {
raworiginal: 'var x: typeof Y | number = Y;',
transformed: 'var x = Y;',
eval: 'Y is not defined',
},
'var {x}: {x: string; } = { x: "hello" };': {
raworiginal: 'var {x}: {x: string; } = { x: "hello" };',
transformed: 'var {x} = { x: "hello" };',
eval: 'Unexpected token {',
},
'var {x}: {x: string } = { x: "hello" };': {
raworiginal: 'var {x}: {x: string } = { x: "hello" };',
transformed: 'var {x} = { x: "hello" };',
eval: 'Unexpected token {',
},
'var [x]: Array<string> = [ "hello" ];': {
raworiginal: 'var [x]: Array<string> = [ "hello" ];',
transformed: 'var [x] = [ "hello" ];',
eval: 'Unexpected token [',
},
'function foo({x}: { x: string; }) {}': {
raworiginal: 'function foo({x}: { x: string; }) {}',
transformed: 'function foo({x} ) {}',
eval: 'Unexpected token {',
},
'function foo([x]: Array<string>) {}': {
raworiginal: 'function foo([x]: Array<string>) {}',
transformed: 'function foo([x] ) {}',
eval: 'Unexpected token [',
},
'function foo(...rest: Array<number>) {}': {
raworiginal: 'function foo(...rest: Array<number>) {}',
transformed: 'function foo(...rest ) {}',
eval: 'Unexpected token .',
},
'(function (...rest: Array<number>) {})': {
raworiginal: '(function (...rest: Array<number>) {})',
transformed: '(function (...rest ) {})',
eval: 'Unexpected token .',
},
'((...rest: Array<number>) => rest)': {
raworiginal: '((...rest: Array<number>) => rest)',
transformed: '((...rest ) => rest)',
eval: 'Unexpected token .',
},
'var a: Map<string, Array<string> >': {
raworiginal: 'var a: Map<string, Array<string> >',
transformed: 'var a ',
eval: 'No error',
},
'var a: Map<string, Array<string>>': {
raworiginal: 'var a: Map<string, Array<string>>',
transformed: 'var a ',
eval: 'No error',
},
'var a: number[]': {
raworiginal: 'var a: number[]',
transformed: 'var a ',
eval: 'No error',
},
'var a: ?string[]': {
raworiginal: 'var a: ?string[]',
transformed: 'var a ',
eval: 'No error',
},
'var a: Promise<bool>[]': {
raworiginal: 'var a: Promise<bool>[]',
transformed: 'var a ',
eval: 'No error',
},
'var a:(...rest:Array<number>) => number': {
raworiginal: 'var a:(...rest:Array<number>) => number',
transformed: 'var a ',
eval: 'No error',
},
},
'Type Alias': {
'type FBID = number;': {
raworiginal: 'type FBID = number;',
transformed: ' ',
eval: 'No error',
},
'type Foo<T> = Bar<T>': {
raworiginal: 'type Foo<T> = Bar<T>',
transformed: ' ',
eval: 'No error',
},
},
'Interfaces': {
'interface A {}': {
raworiginal: 'interface A {}',
transformed: ' ',
eval: 'No error',
},
'interface A extends B {}': {
raworiginal: 'interface A extends B {}',
transformed: ' ',
eval: 'No error',
},
'interface A<T> extends B<T>, C<T> {}': {
raworiginal: 'interface A<T> extends B<T>, C<T> {}',
transformed: ' ',
eval: 'No error',
},
'interface A { foo: () => number; }': {
raworiginal: 'interface A { foo: () => number; }',
transformed: ' ',
eval: 'No error',
},
'interface Dictionary { [index: string]: string; length: number; }': {
raworiginal: 'interface Dictionary { [index: string]: string; length: number; }',
transformed: ' ',
eval: 'No error',
},
'class Foo implements Bar {}': {
raworiginal: 'class Foo implements Bar {}',
transformed: 'class Foo implements Bar {}',
eval: 'Unexpected reserved word',
},
'class Foo extends Bar implements Bat, Man<number> {}': {
raworiginal: 'class Foo extends Bar implements Bat, Man<number> {}',
transformed: 'class Foo extends Bar implements Bat, Man<number> {}',
eval: 'Unexpected reserved word',
},
'class Foo extends class Bar implements Bat {} {}': {
raworiginal: 'class Foo extends class Bar implements Bat {} {}',
transformed: 'class Foo extends class Bar implements Bat {} {}',
eval: 'Unexpected reserved word',
},
'class Foo extends class Bar implements Bat {} implements Man {}': {
raworiginal: 'class Foo extends class Bar implements Bat {} implements Man {}',
transformed: 'class Foo extends class Bar implements Bat {} implements Man {}',
eval: 'Unexpected reserved word',
},
},
'Type Grouping': {
'var a: (number)': {
raworiginal: 'var a: (number)',
transformed: 'var a ',
eval: 'No error',
},
'var a: (() => number) | () => string': {
raworiginal: 'var a: (() => number) | () => string',
transformed: 'var a ',
eval: 'No error',
},
'var a: number & (string | bool)': {
raworiginal: 'var a: number & (string | bool)',
transformed: 'var a ',
eval: 'No error',
},
'var a: (typeof A)': {
raworiginal: 'var a: (typeof A)',
transformed: 'var a ',
eval: 'No error',
},
},
'XJS': {
'<a />': {
raworiginal: '<a />',
transformed: '<a />',
eval: 'Unexpected token <',
},
'<n:a n:v />': {
raworiginal: '<n:a n:v />',
transformed: '<n:a n:v />',
eval: 'Unexpected token <',
},
'<a n:foo="bar"> {value} <b><c /></b></a>': {
raworiginal: '<a n:foo="bar"> {value} <b><c /></b></a>',
transformed: '<a n:foo="bar"> {value} <b><c /></b></a>',
eval: 'Unexpected token <',
},
'<a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;" />': {
raworiginal: '<a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;" />',
transformed: '<a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;" />',
eval: 'Unexpected token <',
},
'<a\n/>': {
raworiginal: '<a\n/>',
transformed: '<a\n/>',
eval: 'Unexpected token <',
},
'<日本語></日本語>': {
raworiginal: '<日本語></日本語>',
transformed: '<日本語></日本語>',
eval: 'Unexpected token <',
},
'<AbC-def\n test="&#x0026;&#38;">\nbar\nbaz\r\n</AbC-def>': {
raworiginal: '<AbC-def\n test="&#x0026;&#38;">\nbar\nbaz\r\n</AbC-def>',
transformed: '<AbC-def\n test="&#x0026;&#38;">\nbar\nbaz\r\n</AbC-def>',
eval: 'Unexpected token <',
},
'<a b={x ? <c /> : <d />} />': {
raworiginal: '<a b={x ? <c /> : <d />} />',
transformed: '<a b={x ? <c /> : <d />} />',
eval: 'Unexpected token <',
},
'<a>{}</a>': {
raworiginal: '<a>{}</a>',
transformed: '<a>{}</a>',
eval: 'Unexpected token <',
},
'<a>{/* this is a comment */}</a>': {
raworiginal: '<a>{/* this is a comment */}</a>',
transformed: '<a>{/* this is a comment */}</a>',
eval: 'Unexpected token <',
},
'<div>@test content</div>': {
raworiginal: '<div>@test content</div>',
transformed: '<div>@test content</div>',
eval: 'Unexpected token <',
},
'<div><br />7x invalid-js-identifier</div>': {
raworiginal: '<div><br />7x invalid-js-identifier</div>',
transformed: '<div><br />7x invalid-js-identifier</div>',
eval: 'Unexpected token <',
},
'<LeftRight left=<a /> right=<b>monkeys</b> />': {
raworiginal: '<LeftRight left=<a /> right=<b>monkeys</b> />',
transformed: '<LeftRight left=<a /> right=<b>monkeys</b> />',
eval: 'Unexpected token <',
},
'<a.b></a.b>': {
raworiginal: '<a.b></a.b>',
transformed: '<a.b></a.b>',
eval: 'Unexpected token <',
},
'<a.b.c></a.b.c>': {
raworiginal: '<a.b.c></a.b.c>',
transformed: '<a.b.c></a.b.c>',
eval: 'Unexpected token <',
},
'(<div />) < x;': {
raworiginal: '(<div />) < x;',
transformed: '(<div />) < x;',
eval: 'Unexpected token <',
},
'<div {...props} />': {
raworiginal: '<div {...props} />',
transformed: '<div {...props} />',
eval: 'Unexpected token <',
},
'<div {...props} post="attribute" />': {
raworiginal: '<div {...props} post="attribute" />',
transformed: '<div {...props} post="attribute" />',
eval: 'Unexpected token <',
},
'<div pre="leading" pre2="attribute" {...props}></div>': {
raworiginal: '<div pre="leading" pre2="attribute" {...props}></div>',
transformed: '<div pre="leading" pre2="attribute" {...props}></div>',
eval: 'Unexpected token <',
},
'<a> </a>': {
raworiginal: '<a> </a>',
transformed: '<a> </a>',
eval: 'Unexpected token <',
},
},
'Call Properties': {
'var a : { (): number }': {
raworiginal: 'var a : { (): number }',
transformed: 'var a ',
eval: 'No error',
},
'var a : { (): number; }': {
raworiginal: 'var a : { (): number; }',
transformed: 'var a ',
eval: 'No error',
},
'var a : { (): number; y: string; (x: string): string }': {
raworiginal: 'var a : { (): number; y: string; (x: string): string }',
transformed: 'var a ',
eval: 'No error',
},
'var a : { <T>(x: T): number; }': {
raworiginal: 'var a : { <T>(x: T): number; }',
transformed: 'var a ',
eval: 'No error',
},
'interface A { (): number; }': {
raworiginal: 'interface A { (): number; }',
transformed: ' ',
eval: 'No error',
},
},
'String Literal Types': {
'function createElement(tagName: "div"): HTMLDivElement {}': {
raworiginal: 'function createElement(tagName: "div"): HTMLDivElement {}',
transformed: 'function createElement(tagName ) {}',
eval: 'No error',
},
'function createElement(tagName: \'div\'): HTMLDivElement {}': {
raworiginal: 'function createElement(tagName: \'div\'): HTMLDivElement {}',
transformed: 'function createElement(tagName ) {}',
eval: 'No error',
},
},
'Qualified Generic Type': {
'var a : A.B': {
raworiginal: 'var a : A.B',
transformed: 'var a ',
eval: 'No error',
},
'var a : A.B.C': {
raworiginal: 'var a : A.B.C',
transformed: 'var a ',
eval: 'No error',
},
'var a : A.B<T>': {
raworiginal: 'var a : A.B<T>',
transformed: 'var a ',
eval: 'No error',
},
'var a : typeof A.B<T>': {
raworiginal: 'var a : typeof A.B<T>',
transformed: 'var a ',
eval: 'No error',
},
},
'Declare Statements': {
'declare var foo': {
raworiginal: 'declare var foo',
transformed: ' ',
eval: 'No error',
},
'declare var foo;': {
raworiginal: 'declare var foo;',
transformed: ' ',
eval: 'No error',
},
'declare function foo(): void': {
raworiginal: 'declare function foo(): void',
transformed: ' ',
eval: 'No error',
},
'declare function foo(): void;': {
raworiginal: 'declare function foo(): void;',
transformed: ' ',
eval: 'No error',
},
'declare function foo<T>(): void;': {
raworiginal: 'declare function foo<T>(): void;',
transformed: ' ',
eval: 'No error',
},
'declare function foo(x: number, y: string): void;': {
raworiginal: 'declare function foo(x: number, y: string): void;',
transformed: ' ',
eval: 'No error',
},
'declare class A {}': {
raworiginal: 'declare class A {}',
transformed: ' ',
eval: 'No error',
},
'declare class A<T> extends B<T> { x: number }': {
raworiginal: 'declare class A<T> extends B<T> { x: number }',
transformed: ' ',
eval: 'No error',
},
'declare class A { static foo(): number; static x : string }': {
raworiginal: 'declare class A { static foo(): number; static x : string }',
transformed: ' ',
eval: 'No error',
},
'declare class A { static [ indexer: number]: string }': {
raworiginal: 'declare class A { static [ indexer: number]: string }',
transformed: ' ',
eval: 'No error',
},
'declare class A { static () : number }': {
raworiginal: 'declare class A { static () : number }',
transformed: ' ',
eval: 'No error',
},
},
'Declare Module': {
'declare module A {}': {
raworiginal: 'declare module A {}',
transformed: ' ',
eval: 'No error',
},
'declare module "./a/b.js" {}': {
raworiginal: 'declare module "./a/b.js" {}',
transformed: ' ',
eval: 'No error',
},
'declare module A { declare var x: number; }': {
raworiginal: 'declare module A { declare var x: number; }',
transformed: ' ',
eval: 'No error',
},
'declare module A { declare function foo(): number; }': {
raworiginal: 'declare module A { declare function foo(): number; }',
transformed: ' ',
eval: 'No error',
},
'declare module A { declare class B { foo(): number; } }': {
raworiginal: 'declare module A { declare class B { foo(): number; } }',
transformed: ' ',
eval: 'No error',
},
},
'Typecasts': {
'(xxx: number)': {
raworiginal: '(xxx: number)',
transformed: '(xxx )',
eval: 'xxx is not defined',
},
'({xxx: 0, yyy: "hey"}: {xxx: number; yyy: string})': {
raworiginal: '({xxx: 0, yyy: "hey"}: {xxx: number; yyy: string})',
transformed: '({xxx: 0, yyy: "hey"} )',
eval: 'No error',
},
'((xxx) => xxx + 1: (xxx: number) => number)': {
raworiginal: '((xxx) => xxx + 1: (xxx: number) => number)',
transformed: '((xxx) => xxx + 1 )',
eval: 'Unexpected token >',
},
'((xxx: number), (yyy: string))': {
raworiginal: '((xxx: number), (yyy: string))',
transformed: '((xxx ), (yyy ))',
eval: 'xxx is not defined',
},
'((xxx: any): number)': {
raworiginal: '((xxx: any): number)',
transformed: '((xxx ) )',
eval: 'xxx is not defined',
},
},
};

View File

@@ -0,0 +1,74 @@
/**
* Copyright 2014 Facebook, Inc.
*
* 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.
*
* @emails jeffmo@fb.com
*/
/*jshint evil:true*/
jest.autoMockOff();
describe('reserved-words', function() {
var transformFn;
var visitors;
beforeEach(function() {
require('mock-modules').dumpCache();
visitors = require('../reserved-words-visitors').visitorList;
transformFn = require('../../src/jstransform').transform;
});
function transform(code, opts) {
// No need for visitors as long as we are not in es5 mode.
return transformFn(visitors, code, opts).code;
}
describe('reserved words in member expressions', function() {
it('should transform to reserved word members to computed', function() {
var code = 'foo.delete;';
expect(transform(code)).toEqual('foo["delete"];');
code = '(foo++).delete;';
expect(transform(code)).toEqual('(foo++)["delete"];');
});
it('should handle call expressions', function() {
var code = 'foo.return();';
expect(transform(code)).toEqual('foo["return"]();');
});
it('should only quote ES3 reserved words', function() {
var code = 'foo.await();';
expect(transform(code)).toEqual('foo.await();');
});
});
describe('reserved words in properties', function() {
it('should quote reserved words in properties', function() {
var code = 'var x = {null: 1};';
expect(transform(code)).toEqual('var x = {"null": 1};');
});
it('should only quote ES3 reserved words', function() {
var code = 'var x = {await: 1};';
expect(transform(code)).toEqual('var x = {await: 1};');
});
});
});

View File

@@ -0,0 +1,83 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('static type syntax syntax', function() {
var flowSyntaxVisitors;
var jstransform;
beforeEach(function() {
require('mock-modules').dumpCache();
flowSyntaxVisitors = require('../type-syntax.js').visitorList;
jstransform = require('jstransform');
});
function transform(code, visitors) {
code = jstransform.transform(
flowSyntaxVisitors,
code.join('\n'),
{sourceType: 'nonStrictModule'}
).code;
if (visitors) {
code = jstransform.transform(
visitors,
code
).code;
}
return code;
}
describe('type alias', () => {
it('strips type aliases', () => {
/*global type*/
/*global sanityCheck*/
var code = transform([
'var type = 42;',
'type FBID = number;',
'type type = string',
'type += 42;'
]);
eval(code);
expect(type).toBe(84);
});
it('strips import-type declarations', () => {
var code = transform([
'import type DefaultExport from "MyModule";',
'var sanityCheck = 42;',
]);
eval(code);
expect(sanityCheck).toBe(42);
});
it('catches up correctly', () => {
var code = transform([
"var X = require('X');",
'type FBID = number;',
]);
expect(code).toBe([
"var X = require('X');",
' '
].join('\n'));
});
});
});

View File

@@ -0,0 +1,298 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/*jshint evil:true*/
/*jshint -W117*/
require('mock-modules').autoMockOff();
describe('static type class syntax', function() {
var classSyntaxVisitors;
var visitorList;
var flowSyntaxVisitors;
var jstransform;
beforeEach(function() {
require('mock-modules').dumpCache();
classSyntaxVisitors =
require('../es6-class-visitors').visitorList;
flowSyntaxVisitors = require('../type-syntax.js').visitorList;
jstransform = require('jstransform');
visitorList = classSyntaxVisitors;
});
function transform(code, visitors) {
visitors = visitors ? visitorList.concat(visitors) : visitorList;
code = jstransform.transform(
flowSyntaxVisitors,
code.join('\n')
).code;
return jstransform.transform(
visitors,
code
).code;
}
describe('param type annotations', () => {
it('strips single param annotation', () => {
var code = transform([
'class Foo {',
' method1(param1: bool) {',
' return param1;',
' }',
'}',
'',
'var Bar = class {',
' method1(param1: bool) {',
' return param1;',
' }',
'}'
]);
eval(code);
expect((new Foo()).method1(42)).toBe(42);
expect((new Bar()).method1(42)).toBe(42);
});
it('strips multiple param annotations', () => {
var code = transform([
'class Foo {',
' method1(param1: bool, param2: number) {',
' return [param1, param2];',
' }',
'}',
'',
'var Bar = class {',
' method1(param1: bool, param2: number) {',
' return [param1, param2];',
' }',
'}'
]);
eval(code);
expect((new Foo()).method1(true, 42)).toEqual([true, 42]);
expect((new Bar()).method1(true, 42)).toEqual([true, 42]);
});
it('strips higher-order param annotations', () => {
var code = transform([
'class Foo {',
' method1(param1: (_:bool) => number) {',
' return param1;',
' }',
'}',
'',
'var Bar = class {',
' method1(param1: (_:bool) => number) {',
' return param1;',
' }',
'}'
]);
eval(code);
var callback = function(param) {
return param ? 42 : 0;
};
expect((new Foo()).method1(callback)).toBe(callback);
expect((new Bar()).method1(callback)).toBe(callback);
});
it('strips annotated params next to non-annotated params', () => {
var code = transform([
'class Foo {',
' method1(param1, param2: number) {',
' return [param1, param2];',
' }',
'}',
'',
'var Bar = class {',
' method1(param1, param2: number) {',
' return [param1, param2];',
' }',
'}'
]);
eval(code);
expect((new Foo()).method1('p1', 42)).toEqual(['p1', 42]);
expect((new Bar()).method1('p1', 42)).toEqual(['p1', 42]);
});
it('strips annotated params before a rest parameter', () => {
var restParamVisitors =
require('../es6-rest-param-visitors').visitorList;
var code = transform([
'class Foo {',
' method1(param1: number, ...args) {',
' return [param1, args];',
' }',
'}',
'',
'var Bar = class {',
' method1(param1: number, ...args) {',
' return [param1, args];',
' }',
'}'
], restParamVisitors);
eval(code);
expect((new Foo()).method1(42, 43, 44)).toEqual([42, [43, 44]]);
expect((new Bar()).method1(42, 43, 44)).toEqual([42, [43, 44]]);
});
});
describe('return type annotations', () => {
it('strips method return types', () => {
var code = transform([
'class Foo {',
' method1(param1:number): () => number {',
' return function() { return param1; };',
' }',
'}',
'',
'var Bar = class {',
' method1(param1:number): () => number {',
' return function() { return param1; };',
' }',
'}'
]);
eval(code);
expect((new Foo()).method1(42)()).toBe(42);
expect((new Bar()).method1(42)()).toBe(42);
});
});
describe('parametric type annotation', () => {
it('strips parametric class type annotations', () => {
var code = transform([
'class Foo<T> {',
' method1(param1) {',
' return param1;',
' }',
'}',
'',
// TODO: Need to add support to esprima for this
// 'var Bar = class<T> {',
// ' method1(param1) {',
// ' return param1;',
// ' }',
// '}'
]);
eval(code);
expect((new Foo()).method1(42)).toBe(42);
// expect((new Bar()).method1(42)).toBe(42);
});
it('strips multi-parameter class type annotations', () => {
var code = transform([
'class Foo<T,S> {',
' method1(param1) {',
' return param1;',
' }',
'}',
'',
// TODO: Need to add support to esprima for this
// 'var Bar = class<T> {',
// ' method1(param1) {',
// ' return param1;',
// ' }',
// '}'
]);
eval(code);
expect((new Foo()).method1(42)).toBe(42);
// expect(bar(42)).toBe(42);
});
it('strips parametric method type annotations', () => {
var code = transform([
'class Foo<T> {',
' method1<T>(param1) {',
' return param1;',
' }',
'}',
'',
// TODO: Need to add support to esprima for this
// 'var Bar = class<T> {',
// ' method1<T>(param1) {',
// ' return param1;',
// ' }',
// '}'
]);
eval(code);
expect((new Foo()).method1(42)).toBe(42);
// expect((new Bar()).method1(42)).toBe(42);
});
it('strips multi-parameter class type annotations', () => {
var code = transform([
'class Foo<T,S> {',
' method1<T,S>(param1) {',
' return param1;',
' }',
'}',
'',
// TODO: Need to add support to esprima for this
// 'var Bar = class<T> {',
// ' method1(param1) {',
// ' return param1;',
// ' }',
// '}'
]);
eval(code);
expect((new Foo()).method1(42)).toBe(42);
// expect(bar(42)).toBe(42);
});
});
describe('class property annotations', () => {
it('strips single class property', () => {
var code = transform([
'class Foo {',
' prop1: T;',
'}'
]);
eval(code);
expect((new Foo()).prop1).toEqual(undefined);
});
it('strips multiple adjacent class properties', () => {
var code = transform([
'class Foo {',
' prop1: T;',
' prop2: U;',
'}'
]);
eval(code);
expect((new Foo()).prop1).toEqual(undefined);
expect((new Foo()).prop2).toEqual(undefined);
});
it('strips class properties between methods', () => {
var code = transform([
'class Foo {',
' method1() {}',
' prop1: T;',
' method2() {}',
' prop2: U;',
' method3() {}',
'}'
]);
eval(code);
expect((new Foo()).prop1).toEqual(undefined);
expect((new Foo()).prop2).toEqual(undefined);
});
});
});

View File

@@ -0,0 +1,298 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/*jshint evil:true*/
/*jshint -W117*/
require('mock-modules').autoMockOff();
describe('static type function syntax', function() {
var flowSyntaxVisitors;
var jstransform;
beforeEach(function() {
require('mock-modules').dumpCache();
flowSyntaxVisitors = require('../type-syntax.js').visitorList;
jstransform = require('jstransform');
});
function transform(code, visitors) {
code = code.join('\n');
// We run the flow transform first
code = jstransform.transform(
flowSyntaxVisitors,
code
).code;
if (visitors) {
code = jstransform.transform(
visitors,
code
).code;
}
return code;
}
describe('param type annotations', () => {
it('strips single param annotation', () => {
var code = transform([
'function foo(param1: bool) {',
' return param1;',
'}',
'',
'var bar = function(param1: bool) {',
' return param1;',
'}'
]);
eval(code);
expect(foo(42)).toBe(42);
expect(bar(42)).toBe(42);
});
it('strips multiple param annotations', () => {
var code = transform([
'function foo(param1: bool, param2: number) {',
' return [param1, param2];',
'}',
'',
'var bar = function(param1: bool, param2: number) {',
' return [param1, param2];',
'}'
]);
eval(code);
expect(foo(true, 42)).toEqual([true, 42]);
expect(bar(true, 42)).toEqual([true, 42]);
});
it('strips higher-order param annotations', () => {
var code = transform([
'function foo(param1: (_:bool) => number) {',
' return param1;',
'}',
'',
'var bar = function(param1: (_:bool) => number) {',
' return param1;',
'}'
]);
eval(code);
var callback = function(param) {
return param ? 42 : 0;
};
expect(foo(callback)).toBe(callback);
expect(bar(callback)).toBe(callback);
});
it('strips annotated params next to non-annotated params', () => {
var code = transform([
'function foo(param1, param2: number) {',
' return [param1, param2];',
'}',
'',
'var bar = function(param1, param2: number) {',
' return [param1, param2];',
'}'
]);
eval(code);
expect(foo('p1', 42)).toEqual(['p1', 42]);
expect(bar('p1', 42)).toEqual(['p1', 42]);
});
it('strips annotated params before a rest parameter', () => {
var restParamVisitors =
require('../es6-rest-param-visitors').visitorList;
var code = transform([
'function foo(param1: number, ...args) {',
' return [param1, args];',
'}',
'',
'var bar = function(param1: number, ...args) {',
' return [param1, args];',
'}'
], restParamVisitors);
eval(code);
expect(foo(42, 43, 44)).toEqual([42, [43, 44]]);
expect(bar(42, 43, 44)).toEqual([42, [43, 44]]);
});
it('strips annotated rest parameter', () => {
var restParamVisitors =
require('../es6-rest-param-visitors').visitorList;
var code = transform([
'function foo(param1: number, ...args: Array<number>) {',
' return [param1, args];',
'}',
'',
'var bar = function(param1: number, ...args: Array<number>) {',
' return [param1, args];',
'}'
], restParamVisitors);
eval(code);
expect(foo(42, 43, 44)).toEqual([42, [43, 44]]);
expect(bar(42, 43, 44)).toEqual([42, [43, 44]]);
});
it('strips optional param marker without type annotation', () => {
var code = transform([
'function foo(param1?, param2 ?) {',
' return 42;',
'}'
]);
eval(code);
expect(foo()).toBe(42);
});
it('strips optional param marker with type annotation', () => {
var code = transform([
'function foo(param1?:number, param2 ?: string, param3 ? : bool) {',
' return 42;',
'}'
]);
eval(code);
expect(foo()).toBe(42);
});
});
describe('return type annotations', () => {
it('strips function return types', () => {
var code = transform([
'function foo(param1:number): () => number {',
' return function() { return param1; };',
'}',
'',
'var bar = function(param1:number): () => number {',
' return function() { return param1; };',
'}'
]);
eval(code);
expect(foo(42)()).toBe(42);
expect(bar(42)()).toBe(42);
});
it('strips void return types', () => {
var code = transform([
'function foo(param1): void {',
' param1();',
'}',
'',
'var bar = function(param1): void {',
' param1();',
'}'
]);
eval(code);
var counter = 0;
function testFn() {
counter++;
}
foo(testFn);
expect(counter).toBe(1);
bar(testFn);
expect(counter).toBe(2);
});
it('strips void return types with rest params', () => {
var code = transform(
[
'function foo(param1, ...rest): void {',
' param1();',
'}',
'',
'var bar = function(param1, ...rest): void {',
' param1();',
'}'
],
require('../es6-rest-param-visitors').visitorList
);
eval(code);
var counter = 0;
function testFn() {
counter++;
}
foo(testFn);
expect(counter).toBe(1);
bar(testFn);
expect(counter).toBe(2);
});
it('strips object return types', () => {
var code = transform([
'function foo(param1:number): {num: number} {',
' return {num: param1};',
'}',
'',
'var bar = function(param1:number): {num: number} {',
' return {num: param1};',
'}'
]);
eval(code);
expect(foo(42)).toEqual({num: 42});
expect(bar(42)).toEqual({num: 42});
});
});
describe('parametric type annotation', () => {
it('strips parametric type annotations', () => {
var code = transform([
'function foo<T>(param1) {',
' return param1;',
'}',
'',
'var bar = function<T>(param1) {',
' return param1;',
'}',
]);
eval(code);
expect(foo(42)).toBe(42);
expect(bar(42)).toBe(42);
});
it('strips multi-parameter type annotations', () => {
var restParamVisitors =
require('../es6-rest-param-visitors').visitorList;
var code = transform([
'function foo<T, S>(param1) {',
' return param1;',
'}',
'',
'var bar = function<T,S>(param1) {',
' return param1;',
'}'
], restParamVisitors);
eval(code);
expect(foo(42)).toBe(42);
expect(bar(42)).toBe(42);
});
});
describe('arrow functions', () => {
// TODO: We don't currently support arrow functions, but we should
// soon! The only reason we don't now is because we don't
// need it at this very moment and I'm in a rush to get the
// basics in.
});
});

View File

@@ -0,0 +1,72 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/*jshint evil:true*/
require('mock-modules').autoMockOff();
describe('static type interface syntax', function() {
var flowSyntaxVisitors;
var jstransform;
beforeEach(function() {
require('mock-modules').dumpCache();
flowSyntaxVisitors = require('../type-syntax.js').visitorList;
jstransform = require('jstransform');
});
function transform(code, visitors) {
code = jstransform.transform(
flowSyntaxVisitors,
code.join('\n')
).code;
if (visitors) {
code = jstransform.transform(
visitors,
code
).code;
}
return code;
}
describe('Interface Declaration', () => {
it('strips interface declarations', () => {
/*global interface*/
var code = transform([
'var interface = 42;',
'interface A { foo: () => number; }',
'if (true) interface += 42;',
'interface A<T> extends B, C<T> { foo: () => number; }',
'interface += 42;'
]);
eval(code);
expect(interface).toBe(126);
});
it('catches up correctly', () => {
var code = transform([
"var X = require('X');",
'interface A { foo: () => number; }',
]);
expect(code).toBe([
"var X = require('X');",
' '
].join('\n'));
});
});
});

View File

@@ -0,0 +1,186 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/*jshint evil:true*/
/*jshint -W117*/
require('mock-modules').autoMockOff();
describe('static type object-method syntax', function() {
var flowSyntaxVisitors;
var jstransform;
var visitorList;
beforeEach(function() {
require('mock-modules').dumpCache();
flowSyntaxVisitors = require('../type-syntax.js').visitorList;
jstransform = require('jstransform');
objMethodVisitors =
require('../es6-object-concise-method-visitors');
visitorList = objMethodVisitors.visitorList;
});
function transform(code, visitors) {
visitors = visitors ? visitorList.concat(visitors) : visitorList;
// We run the flow transform first
code = jstransform.transform(
flowSyntaxVisitors,
code.join('\n')
).code;
code = jstransform.transform(
visitors,
code
).code;
return code;
}
describe('param type annotations', () => {
it('strips single param annotation', () => {
var code = transform([
'var foo = {',
' bar(param1: bool) {',
' return param1;',
' }',
'};',
]);
eval(code);
expect(foo.bar(42)).toBe(42);
});
it('strips multiple param annotations', () => {
var code = transform([
'var foo = {',
' bar(param1: bool, param2: number) {',
' return [param1, param2];',
' }',
'};'
]);
eval(code);
expect(foo.bar(true, 42)).toEqual([true, 42]);
});
it('strips higher-order param annotations', () => {
var code = transform([
'var foo = {',
' bar(param1: (_:bool) => number) {',
' return param1;',
' }',
'};'
]);
eval(code);
var callback = function(param) {
return param ? 42 : 0;
};
expect(foo.bar(callback)).toBe(callback);
});
it('strips annotated params next to non-annotated params', () => {
var code = transform([
'var foo = {',
' bar(param1, param2: number) {',
' return [param1, param2];',
' }',
'}',
]);
eval(code);
expect(foo.bar('p1', 42)).toEqual(['p1', 42]);
});
it('strips annotated params before a rest parameter', () => {
var restParamVisitors =
require('../es6-rest-param-visitors').visitorList;
var code = transform([
'var foo = {',
' bar(param1: number, ...args) {',
' return [param1, args];',
' }',
'}',
], restParamVisitors);
eval(code);
expect(foo.bar(42, 43, 44)).toEqual([42, [43, 44]]);
});
it('strips annotated rest parameter', () => {
var restParamVisitors =
require('../es6-rest-param-visitors').visitorList;
var code = transform([
'var foo = {',
' bar(param1: number, ...args: Array<number>): Array<any> {',
' return [param1, args];',
' }',
'}',
], restParamVisitors);
eval(code);
expect(foo.bar(42, 43, 44)).toEqual([42, [43, 44]]);
});
});
describe('return type annotations', () => {
it('strips function return types', () => {
var code = transform([
'var foo = {',
' bar(param1:number): () => number {',
' return function() { return param1; };',
' }',
'}',
]);
eval(code);
expect(foo.bar(42)()).toBe(42);
});
});
describe('parametric type annotation', () => {
// TODO: Fix esprima parsing for these cases
/*
it('strips parametric type annotations', () => {
// TODO: Doesnt parse
var code = transform([
'var foo = {',
' bar<T>(param1) {',
' return param1;',
' }',
'}',
]);
eval(code);
expect(foo.bar(42)).toBe(42);
});
it('strips multi-parameter type annotations', () => {
// TODO: Doesnt parse
var restParamVisitors =
require('../es6-rest-param-visitors').visitorList;
var code = transform([
'var foo = {',
' bar<T, S>(param1) {',
' return param1;',
' }',
'}',
], restParamVisitors);
eval(code);
expect(foo.bar(42)).toBe(42);
});
*/
});
});

View File

@@ -0,0 +1,91 @@
/**
* Copyright 2013 Facebook, Inc.
*
* 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.
*/
/*jshint evil:true*/
/*jshint -W117*/
require('mock-modules').autoMockOff();
describe('static type pattern syntax', function() {
var flowSyntaxVisitors;
var jstransform;
beforeEach(function() {
require('mock-modules').dumpCache();
flowSyntaxVisitors = require('../type-syntax.js').visitorList;
jstransform = require('jstransform');
destructuringVisitors =
require('../es6-destructuring-visitors');
visitorList = destructuringVisitors.visitorList;
});
function transform(code, visitors) {
visitors = visitors ? visitorList.concat(visitors) : visitorList;
// We run the flow transform first
code = jstransform.transform(
flowSyntaxVisitors,
code.join('\n')
).code;
code = jstransform.transform(
visitors,
code
).code;
return code;
}
describe('Object Pattern', () => {
it('strips function argument type annotation', () => {
var code = transform([
'function foo({x, y}: {x: number; y: number}) { return x+y; }',
'var thirty = foo({x: 10, y: 20});'
]);
eval(code);
expect(thirty).toBe(30);
});
it('strips var declaration type annotation', () => {
var code = transform([
'var {x, y}: {x: number; y: string} = { x: 42, y: "hello" };'
]);
eval(code);
expect(x).toBe(42);
expect(y).toBe("hello");
});
});
describe('Array Pattern', () => {
it('strips function argument type annotation', () => {
var code = transform([
'function foo([x, y]: Array<number>) { return x+y; }',
'var thirty = foo([10, 20]);'
]);
eval(code);
expect(thirty).toBe(30);
});
it('strips var declaration type annotation', () => {
var code = transform([
'var [x, y]: Array<number> = [42, "hello"];'
]);
eval(code);
expect(x).toBe(42);
expect(y).toBe("hello");
});
});
});

View File

@@ -0,0 +1,194 @@
/*jshint evil: true*/
/*jshint loopfunc: true*/
if (!!module.parent) {
module.exports = {
'TypeAnnotations': [
'function foo(numVal: any){}',
'function foo(numVal: number){}',
'function foo(numVal: number, strVal: string){}',
'function foo(numVal: number, untypedVal){}',
'function foo(untypedVal, numVal: number){}',
'function foo(nullableNum: ?number){}',
'function foo(callback: () => void){}',
'function foo(callback: () => number){}',
'function foo(callback: (_:bool) => number){}',
'function foo(callback: (_1:bool, _2:string) => number){}',
'function foo(callback: (_1:bool, ...foo:Array<number>) => number){}',
'function foo():number{}',
'function foo():() => void{}',
'function foo():(_:bool) => number{}',
'function foo():(_?:bool) => number{}',
'function foo(): {} {}',
'function foo<T>() {}',
'function foo<T: Foo>() {}',
'function foo<T,S>() {}',
'a=function<T,S>() {}',
'a={set fooProp(value:number){}}',
'a={set fooProp(value:number): void{}}',
'a={get fooProp(): number {}}',
'class Foo {set fooProp(value:number){}}',
'class Foo {set fooProp(value:number): void{}}',
'class Foo {get fooProp(): number{}}',
'var numVal:number;',
'var numVal:number = otherNumVal;',
'var a: {numVal: number};',
'var a: {numVal: number;};',
'var a: {numVal: number; [indexer: string]: number};',
'var a: ?{numVal: number};',
'var a: {numVal: number; strVal: string}',
'var a: {subObj: {strVal: string}}',
'var a: {subObj: ?{strVal: string}}',
'var a: {param1: number; param2: string}',
'var a: {param1: number; param2?: string}',
'var a: {add(x:number, ...y:Array<string>): void}',
'var a: { id<T>(x: T): T; }',
'var a:Array<number> = [1, 2, 3]',
'a = class Foo<T> { }',
'a = class Foo<T> extends Bar<T> { }',
'class Foo<T> {}',
'class Foo<T: Bar> {}',
'class Foo<T> extends Bar<T> { }',
'class Foo<T> extends mixin(Bar) { }',
'class Foo<T> { bar<U>():number { return 42; }}',
'class Foo { "bar"<T>() { } }',
'function foo(requiredParam, optParam?) {}',
'class Foo { prop1:string; prop2:number; }',
'var x : number | string = 4;',
'class Array { concat(items:number | string) {}; }',
'var x : () => number | () => string = fn;',
'var x: typeof Y = Y;',
'var x: typeof Y | number = Y;',
'var {x}: {x: string; } = { x: "hello" };',
'var {x}: {x: string } = { x: "hello" };',
'var [x]: Array<string> = [ "hello" ];',
'function foo({x}: { x: string; }) {}',
'function foo([x]: Array<string>) {}',
'function foo(...rest: Array<number>) {}',
'(function (...rest: Array<number>) {})',
'((...rest: Array<number>) => rest)',
'var a: Map<string, Array<string> >',
'var a: Map<string, Array<string>>',
'var a: number[]',
'var a: ?string[]',
'var a: Promise<bool>[]',
'var a:(...rest:Array<number>) => number'
],
'Type Alias': [
'type FBID = number;',
'type Foo<T> = Bar<T>',
],
'Interfaces': [
'interface A {}',
'interface A extends B {}',
'interface A<T> extends B<T>, C<T> {}',
'interface A { foo: () => number; }',
'interface Dictionary { [index: string]: string; length: number; }',
'class Foo implements Bar {}',
'class Foo extends Bar implements Bat, Man<number> {}',
'class Foo extends class Bar implements Bat {} {}',
'class Foo extends class Bar implements Bat {} implements Man {}',
],
'Type Grouping': [
'var a: (number)',
'var a: (() => number) | () => string',
'var a: number & (string | bool)',
'var a: (typeof A)',
],
'XJS': [
'<a />',
'<n:a n:v />',
'<a n:foo="bar"> {value} <b><c /></b></a>',
'<a b={" "} c=" " d="&amp;" e="id=1&group=2" f="&#123456789" g="&#123*;" h="&#x;" />',
'<a\n/>',
'<日本語></日本語>',
'<AbC-def\n test="&#x0026;&#38;">\nbar\nbaz\r\n</AbC-def>',
'<a b={x ? <c /> : <d />} />',
'<a>{}</a>',
'<a>{/* this is a comment */}</a>',
'<div>@test content</div>',
'<div><br />7x invalid-js-identifier</div>',
'<LeftRight left=<a /> right=<b>monkeys</b> />',
'<a.b></a.b>',
'<a.b.c></a.b.c>',
'(<div />) < x;',
'<div {...props} />',
'<div {...props} post="attribute" />',
'<div pre="leading" pre2="attribute" {...props}></div>',
'<a> </a>',
],
'Call Properties': [
'var a : { (): number }',
'var a : { (): number; }',
'var a : { (): number; y: string; (x: string): string }',
'var a : { <T>(x: T): number; }',
'interface A { (): number; }',
],
'String Literal Types': [
'function createElement(tagName: "div"): HTMLDivElement {}',
'function createElement(tagName: \'div\'): HTMLDivElement {}',
],
'Qualified Generic Type': [
'var a : A.B',
'var a : A.B.C',
'var a : A.B<T>',
'var a : typeof A.B<T>',
],
'Declare Statements': [
'declare var foo',
'declare var foo;',
'declare function foo(): void',
'declare function foo(): void;',
'declare function foo<T>(): void;',
'declare function foo(x: number, y: string): void;',
'declare class A {}',
'declare class A<T> extends B<T> { x: number }',
'declare class A { static foo(): number; static x : string }',
'declare class A { static [ indexer: number]: string }',
'declare class A { static () : number }',
],
'Declare Module': [
'declare module A {}',
'declare module "./a/b.js" {}',
'declare module A { declare var x: number; }',
'declare module A { declare function foo(): number; }',
'declare module A { declare class B { foo(): number; } }',
],
'Typecasts': [
'(xxx: number)',
'({xxx: 0, yyy: "hey"}: {xxx: number; yyy: string})',
// distinguish between function type params and typecasts
'((xxx) => xxx + 1: (xxx: number) => number)',
// parens disambiguate groups from casts
'((xxx: number), (yyy: string))',
'((xxx: any): number)',
],
};
} else {
require('mock-modules').autoMockOff();
var tests = require("./gen/type-syntax-test.rec.js");
var jstransform = require('../../src/jstransform');
var visitors = require('../type-syntax').visitorList;
describe('transforms match expectations', function () {
for (var section in tests) {
for (var test in tests[section]) {
it('transforms "'+test+'"', function (section, test) { return function () {
var transformed = jstransform.transform(visitors, test).code;
expect(transformed).toBe(tests[section][test].transformed);
}; } (section, test));
it('evals "'+test+'"', function (section, test) { return function () {
var evalResult;
var transformed = jstransform.transform(visitors, test).code;
try {
eval(transformed);
evalResult = "No error";
} catch (e) {
evalResult = e.message;
}
expect(evalResult).toBe(tests[section][test].eval);
}; } (section, test));
}
}
});
}

Some files were not shown because too many files have changed in this diff Show More