flow-remove-types 1.0.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ BSD License
2
+
3
+ For flow-remove-types software
4
+
5
+ Copyright (c) 2014-2016, Facebook, Inc. All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without modification,
8
+ are permitted provided that the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ * Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ * Neither the name Facebook nor the names of its contributors may be used to
18
+ endorse or promote products derived from this software without specific
19
+ prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/PATENTS ADDED
@@ -0,0 +1,11 @@
1
+ Additional Grant of Patent Rights Version 2
2
+
3
+ "Software" means the flow-remove-types software distributed by Facebook, Inc.
4
+
5
+ Facebook, Inc. (“Facebook”) hereby grants to each recipient of the Software (“you”) a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (subject to the termination provision below) license under any Necessary Claims, to make, have made, use, sell, offer to sell, import, and otherwise transfer the Software. For avoidance of doubt, no license is granted under Facebook’s rights in any patent claims that are infringed by (i) modifications to the Software made by you or any third party or (ii) the Software in combination with any software or other technology.
6
+
7
+ The license granted hereunder will terminate, automatically and without notice, if you (or any of your subsidiaries, corporate affiliates or agents) initiate directly or indirectly, or take a direct financial interest in, any Patent Assertion: (i) against Facebook or any of its subsidiaries or corporate affiliates, (ii) against any party if such Patent Assertion arises in whole or in part from any software, technology, product or service of Facebook or any of its subsidiaries or corporate affiliates, or (iii) against any party relating to the Software. Notwithstanding the foregoing, if Facebook or any of its subsidiaries or corporate affiliates files a lawsuit alleging patent infringement against you in the first instance, and you respond by filing a patent infringement counterclaim in that lawsuit against that party that is unrelated to the Software, the license granted hereunder will not terminate under section (i) of this paragraph due to such counterclaim.
8
+
9
+ A “Necessary Claim” is a claim of a patent owned by Facebook that is necessarily infringed by the Software standing alone.
10
+
11
+ A “Patent Assertion” is any lawsuit or other action alleging direct, indirect, or contributory infringement or inducement to infringe any patent, including a cross-claim or counterclaim.
package/README.md CHANGED
@@ -2,7 +2,7 @@ flow-remove-types
2
2
  =================
3
3
 
4
4
  [![npm](https://img.shields.io/npm/v/flow-remove-types.svg?maxAge=86400)](https://www.npmjs.com/package/flow-remove-types)
5
- [![Build Status](https://img.shields.io/travis/leebyron/flow-remove-types.svg?style=flat&label=travis&branch=master)](https://travis-ci.org/leebyron/flow-remove-types)
5
+ [![Build Status](https://img.shields.io/travis/flowtype/flow-remove-types.svg?style=flat&label=travis&branch=master)](https://travis-ci.org/flowtype/flow-remove-types)
6
6
 
7
7
  Turn your JavaScript with [Flow](https://flowtype.org/) type annotations into
8
8
  standard JavaScript in an instant with no configuration and minimal setup.
@@ -46,16 +46,24 @@ var fs = require('fs');
46
46
 
47
47
  var input = fs.readFileSync('input.js', 'utf8');
48
48
  var output = flowRemoveTypes(input);
49
- fs.writeFileSync('output.js', output);
49
+ fs.writeFileSync('output.js', output.toString());
50
50
  ```
51
51
 
52
52
 
53
53
  ## Use in Build Systems:
54
54
 
55
- **Rollup**: [`rollup-plugin-flow`](https://github.com/leebyron/rollup-plugin-flow)
55
+ **Rollup:** [`rollup-plugin-flow`](https://github.com/leebyron/rollup-plugin-flow)
56
56
 
57
57
  **Browserify:** [`unflowify`](https://github.com/leebyron/unflowify)
58
58
 
59
+ **Webpack:** [`remove-flow-types-loader`](https://github.com/conorhastings/remove-flow-types-loader)
60
+
61
+
62
+ ## Use with existing development tools
63
+
64
+ * ESLint: [eslint-plugin-flowtype](https://github.com/gajus/eslint-plugin-flowtype)
65
+ * Mocha: `mocha -r flow-remove-types/register`
66
+
59
67
 
60
68
  ## Use `flow-node`
61
69
 
@@ -74,13 +82,33 @@ undefined
74
82
  ## Use the require hook
75
83
 
76
84
  Using the require hook allows you to automatically compile files on the fly when
77
- requiring in node:
85
+ requiring in node, useful during development:
78
86
 
79
87
  ```js
80
88
  require('flow-remove-types/register')
81
89
  require('./some-module-with-flow-type-syntax')
82
90
  ```
83
91
 
92
+ You can also provide options to the require hook:
93
+
94
+ ```js
95
+ // Transforms all files, not just those with a "@flow" comment.
96
+ require('flow-remove-types/register')({ all: true })
97
+ ```
98
+
99
+ Use options to define exactly which files to `includes` or `excludes` with regular
100
+ expressions. All files are included by default except those found in the
101
+ `node_modules` folder, which is excluded by default.
102
+
103
+ ```js
104
+ require('flow-remove-types/register')({ includes: /\/custom_path\// })
105
+ ```
106
+
107
+ > #### Don't use the require hook in packages distributed on NPM
108
+ > As always, don't forget to use `flow-remove-types` to compile files before distributing
109
+ > your code on npm, as using the require hook affects the whole runtime and not
110
+ > just your module and may hurt the runtime performance of code that includes it.
111
+
84
112
 
85
113
  ## Dead-Simple Transforms
86
114
 
@@ -138,6 +166,27 @@ export class MyClass extends SomeClass {
138
166
  }
139
167
  ```
140
168
 
169
+ ### Pretty Transform
170
+
171
+ Rather not have the whitespace? Pass the `--pretty` flag to remove the whitespace.
172
+
173
+ ```
174
+ flow-remove-types --pretty --sourcemaps source.js
175
+ ```
176
+
177
+ Or using the JS API:
178
+
179
+ ```js
180
+ var flowRemoveTypes = require('flow-remove-types');
181
+ var fs = require('fs');
182
+
183
+ var input = fs.readFileSync('input.js', 'utf8');
184
+ var output = flowRemoveTypes(input, { pretty: true });
185
+ fs.writeFileSync('output.js', output.toString());
186
+ var sourceMap = output.generateMap();
187
+ fs.writeFileSync('output.js.map', JSON.stringify(sourceMap));
188
+ ```
189
+
141
190
 
142
191
  ## Performance
143
192
 
package/flow-node CHANGED
@@ -7,8 +7,7 @@ var repl = require('repl');
7
7
  var util = require('util');
8
8
  var vm = require('vm');
9
9
 
10
- var removeTypes = require('./index');
11
- require('./register');
10
+ var flowRemoveTypes = require('./index');
12
11
 
13
12
  var usage = 'Usage: flow-node [options] [ script.js ] [arguments] \n' +
14
13
 
@@ -18,11 +17,13 @@ var usage = 'Usage: flow-node [options] [ script.js ] [arguments] \n' +
18
17
  ' -e, --eval script Evaluate script\n' +
19
18
  ' -p, --print script Evaluate script and print result\n' +
20
19
  ' -c, --check Syntax check script without executing\n';
20
+ ' -a, --all Interpret all files as flow-typed, not just those with a @flow comment\n';
21
21
 
22
22
  // Collect arguments
23
23
  var evalScript;
24
24
  var printScript;
25
25
  var checkSource;
26
+ var all;
26
27
  var source;
27
28
  var i = 2;
28
29
  while (i < process.argv.length) {
@@ -47,12 +48,16 @@ while (i < process.argv.length) {
47
48
  }
48
49
  } else if (arg === '-c' || arg === '--check') {
49
50
  checkSource = true;
51
+ } else if (arg === '-a' || arg === '--all') {
52
+ all = true;
50
53
  } else if (arg[0] !== '-') {
51
54
  source = arg;
52
55
  break;
53
56
  }
54
57
  }
55
58
 
59
+ require('./register')({ all: all });
60
+
56
61
  // Evaluate and possibly also print a script.
57
62
  if (evalScript || printScript) {
58
63
  global.__filename = '[eval]';
@@ -64,7 +69,7 @@ if (evalScript || printScript) {
64
69
  global.module = evalModule;
65
70
  global.require = evalModule.require.bind(evalModule);
66
71
  var result = vm.runInThisContext(
67
- removeTypes(evalScript || printScript, { checkPragma: false }),
72
+ flowRemoveTypes(evalScript || printScript, { all: true }).toString(),
68
73
  { filename: global.__filename }
69
74
  );
70
75
 
@@ -76,7 +81,7 @@ if (evalScript || printScript) {
76
81
  } else if (source && checkSource) {
77
82
  var code = fs.readFileSync(source, 'utf8');
78
83
  try {
79
- removeTypes(code);
84
+ flowRemoveTypes(code, { all: all });
80
85
  } catch (error) {
81
86
  var lines = code.split(/\r\n?|\n|\u2028|\u2029/);
82
87
  process.stdout.write(source + ':' + error.loc.line + '\n');
@@ -108,7 +113,7 @@ if (evalScript || printScript) {
108
113
  var error;
109
114
  var result;
110
115
  try {
111
- var runCode = removeTypes(code, { checkPragma: false });
116
+ var runCode = flowRemoveTypes(code, { all: true }).toString();
112
117
  try {
113
118
  result = vm.runInThisContext(runCode, { filename: filename });
114
119
  } catch (runError) {
package/flow-remove-types CHANGED
@@ -14,6 +14,11 @@ var usage = 'Usage: flow-remove-types [options] [sources] \n' +
14
14
  ' -x, --extensions File extensions to transform\n' +
15
15
  ' -o, --out-file The file path to write transformed file to\n' +
16
16
  ' -d, --out-dir The directory path to write transformed files within\n' +
17
+ ' -a, --all Transform all files, not just those with a @flow comment\n' +
18
+ ' -p, --pretty Remove flow types without replacing with spaces, \n' +
19
+ ' producing prettier output but may require using source maps\n' +
20
+ ' -m, --sourcemaps Also output source map files. Optionally pass "inline"\n' +
21
+ ' -q, --quiet Does not produce any output concerning successful progress.\n' +
17
22
 
18
23
  '\nExamples:\n' +
19
24
 
@@ -26,14 +31,15 @@ var usage = 'Usage: flow-remove-types [options] [sources] \n' +
26
31
  '\nTransform files in directory:\n' +
27
32
  ' flow-remove-types --out-dir out/ indir/\n' +
28
33
 
34
+ '\nTransform files with source maps:\n' +
35
+ ' flow-remove-types --out-dir out/ indir/ --sourcemaps\n' +
36
+
37
+ '\nTransform files with inline source maps:\n' +
38
+ ' flow-remove-types --out-dir out/ indir/ --sourcemaps inline\n' +
39
+
29
40
  '\nTransform stdin:\n' +
30
41
  ' cat input.js | flow-remove-types > output.js\n';
31
42
 
32
- function error(msg) {
33
- process.stderr.write('\n\033[31m ' + msg + '\033[0m\n\n');
34
- process.exit(1);
35
- }
36
-
37
43
  var _memo = {};
38
44
 
39
45
  function mkdirp(dirpath) {
@@ -65,6 +71,11 @@ var ignore = /node_modules/;
65
71
  var extensions = [ '.js', '.jsx', '.flow', '.es6' ];
66
72
  var outDir;
67
73
  var outFile;
74
+ var all;
75
+ var pretty;
76
+ var sourceMaps;
77
+ var inlineSourceMaps;
78
+ var quiet;
68
79
  var sources = [];
69
80
  var i = 2;
70
81
  while (i < process.argv.length) {
@@ -83,11 +94,34 @@ while (i < process.argv.length) {
83
94
  outFile = process.argv[i++];
84
95
  } else if (arg === '-d' || arg === '--out-dir') {
85
96
  outDir = process.argv[i++];
97
+ } else if (arg === '-a' || arg === '--all') {
98
+ all = true;
99
+ } else if (arg === '-p' || arg === '--pretty') {
100
+ pretty = true;
101
+ } else if (arg === '-m' || arg === '--sourcemaps') {
102
+ sourceMaps = true;
103
+ if (process.argv[i] === 'inline') {
104
+ inlineSourceMaps = true;
105
+ i++;
106
+ }
107
+ } else if (arg === '-q' || arg === '--quiet') {
108
+ quiet = true;
86
109
  } else {
87
110
  sources.push(arg);
88
111
  }
89
112
  }
90
113
 
114
+ function info(msg) {
115
+ if (!quiet) {
116
+ process.stderr.write(msg);
117
+ }
118
+ }
119
+
120
+ function error(msg) {
121
+ process.stderr.write('\n\033[31m ' + msg + '\033[0m\n\n');
122
+ process.exit(1);
123
+ }
124
+
91
125
  // Validate arguments
92
126
  if (outDir && outFile) {
93
127
  error('Only specify one of --out-dir or --out-file');
@@ -97,6 +131,10 @@ if (outDir && sources.length === 0) {
97
131
  error('Must specify files when providing --out-dir');
98
132
  }
99
133
 
134
+ if (!outDir && !outFile && sourceMaps && !inlineSourceMaps) {
135
+ error('Must specify either an output path or inline source maps');
136
+ }
137
+
100
138
  // Ensure all sources exist
101
139
  for (var i = 0; i < sources.length; i++) {
102
140
  try {
@@ -116,12 +154,7 @@ if (sources.length === 0) {
116
154
  process.stdin.resume();
117
155
  process.stdin.on('data', function (str) { content += str; });
118
156
  process.stdin.on('end', function () {
119
- var result = transformSource(content, '<stdin>');
120
- if (outFile) {
121
- fs.writeFileSync(outFile, result);
122
- } else {
123
- process.stdout.write(result);
124
- }
157
+ transformAndOutput(content, outFile);
125
158
  });
126
159
  return;
127
160
  }
@@ -145,24 +178,60 @@ for (var i = 0; i < sources.length; i++) {
145
178
  }
146
179
  }
147
180
  } else if (stat.isFile() && extensions.indexOf(path.extname(source)) !== -1) {
148
- var content = fs.readFileSync(source, 'utf8');
149
- var result = transformSource(content, source);
150
181
  if (outDir) {
151
182
  outFile = path.join(outDir, isDirSource ? path.relative(sources[0], source) : source);
152
183
  mkdirp(path.dirname(outFile));
153
184
  }
154
- if (outFile) {
155
- fs.writeFileSync(outFile, result);
156
- process.stdout.write(source + '\n \u21B3 \033[32m' + outFile + '\033[0m\n');
185
+ var content = fs.readFileSync(source, 'utf8');
186
+ transformAndOutput(content, outFile, source);
187
+ }
188
+ }
189
+
190
+ function transformAndOutput(content, outFile, source) {
191
+ var fileName = source || '<stdin>';
192
+ var result = transformSource(content, fileName);
193
+ var code = result.toString();
194
+
195
+ if (sourceMaps) {
196
+ var map = result.generateMap();
197
+ delete map.file;
198
+ map.sources[0] = fileName;
199
+
200
+ if (source) {
201
+ delete map.sourcesContent;
202
+ if (outFile) {
203
+ map.sources[0] = path.join(path.relative(path.dirname(outFile), path.dirname(source)), path.basename(source));
204
+ }
157
205
  } else {
158
- process.stdout.write(result);
206
+ map.sourcesContent[0] = content;
207
+ }
208
+
209
+ code += '\n//# sourceMappingURL=' + (inlineSourceMaps ?
210
+ 'data:application/json;charset=utf-8;base64,' + btoa(JSON.stringify(map)) :
211
+ path.basename(outFile) + '.map'
212
+ ) + '\n';
213
+ }
214
+
215
+ if (outFile) {
216
+ fs.writeFileSync(outFile, code);
217
+ info(fileName + '\n \u21B3 \033[32m' + outFile + '\033[0m\n');
218
+ if (sourceMaps && !inlineSourceMaps) {
219
+ var mapOutFile = outFile + '.map';
220
+ fs.writeFileSync(mapOutFile, JSON.stringify(map) + '\n');
221
+ info('\033[2m \u21B3 \033[32m' + mapOutFile + '\033[0m\n');
159
222
  }
223
+ } else {
224
+ process.stdout.write(code);
160
225
  }
161
226
  }
162
227
 
228
+ function btoa(str) {
229
+ return (Buffer.from ? Buffer.from(str) : new Buffer(str)).toString('base64');
230
+ }
231
+
163
232
  function transformSource(content, filepath) {
164
233
  try {
165
- return flowRemoveTypes(content);
234
+ return flowRemoveTypes(content, { all: all, pretty: pretty });
166
235
  } catch (error) {
167
236
  if (error.loc) {
168
237
  var line = error.loc.line - 1;
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  var babylon = require('babylon');
2
+ var vlq = require('vlq');
2
3
 
3
4
  /**
4
5
  * Given a string JavaScript source which contains Flow types, return a string
@@ -6,23 +7,57 @@ var babylon = require('babylon');
6
7
  *
7
8
  * Options:
8
9
  *
9
- * - checkPragma: (default: true) looks for an @flow pragma before parsing.
10
+ * - all: (default: false)
11
+ * If true, bypasses looking for an @flow pragma comment before parsing.
12
+ *
13
+ * - pretty: (default: false)
14
+ * If true, removes types completely rather than replacing with spaces.
15
+ * This may require using source maps.
16
+ *
17
+ * Returns an object with two methods:
18
+ *
19
+ * - .toString()
20
+ * Returns the transformed source code.
21
+ *
22
+ * - .generateMap()
23
+ * Returns a v3 source map.
10
24
  */
11
25
  module.exports = function flowRemoveTypes(source, options) {
26
+ // Options
27
+ var all = Boolean(options && options.all);
28
+ if (options && options.checkPragma) {
29
+ throw new Error(
30
+ 'flow-remove-types: the "checkPragma" option has been replaced by "all".'
31
+ );
32
+ }
33
+
12
34
  // If there's no @flow or @noflow flag, then expect no annotation.
13
- var pragmaStart = source.indexOf('@flow');
14
- var pragmaEnd = pragmaStart + 5;
35
+ var pragmaStart = source.indexOf('@' + 'flow');
36
+ var pragmaSize = 5;
15
37
  if (pragmaStart === -1) {
16
- pragmaStart = source.indexOf('@noflow');
17
- pragmaEnd = pragmaStart + 7;
18
- if (pragmaStart === -1 && !(options && options.checkPragma === false)) {
19
- return source;
38
+ pragmaStart = source.indexOf('@' + 'noflow');
39
+ pragmaSize = 7;
40
+ if (pragmaStart === -1 && !all) {
41
+ return resultPrinter(options, source);
20
42
  }
21
43
  }
22
44
 
23
- var removedNodes = pragmaStart === -1 ?
24
- [] :
25
- [ { start: pragmaStart, end: pragmaEnd } ];
45
+ var removedNodes = [];
46
+
47
+ // Remove the flow pragma.
48
+ if (pragmaStart !== -1) {
49
+ var prePragmaLines = source.slice(0, pragmaStart).split('\n');
50
+ var pragmaLine = prePragmaLines.length;
51
+ var pragmaCol = prePragmaLines[pragmaLine - 1].length;
52
+ removedNodes.push({
53
+ start: pragmaStart,
54
+ end: pragmaStart + pragmaSize,
55
+ loc: {
56
+ start: { line: pragmaLine, column: pragmaCol },
57
+ end: { line: pragmaLine, column: pragmaCol + pragmaSize }
58
+ },
59
+ })
60
+ }
26
61
 
27
62
  // Babylon is one of the sources of truth for Flow syntax. This parse
28
63
  // configuration is intended to be as permissive as possible.
@@ -34,33 +69,61 @@ module.exports = function flowRemoveTypes(source, options) {
34
69
  plugins: [ '*', 'jsx', 'flow' ],
35
70
  });
36
71
 
37
- visit(ast, removedNodes, removeFlowVisitor);
72
+ var context = {
73
+ ast: ast,
74
+ source: source,
75
+ removedNodes: removedNodes,
76
+ pretty: Boolean(options && options.pretty)
77
+ };
38
78
 
39
- if (removedNodes.length === 0) {
40
- return source;
41
- }
79
+ visit(ast, context, removeFlowVisitor);
42
80
 
43
- var result = '';
44
- var lastPos = 0;
81
+ return resultPrinter(options, source, removedNodes);
82
+ }
45
83
 
46
- // Step through the removed nodes, building up the resulting string.
47
- for (var i = 0; i < removedNodes.length; i++) {
48
- var node = removedNodes[i];
49
- result += source.slice(lastPos, node.start);
50
- var toReplace = source.slice(node.start, node.end);
51
- lastPos = node.end;
52
- if (!node.loc || node.loc.start.line === node.loc.end.line) {
53
- result += space(toReplace.length);
54
- } else {
55
- var toReplaceLines = toReplace.split(LINE_RX);
56
- result += space(toReplaceLines[0].length);
57
- for (var j = 1; j < toReplaceLines.length; j += 2) {
58
- result += toReplaceLines[j] + space(toReplaceLines[j + 1].length);
84
+ function resultPrinter(options, source, removedNodes) {
85
+ // Options
86
+ var pretty = Boolean(options && options.pretty);
87
+
88
+ return {
89
+ toString: function () {
90
+ if (!removedNodes || removedNodes.length === 0) {
91
+ return source;
92
+ }
93
+
94
+ var result = '';
95
+ var lastPos = 0;
96
+
97
+ // Step through the removed nodes, building up the resulting string.
98
+ for (var i = 0; i < removedNodes.length; i++) {
99
+ var node = removedNodes[i];
100
+ result += source.slice(lastPos, node.start);
101
+ lastPos = node.end;
102
+ if (!pretty) {
103
+ var toReplace = source.slice(node.start, node.end);
104
+ if (!node.loc || node.loc.start.line === node.loc.end.line) {
105
+ result += space(toReplace.length);
106
+ } else {
107
+ var toReplaceLines = toReplace.split(LINE_RX);
108
+ result += space(toReplaceLines[0].length);
109
+ for (var j = 1; j < toReplaceLines.length; j += 2) {
110
+ result += toReplaceLines[j] + space(toReplaceLines[j + 1].length);
111
+ }
112
+ }
113
+ }
59
114
  }
115
+
116
+ return result += source.slice(lastPos);
117
+ },
118
+ generateMap: function () {
119
+ return {
120
+ version: 3,
121
+ sources: [ 'source.js' ],
122
+ names: [],
123
+ mappings: pretty ? generateSourceMappings(removedNodes) : ''
124
+ };
60
125
  }
61
126
  }
62
-
63
- return result += source.slice(lastPos);
64
127
  }
65
128
 
66
129
  var LINE_RX = /(\r\n?|\n|\u2028|\u2029)/;
@@ -83,39 +146,56 @@ var removeFlowVisitor = {
83
146
  ClassDeclaration: removeImplementedInterfaces,
84
147
  ClassExpression: removeImplementedInterfaces,
85
148
 
86
- Identifier: function (removedNodes, node, ast) {
149
+ Identifier: function (context, node, ast) {
87
150
  if (node.optional) {
88
151
  // Find the optional token.
89
152
  var idx = findTokenIndex(ast.tokens, node.start);
90
153
  do {
91
154
  idx++;
92
155
  } while (ast.tokens[idx].type.label !== '?');
93
- removeNode(removedNodes, ast.tokens[idx]);
156
+ removeNode(context, ast.tokens[idx]);
94
157
  }
95
158
  },
96
159
 
97
- ClassProperty: function (removedNodes, node) {
160
+ ClassProperty: function (context, node) {
98
161
  if (!node.value) {
99
- return removeNode(removedNodes, node)
162
+ return removeNode(context, node)
100
163
  }
101
164
  },
102
165
 
103
- ExportNamedDeclaration: function (removedNodes, node) {
104
- if (node.exportKind === 'type') {
105
- return removeNode(removedNodes, node);
166
+ ExportNamedDeclaration: function (context, node) {
167
+ if (node.exportKind === 'type' || node.exportKind === 'typeof') {
168
+ return removeNode(context, node);
106
169
  }
107
170
  },
108
171
 
109
- ImportDeclaration: function (removedNodes, node) {
110
- if (node.importKind === 'type') {
111
- return removeNode(removedNodes, node);
172
+ ImportDeclaration: function (context, node) {
173
+ if (node.importKind === 'type' || node.importKind === 'typeof') {
174
+ return removeNode(context, node);
175
+ }
176
+ },
177
+
178
+ ImportSpecifier: function(context, node) {
179
+ if (node.importKind === 'type' || node.importKind === 'typeof') {
180
+ removeNode(context, node);
181
+
182
+ // Remove trailing comma
183
+ var ast = context.ast;
184
+ var idx = findTokenIndex(ast.tokens, node.end);
185
+ while (isComment(ast.tokens[idx])) {
186
+ idx++;
187
+ }
188
+ if (ast.tokens[idx].type.label === ',') {
189
+ removeNode(context, ast.tokens[idx]);
190
+ }
191
+ return false;
112
192
  }
113
193
  }
114
194
  };
115
195
 
116
196
  // If this class declaration or expression implements interfaces, remove
117
197
  // the associated tokens.
118
- function removeImplementedInterfaces(removedNodes, node, ast) {
198
+ function removeImplementedInterfaces(context, node, ast) {
119
199
  if (node.implements && node.implements.length > 0) {
120
200
  var first = node.implements[0];
121
201
  var last = node.implements[node.implements.length - 1];
@@ -126,9 +206,8 @@ function removeImplementedInterfaces(removedNodes, node, ast) {
126
206
 
127
207
  var lastIdx = findTokenIndex(ast.tokens, last.start);
128
208
  do {
129
- if (ast.tokens[idx].type !== 'CommentBlock' &&
130
- ast.tokens[idx].type !== 'CommentLine') {
131
- removeNode(removedNodes, ast.tokens[idx]);
209
+ if (!isComment(ast.tokens[idx])) {
210
+ removeNode(context, ast.tokens[idx]);
132
211
  }
133
212
  } while (idx++ !== lastIdx);
134
213
  }
@@ -136,17 +215,131 @@ function removeImplementedInterfaces(removedNodes, node, ast) {
136
215
 
137
216
  // Append node to the list of removed nodes, ensuring the order of the nodes
138
217
  // in the list.
139
- function removeNode(removedNodes, node) {
218
+ function removeNode(context, node) {
219
+ var removedNodes = context.removedNodes;
140
220
  var length = removedNodes.length;
141
221
  var index = length;
222
+
223
+ // Check for line's leading and trailing space to be removed.
224
+ var spaceNode = context.pretty ? getLeadingSpaceNode(context, node) : null;
225
+ var lineNode = context.pretty ? getTrailingLineNode(context, node) : null;
226
+
142
227
  while (index > 0 && removedNodes[index - 1].end > node.start) {
143
228
  index--;
144
229
  }
230
+
145
231
  if (index === length) {
232
+ if (spaceNode) {
233
+ removedNodes.push(spaceNode);
234
+ }
146
235
  removedNodes.push(node);
236
+ if (lineNode) {
237
+ removedNodes.push(lineNode);
238
+ }
147
239
  } else {
148
- removedNodes.splice(index, 0, node);
240
+ if (lineNode) {
241
+ if (spaceNode) {
242
+ removedNodes.splice(index, 0, spaceNode, node, lineNode);
243
+ } else {
244
+ removedNodes.splice(index, 0, node, lineNode);
245
+ }
246
+ } else if (spaceNode) {
247
+ removedNodes.splice(index, 0, spaceNode, node);
248
+ } else {
249
+ removedNodes.splice(index, 0, node);
250
+ }
251
+ }
252
+
253
+ return false;
254
+ }
255
+
256
+ function getLeadingSpaceNode(context, node) {
257
+ var source = context.source;
258
+ var end = node.start;
259
+ var start = end;
260
+ while (source[start - 1] === ' ' || source[start - 1] === '\t') {
261
+ start--;
262
+ }
263
+ if (start !== end) {
264
+ return {
265
+ start: start,
266
+ end: end,
267
+ loc: { start: node.loc.start, end: node.loc.start }
268
+ };
269
+ }
270
+ }
271
+
272
+ function getTrailingLineNode(context, node) {
273
+ var source = context.source;
274
+ var start = node.end;
275
+ var end = start;
276
+ while (source[end] === ' ' || source[end] === '\t') {
277
+ end++;
278
+ }
279
+
280
+ // Remove all space including the line break if this token was alone on the line.
281
+ if (source[end] === '\n' || source[end] === '\r') {
282
+ if (source[end] === '\r' && source[end + 1] === '\n') {
283
+ end++;
284
+ }
285
+ end++;
286
+
287
+ if (isLastNodeRemovedFromLine(context, node)) {
288
+ return {
289
+ start: start,
290
+ end: end,
291
+ loc: { start: node.loc.end, end: node.loc.end }
292
+ };
293
+ }
294
+ }
295
+ }
296
+
297
+ // Returns true if node is the last to be removed from a line.
298
+ function isLastNodeRemovedFromLine(context, node) {
299
+ var tokens = context.ast.tokens;
300
+ var priorTokenIdx = findTokenIndex(tokens, node.start) - 1;
301
+ var token = tokens[priorTokenIdx];
302
+ var line = node.loc.end.line;
303
+
304
+ // Find previous token that was not removed on the same line.
305
+ while (priorTokenIdx >= 0 &&
306
+ token.loc.end.line === line &&
307
+ isRemovedToken(context, token)) {
308
+ token = tokens[--priorTokenIdx]
309
+ }
310
+
311
+ // If there's no prior token (start of file), or the prior token is on another
312
+ // line, this line must be fully removed.
313
+ return !token || token.loc.end.line !== line;
314
+ }
315
+
316
+ // Returns true if the provided token was previously marked as removed.
317
+ function isRemovedToken(context, token) {
318
+ var removedNodes = context.removedNodes;
319
+ var nodeIdx = removedNodes.length - 1;
320
+
321
+ // Find the last removed node which could possibly contain this token.
322
+ while (nodeIdx >= 0 && removedNodes[nodeIdx].start > token.start) {
323
+ nodeIdx--;
324
+ }
325
+
326
+ var node = removedNodes[nodeIdx];
327
+
328
+ // This token couldn't be removed if not contained within the removed node.
329
+ if (nodeIdx === -1 || node.end < token.end) {
330
+ return false;
331
+ }
332
+
333
+ // Iterate through the tokens contained by the removed node to find a match.
334
+ var tokens = context.ast.tokens;
335
+ var tokenIdx = findTokenIndex(tokens, node.start);
336
+ while (tokens[tokenIdx].end <= node.end) {
337
+ if (token === tokens[tokenIdx]) {
338
+ return true;
339
+ }
340
+ tokenIdx++;
149
341
  }
342
+
150
343
  return false;
151
344
  }
152
345
 
@@ -204,6 +397,11 @@ function findTokenIndex(tokens, offset) {
204
397
  return ptr;
205
398
  }
206
399
 
400
+ // True if the provided token is a comment.
401
+ function isComment(token) {
402
+ return token.type === 'CommentBlock' || token.type === 'CommentLine';
403
+ }
404
+
207
405
  // Produce a string full of space characters of a given size.
208
406
  function space(size) {
209
407
  var sp = ' '
@@ -221,3 +419,37 @@ function space(size) {
221
419
  }
222
420
  return result;
223
421
  }
422
+
423
+ // Generate a source map when *removing* nodes rather than replacing them
424
+ // with spaces.
425
+ function generateSourceMappings(removedNodes) {
426
+ var mappings = '';
427
+ if (!removedNodes || removedNodes.length === '') {
428
+ return mappings;
429
+ }
430
+
431
+ var end = { line: 1, column: 0 };
432
+
433
+ for (var i = 0; i < removedNodes.length; i++) {
434
+ var start = removedNodes[i].loc.start;
435
+ var lineDiff = start.line - end.line;
436
+ var columnDiff = start.column - end.column;
437
+ if (lineDiff) {
438
+ for (var l = 0; l !== lineDiff; l++) {
439
+ mappings += ';';
440
+ }
441
+ mappings += vlq.encode([ start.column, 0, lineDiff, columnDiff ]);
442
+ } else if (columnDiff) {
443
+ if (i) {
444
+ mappings += ',';
445
+ }
446
+ mappings += vlq.encode([ columnDiff, 0, lineDiff, columnDiff ]);
447
+ }
448
+
449
+ end = removedNodes[i].loc.end;
450
+ mappings += ',';
451
+ mappings += vlq.encode([ 0, 0, end.line - start.line, end.column - start.column ]);
452
+ }
453
+
454
+ return mappings;
455
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flow-remove-types",
3
- "version": "1.0.5",
3
+ "version": "1.2.0",
4
4
  "description": "Removes Flow type annotations from JavaScript files with speed and simplicity.",
5
5
  "author": "Lee Byron <lee@leebyron.com> (http://leebyron.com/)",
6
6
  "license": "BSD-3-Clause",
@@ -9,17 +9,25 @@
9
9
  "flow-remove-types": "./flow-remove-types",
10
10
  "flow-node": "./flow-node"
11
11
  },
12
- "homepage": "https://github.com/leebyron/flow-remove-types",
12
+ "files": [
13
+ "index.js",
14
+ "register.js",
15
+ "flow-remove-types",
16
+ "flow-node",
17
+ "LICENSE",
18
+ "PATENTS"
19
+ ],
20
+ "homepage": "https://github.com/flowtype/flow-remove-types",
13
21
  "bugs": {
14
- "url": "https://github.com/leebyron/flow-remove-types/issues"
22
+ "url": "https://github.com/flowtype/flow-remove-types/issues"
15
23
  },
16
24
  "repository": {
17
25
  "type": "git",
18
- "url": "http://github.com/leebyron/flow-remove-types.git"
26
+ "url": "http://github.com/flowtype/flow-remove-types.git"
19
27
  },
20
28
  "scripts": {
21
- "test": "DIFF=$(./flow-remove-types test/source.js | diff test/expected.js -); if [ -n \"$DIFF\" ]; then echo \"$DIFF\"; exit 1; fi; RES=$(node -e 'require(\"./register\");require(\"./test/test-node-module.js\")'); if [ \"$RES\" != 42 ]; then echo 'Node register hook failed'; exit 1; fi; FLOW_NODE=$(./flow-node ./test/test-node-module.js); if [ \"$FLOW_NODE\" != 42 ]; then echo 'flow-node failed'; exit 1; fi;",
22
- "test-update": "./flow-remove-types test/source.js > test/expected.js"
29
+ "test": "./test.sh",
30
+ "test-update": "./test-update.sh"
23
31
  },
24
32
  "keywords": [
25
33
  "flow",
@@ -30,6 +38,7 @@
30
38
  "es6"
31
39
  ],
32
40
  "dependencies": {
33
- "babylon": "^6.8.4"
41
+ "babylon": "^6.15.0",
42
+ "vlq": "^0.2.1"
34
43
  }
35
44
  }
package/register.js CHANGED
@@ -1,13 +1,66 @@
1
- var Module = require('module');
2
- var removeTypes = require('./index');
3
-
4
- // Rather than use require.extensions, swizzle Module#_compile. Not only does
5
- // this typically leverage the existing behavior of require.extensions['.js'],
6
- // but allows for use alongside other "require extension hook" if necessary.
7
- var super_compile = Module.prototype._compile;
8
- Module.prototype._compile = function _compile(source, filename) {
9
- var transformedSource = filename.indexOf('node_modules/') === -1
10
- ? removeTypes(source)
11
- : source;
12
- super_compile.call(this, transformedSource, filename);
13
- };
1
+ var flowRemoveTypes = require('./index');
2
+
3
+ // Supported options:
4
+ //
5
+ // - all: Transform all files, not just those with a @flow comment.
6
+ // - includes: A Regexp/String to determine which files should be transformed.
7
+ // (alias: include)
8
+ // - excludes: A Regexp/String to determine which files should not be
9
+ // transformed, defaults to ignoring /node_modules/, provide null
10
+ // to exclude nothing. (alias: exclude)
11
+ var options;
12
+ module.exports = function setOptions(newOptions) {
13
+ options = newOptions;
14
+ }
15
+
16
+ // Swizzle Module#_compile on each applicable module instance.
17
+ // NOTE: if using alongside Babel or another require-hook which simply
18
+ // over-writes the require.extensions and does not continue execution, then
19
+ // this require hook must come after it. Encourage those module authors to call
20
+ // the prior loader in their require hooks.
21
+ var jsLoader = require.extensions['.js'];
22
+ var exts = [ '.js', '.jsx', '.flow', '.es6' ];
23
+ exts.forEach(function (ext) {
24
+ var superLoader = require.extensions[ext] || jsLoader;
25
+ require.extensions[ext] = function (module, filename) {
26
+ if (shouldTransform(filename, options)) {
27
+ var super_compile = module._compile;
28
+ module._compile = function _compile(code, filename) {
29
+ super_compile.call(this, flowRemoveTypes(code, options).toString(), filename);
30
+ };
31
+ }
32
+ superLoader(module, filename);
33
+ };
34
+ });
35
+
36
+ function shouldTransform(filename, options) {
37
+ var includes = options && regexpPattern(options.includes || options.include);
38
+ var excludes =
39
+ options && 'excludes' in options ? regexpPattern(options.excludes) :
40
+ options && 'exclude' in options ? regexpPattern(options.exclude) :
41
+ /\/node_modules\//;
42
+ return (!includes || includes.test(filename)) && !(excludes && excludes.test(filename));
43
+ }
44
+
45
+ // Given a null | string | RegExp | any, returns null | Regexp or throws a
46
+ // more helpful error.
47
+ function regexpPattern(pattern) {
48
+ if (!pattern) {
49
+ return pattern;
50
+ }
51
+ // A very simplified glob transform which allows passing legible strings like
52
+ // "myPath/*.js" instead of a harder to read RegExp like /\/myPath\/.*\.js/.
53
+ if (typeof pattern === 'string') {
54
+ pattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
55
+ if (pattern[0] !== '/') {
56
+ pattern = '/' + pattern;
57
+ }
58
+ return new RegExp(pattern);
59
+ }
60
+ if (typeof pattern.test === 'function') {
61
+ return pattern;
62
+ }
63
+ throw new Error(
64
+ 'flow-remove-types: includes and excludes must be RegExp or path strings. Got: ' + pattern
65
+ );
66
+ }
package/.npmignore DELETED
@@ -1,3 +0,0 @@
1
- .DS_Store
2
- npm-debug.log
3
- node_modules
package/.travis.yml DELETED
@@ -1,12 +0,0 @@
1
- language: node_js
2
-
3
- node_js:
4
- - "6"
5
- - "5"
6
- - "4"
7
- - "iojs"
8
- - "0.12"
9
- - "0.10"
10
-
11
- before_install:
12
- - npm config set spin false --global
package/bow.js DELETED
@@ -1,4 +0,0 @@
1
- var temp, onNeck, purchaseBowtie = function(){ return 'tie' };
2
-
3
- var inBag = purchaseBowtie()
4
- (temp = onNeck, onNeck = inBag, inBag = temp)
package/test/expected.js DELETED
@@ -1,132 +0,0 @@
1
- /* */
2
-
3
- // Regular import
4
- import { Something } from 'some-module';
5
-
6
- // Import types
7
-
8
-
9
- // Typed function
10
- async function test(x , y /*.*/ /*.*/ , z /*.*/ /*.*/ = 123) {
11
- // Typed expression
12
- return await (x );
13
- }
14
-
15
- // Interface
16
-
17
-
18
-
19
-
20
-
21
-
22
- // Exported interface
23
-
24
-
25
-
26
-
27
- // Interface extends
28
-
29
-
30
-
31
-
32
- // Implements interface
33
- class Bar extends Other /*.*/ {
34
- // Class Property with default value
35
- answer = 42;
36
-
37
- // Class Property
38
-
39
-
40
- method() {
41
- return;
42
- }
43
- }
44
-
45
- // Class expression implements interface
46
- var SomeClass = class Baz {
47
-
48
-
49
- method() {
50
- return;
51
- }
52
- };
53
-
54
- // Parametric class
55
- class Wrapper {
56
- get() {
57
- return this.value;
58
- }
59
-
60
- map () {
61
- // do something
62
- }
63
- }
64
-
65
- // Extends Parametric class
66
- class StringWrapper extends Wrapper {
67
- // ...
68
- }
69
-
70
- // Declare class
71
-
72
-
73
-
74
-
75
- // Declare funtion
76
-
77
-
78
- // Declare interface
79
-
80
-
81
-
82
-
83
- // Declare module
84
-
85
-
86
-
87
-
88
- // Declare type alias
89
-
90
-
91
-
92
-
93
-
94
- // Declare variable
95
-
96
-
97
- // Type alias
98
-
99
-
100
- // Export type
101
-
102
-
103
- // Regular export
104
- export { Wrapper };
105
-
106
- // Exported type alias
107
-
108
-
109
- // Object with types within
110
- var someObj = {
111
- objMethod() {
112
- // do nothing.
113
- }
114
- }
115
-
116
- // Example from README
117
- import SomeClass from 'some-module'
118
-
119
-
120
- export class MyClass extends SomeClass {
121
-
122
-
123
-
124
- constructor(value ) {
125
- this.value = value
126
- }
127
-
128
- get() {
129
- return this.value
130
- }
131
-
132
- }
package/test/source.js DELETED
@@ -1,132 +0,0 @@
1
- /* @flow */
2
-
3
- // Regular import
4
- import { Something } from 'some-module';
5
-
6
- // Import types
7
- import type { SomeType } from 'some-module';
8
-
9
- // Typed function
10
- async function test(x: Type, y /*.*/ ? /*.*/ , z /*.*/ ? /*.*/ : /*.*/ number = 123): string {
11
- // Typed expression
12
- return await (x: any);
13
- }
14
-
15
- // Interface
16
- interface Foo {
17
- prop: any;
18
-
19
- method(): mixed;
20
- }
21
-
22
- // Exported interface
23
- export interface IThing {
24
- exported: true;
25
- }
26
-
27
- // Interface extends
28
- interface SillyFoo extends Foo {
29
- silly: string;
30
- }
31
-
32
- // Implements interface
33
- class Bar extends Other implements /*.*/ Foo, ISomething {
34
- // Class Property with default value
35
- answer: number = 42;
36
-
37
- // Class Property
38
- prop: any;
39
-
40
- method(): mixed {
41
- return;
42
- }
43
- }
44
-
45
- // Class expression implements interface
46
- var SomeClass = class Baz implements Foo {
47
- prop: any;
48
-
49
- method(): mixed {
50
- return;
51
- }
52
- };
53
-
54
- // Parametric class
55
- class Wrapper<T> {
56
- get(): T {
57
- return this.value;
58
- }
59
-
60
- map<M>(): Wrapper<M> {
61
- // do something
62
- }
63
- }
64
-
65
- // Extends Parametric class
66
- class StringWrapper extends Wrapper<string> {
67
- // ...
68
- }
69
-
70
- // Declare class
71
- declare class Baz {
72
- method(): mixed;
73
- }
74
-
75
- // Declare funtion
76
- declare function someFunc(): void;
77
-
78
- // Declare interface
79
- declare interface ISomething {
80
- answer: number;
81
- }
82
-
83
- // Declare module
84
- declare module 'fs' {
85
- declare function readThing(path: string): string;
86
- }
87
-
88
- // Declare type alias
89
- declare type Location = {
90
- lat: number,
91
- lon: number
92
- };
93
-
94
- // Declare variable
95
- declare var SOME_CONST: string;
96
-
97
- // Type alias
98
- type T = string;
99
-
100
- // Export type
101
- export type { T };
102
-
103
- // Regular export
104
- export { Wrapper };
105
-
106
- // Exported type alias
107
- export type ONE = { one: number };
108
-
109
- // Object with types within
110
- var someObj = {
111
- objMethod(): void {
112
- // do nothing.
113
- }
114
- }
115
-
116
- // Example from README
117
- import SomeClass from 'some-module'
118
- import type { SomeInterface } from 'some-module'
119
-
120
- export class MyClass<T> extends SomeClass implements SomeInterface {
121
-
122
- value: T
123
-
124
- constructor(value: T) {
125
- this.value = value
126
- }
127
-
128
- get(): T {
129
- return this.value
130
- }
131
-
132
- }
@@ -1,4 +0,0 @@
1
- // @flow
2
-
3
- var n: number = 42;
4
- console.log(n);