flow-remove-types 1.1.2 → 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/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.
@@ -89,10 +89,6 @@ require('flow-remove-types/register')
89
89
  require('./some-module-with-flow-type-syntax')
90
90
  ```
91
91
 
92
- As always, don't forget to use `flow-remove-types` to compile files before
93
- distributing your code on npm, as using the require hook affects the whole
94
- runtime and not just your module.
95
-
96
92
  You can also provide options to the require hook:
97
93
 
98
94
  ```js
@@ -100,14 +96,19 @@ You can also provide options to the require hook:
100
96
  require('flow-remove-types/register')({ all: true })
101
97
  ```
102
98
 
103
- Use options to define exactly which files to `include` or `exclude` with regular
99
+ Use options to define exactly which files to `includes` or `excludes` with regular
104
100
  expressions. All files are included by default except those found in the
105
101
  `node_modules` folder, which is excluded by default.
106
102
 
107
103
  ```js
108
- require('flow-remove-types/register')({ include: /\/custom_path\// })
104
+ require('flow-remove-types/register')({ includes: /\/custom_path\// })
109
105
  ```
110
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
+
111
112
 
112
113
  ## Dead-Simple Transforms
113
114
 
package/flow-remove-types CHANGED
@@ -18,6 +18,7 @@ var usage = 'Usage: flow-remove-types [options] [sources] \n' +
18
18
  ' -p, --pretty Remove flow types without replacing with spaces, \n' +
19
19
  ' producing prettier output but may require using source maps\n' +
20
20
  ' -m, --sourcemaps Also output source map files. Optionally pass "inline"\n' +
21
+ ' -q, --quiet Does not produce any output concerning successful progress.\n' +
21
22
 
22
23
  '\nExamples:\n' +
23
24
 
@@ -39,11 +40,6 @@ var usage = 'Usage: flow-remove-types [options] [sources] \n' +
39
40
  '\nTransform stdin:\n' +
40
41
  ' cat input.js | flow-remove-types > output.js\n';
41
42
 
42
- function error(msg) {
43
- process.stderr.write('\n\033[31m ' + msg + '\033[0m\n\n');
44
- process.exit(1);
45
- }
46
-
47
43
  var _memo = {};
48
44
 
49
45
  function mkdirp(dirpath) {
@@ -79,6 +75,7 @@ var all;
79
75
  var pretty;
80
76
  var sourceMaps;
81
77
  var inlineSourceMaps;
78
+ var quiet;
82
79
  var sources = [];
83
80
  var i = 2;
84
81
  while (i < process.argv.length) {
@@ -107,11 +104,24 @@ while (i < process.argv.length) {
107
104
  inlineSourceMaps = true;
108
105
  i++;
109
106
  }
107
+ } else if (arg === '-q' || arg === '--quiet') {
108
+ quiet = true;
110
109
  } else {
111
110
  sources.push(arg);
112
111
  }
113
112
  }
114
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
+
115
125
  // Validate arguments
116
126
  if (outDir && outFile) {
117
127
  error('Only specify one of --out-dir or --out-file');
@@ -204,11 +214,11 @@ function transformAndOutput(content, outFile, source) {
204
214
 
205
215
  if (outFile) {
206
216
  fs.writeFileSync(outFile, code);
207
- process.stdout.write(fileName + '\n \u21B3 \033[32m' + outFile + '\033[0m\n');
217
+ info(fileName + '\n \u21B3 \033[32m' + outFile + '\033[0m\n');
208
218
  if (sourceMaps && !inlineSourceMaps) {
209
219
  var mapOutFile = outFile + '.map';
210
220
  fs.writeFileSync(mapOutFile, JSON.stringify(map) + '\n');
211
- process.stdout.write('\033[2m \u21B3 \033[32m' + mapOutFile + '\033[0m\n');
221
+ info('\033[2m \u21B3 \033[32m' + mapOutFile + '\033[0m\n');
212
222
  }
213
223
  } else {
214
224
  process.stdout.write(code);
package/index.js CHANGED
@@ -69,7 +69,14 @@ module.exports = function flowRemoveTypes(source, options) {
69
69
  plugins: [ '*', 'jsx', 'flow' ],
70
70
  });
71
71
 
72
- visit(ast, removedNodes, removeFlowVisitor);
72
+ var context = {
73
+ ast: ast,
74
+ source: source,
75
+ removedNodes: removedNodes,
76
+ pretty: Boolean(options && options.pretty)
77
+ };
78
+
79
+ visit(ast, context, removeFlowVisitor);
73
80
 
74
81
  return resultPrinter(options, source, removedNodes);
75
82
  }
@@ -139,39 +146,56 @@ var removeFlowVisitor = {
139
146
  ClassDeclaration: removeImplementedInterfaces,
140
147
  ClassExpression: removeImplementedInterfaces,
141
148
 
142
- Identifier: function (removedNodes, node, ast) {
149
+ Identifier: function (context, node, ast) {
143
150
  if (node.optional) {
144
151
  // Find the optional token.
145
152
  var idx = findTokenIndex(ast.tokens, node.start);
146
153
  do {
147
154
  idx++;
148
155
  } while (ast.tokens[idx].type.label !== '?');
149
- removeNode(removedNodes, ast.tokens[idx]);
156
+ removeNode(context, ast.tokens[idx]);
150
157
  }
151
158
  },
152
159
 
153
- ClassProperty: function (removedNodes, node) {
160
+ ClassProperty: function (context, node) {
154
161
  if (!node.value) {
155
- return removeNode(removedNodes, node)
162
+ return removeNode(context, node)
156
163
  }
157
164
  },
158
165
 
159
- ExportNamedDeclaration: function (removedNodes, node) {
160
- if (node.exportKind === 'type') {
161
- return removeNode(removedNodes, node);
166
+ ExportNamedDeclaration: function (context, node) {
167
+ if (node.exportKind === 'type' || node.exportKind === 'typeof') {
168
+ return removeNode(context, node);
162
169
  }
163
170
  },
164
171
 
165
- ImportDeclaration: function (removedNodes, node) {
166
- if (node.importKind === 'type') {
167
- 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;
168
192
  }
169
193
  }
170
194
  };
171
195
 
172
196
  // If this class declaration or expression implements interfaces, remove
173
197
  // the associated tokens.
174
- function removeImplementedInterfaces(removedNodes, node, ast) {
198
+ function removeImplementedInterfaces(context, node, ast) {
175
199
  if (node.implements && node.implements.length > 0) {
176
200
  var first = node.implements[0];
177
201
  var last = node.implements[node.implements.length - 1];
@@ -182,9 +206,8 @@ function removeImplementedInterfaces(removedNodes, node, ast) {
182
206
 
183
207
  var lastIdx = findTokenIndex(ast.tokens, last.start);
184
208
  do {
185
- if (ast.tokens[idx].type !== 'CommentBlock' &&
186
- ast.tokens[idx].type !== 'CommentLine') {
187
- removeNode(removedNodes, ast.tokens[idx]);
209
+ if (!isComment(ast.tokens[idx])) {
210
+ removeNode(context, ast.tokens[idx]);
188
211
  }
189
212
  } while (idx++ !== lastIdx);
190
213
  }
@@ -192,17 +215,131 @@ function removeImplementedInterfaces(removedNodes, node, ast) {
192
215
 
193
216
  // Append node to the list of removed nodes, ensuring the order of the nodes
194
217
  // in the list.
195
- function removeNode(removedNodes, node) {
218
+ function removeNode(context, node) {
219
+ var removedNodes = context.removedNodes;
196
220
  var length = removedNodes.length;
197
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
+
198
227
  while (index > 0 && removedNodes[index - 1].end > node.start) {
199
228
  index--;
200
229
  }
230
+
201
231
  if (index === length) {
232
+ if (spaceNode) {
233
+ removedNodes.push(spaceNode);
234
+ }
202
235
  removedNodes.push(node);
236
+ if (lineNode) {
237
+ removedNodes.push(lineNode);
238
+ }
203
239
  } else {
204
- 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
+ }
205
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++;
341
+ }
342
+
206
343
  return false;
207
344
  }
208
345
 
@@ -260,6 +397,11 @@ function findTokenIndex(tokens, offset) {
260
397
  return ptr;
261
398
  }
262
399
 
400
+ // True if the provided token is a comment.
401
+ function isComment(token) {
402
+ return token.type === 'CommentBlock' || token.type === 'CommentLine';
403
+ }
404
+
263
405
  // Produce a string full of space characters of a given size.
264
406
  function space(size) {
265
407
  var sp = ' '
@@ -282,6 +424,10 @@ function space(size) {
282
424
  // with spaces.
283
425
  function generateSourceMappings(removedNodes) {
284
426
  var mappings = '';
427
+ if (!removedNodes || removedNodes.length === '') {
428
+ return mappings;
429
+ }
430
+
285
431
  var end = { line: 1, column: 0 };
286
432
 
287
433
  for (var i = 0; i < removedNodes.length; i++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flow-remove-types",
3
- "version": "1.1.2",
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",
@@ -17,13 +17,13 @@
17
17
  "LICENSE",
18
18
  "PATENTS"
19
19
  ],
20
- "homepage": "https://github.com/leebyron/flow-remove-types",
20
+ "homepage": "https://github.com/flowtype/flow-remove-types",
21
21
  "bugs": {
22
- "url": "https://github.com/leebyron/flow-remove-types/issues"
22
+ "url": "https://github.com/flowtype/flow-remove-types/issues"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "http://github.com/leebyron/flow-remove-types.git"
26
+ "url": "http://github.com/flowtype/flow-remove-types.git"
27
27
  },
28
28
  "scripts": {
29
29
  "test": "./test.sh",
@@ -38,7 +38,7 @@
38
38
  "es6"
39
39
  ],
40
40
  "dependencies": {
41
- "babylon": "^6.8.4",
41
+ "babylon": "^6.15.0",
42
42
  "vlq": "^0.2.1"
43
43
  }
44
44
  }
package/register.js CHANGED
@@ -3,9 +3,11 @@ var flowRemoveTypes = require('./index');
3
3
  // Supported options:
4
4
  //
5
5
  // - all: Transform all files, not just those with a @flow comment.
6
- // - includes: A Regexp to determine which files should be transformed.
7
- // - excludes: A Regexp to determine which files should not be transformed,
8
- // defaults to ignoring /node_modules/, set to null to excludes nothing.
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)
9
11
  var options;
10
12
  module.exports = function setOptions(newOptions) {
11
13
  options = newOptions;
@@ -32,7 +34,33 @@ exts.forEach(function (ext) {
32
34
  });
33
35
 
34
36
  function shouldTransform(filename, options) {
35
- var includes = options && options.includes;
36
- var excludes = options && 'excludes' in options ? options.excludes : /\/node_modules\//;
37
- return (!includes || include.test(filename)) && !(excludes && excludes.test(filename));
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
+ );
38
66
  }