Added jsx_orphaned_brackets_transformer for breaking JSX parser change
syranide committed
Feb 2, 2015 at 23:24 UTC
f3271ad53b2476b25532a5ebc206e81f0afefdc1
5 files changed
+271
npm-jsx_orphaned_brackets_transformer/.gitignore
new
+1
@@ -0,0 +1 @@
1
+node_modules
npm-jsx_orphaned_brackets_transformer/README.md
new
+31
@@ -0,0 +1,31 @@
1
+# JSX Orphaned Brackets Transformer
2
+
3
+React 0.13 no longer parses orphaned > and } as text.
4
+
5
+Take this example block:
6
+
7
+```js
8
+<div>
9
+ > }
10
+</div>
11
+```
12
+
13
+In 0.12 and below, this would be transformed to the following:
14
+
15
+```js
16
+React.DOM.div(null,
17
+ "> }",
18
+)
19
+```
20
+
21
+In 0.13, this will instead throw a parser error.
22
+
23
+
24
+## Usage
25
+
26
+The `jsx_orphaned_brackets_transformer` module ships an executable which transforms a file or directory of files. Files will be modified in place, so be sure you are prepared for that.
27
+
28
+```sh
29
+$ npm -g install jsx_orphaned_brackets_transformer
30
+$ jsx_orphaned_brackets_transformer <path_to_file_or_files>
31
+```
npm-jsx_orphaned_brackets_transformer/package.json
new
+18
@@ -0,0 +1,18 @@
1
+{
2
+ "name": "jsx_orphaned_brackets_transformer",
3
+ "description": "A utility to update your JSX to work with React 0.13.",
4
+ "version": "1.0.0",
5
+ "main": "run.js",
6
+ "dependencies": {
7
+ "esprima-fb": "10001.1.0-dev-harmony-fb",
8
+ "graceful-fs": "~2.0.0",
9
+ "jstransform": "~2.0.1",
10
+ "node-find-files": "0.0.2",
11
+ "optimist": "~0.6.0"
12
+ },
13
+ "bin": {
14
+ "jsx_orphaned_brackets_transformer": "./run.js"
15
+ },
16
+ "license": "Apache-2.0",
17
+ "preferGlobal": true
18
+}
npm-jsx_orphaned_brackets_transformer/run.js
new
+163
@@ -0,0 +1,163 @@
1
+#!/usr/bin/env node
2
+
3
+var esprima = require('esprima-fb');
4
+var FileFinder = require('node-find-files');
5
+var fs = require('graceful-fs');
6
+var jstransform = require('jstransform');
7
+var path = require('path');
8
+var visitReactTag = require('./transforms/react').visitReactTag;
9
+
10
+var S = esprima.Syntax;
11
+
12
+var USAGE =
13
+ 'Read a file (or directory of files) from disk, transform any orphaned ' +
14
+ '} and > characters to avoid parser errors with React 0.13.';
15
+
16
+function _visitFbt(node, path, state) {
17
+ return false;
18
+}
19
+_visitFbt.test = function(node, path, state) {
20
+ return node.type === S.XJSElement
21
+ && node.openingElement.name.name === 'fbt';
22
+};
23
+
24
+var VISITORS_LIST = [
25
+ _visitFbt,
26
+ visitReactTag
27
+];
28
+
29
+function _transformSource(source) {
30
+ return jstransform.transform(VISITORS_LIST, source).code;
31
+}
32
+
33
+function transformDir(dirPath, exclude) {
34
+ var finder = new FileFinder({
35
+ rootFolder: dirPath,
36
+ filterFunction: function(path, stat) {
37
+ return /\.jsx?$/.test(path) && (!exclude || !exclude.test(path));
38
+ }
39
+ });
40
+
41
+ var numTransforms = 0;
42
+ var completeTransforms = 0;
43
+ var findingComplete = false;
44
+ function _printProgress() {
45
+ process.stdout.clearLine();
46
+ process.stdout.cursorTo(0);
47
+ process.stdout.write(
48
+ completeTransforms + '/' + numTransforms + ' transforms complete'
49
+ );
50
+
51
+ if (findingComplete && completeTransforms === numTransforms) {
52
+ console.log('\ndone!');
53
+ }
54
+ }
55
+
56
+ finder.on('match', function(pathStr, stat) {
57
+ fs.readFile(pathStr, 'utf8', function(err, data) {
58
+ if (err) {
59
+ err.message = err.message + ' (' + pathStr + ')';
60
+ throw err;
61
+ }
62
+
63
+ numTransforms++;
64
+ _printProgress();
65
+
66
+ var transformedData;
67
+ try {
68
+ transformedData = _transformSource(data);
69
+ } catch (e) {
70
+ e.message = e.message + ' (' + pathStr + ')';
71
+ throw e;
72
+ }
73
+
74
+ if (transformedData !== data) {
75
+ fs.writeFile(pathStr, transformedData, function(err) {
76
+ if (err) {
77
+ err.message = err.message + ' (' + pathStr + ')';
78
+ throw err;
79
+ }
80
+ completeTransforms++;
81
+ _printProgress();
82
+ });
83
+ } else {
84
+ completeTransforms++;
85
+ _printProgress();
86
+ }
87
+ });
88
+ });
89
+
90
+ finder.on('error', function(err) {
91
+ console.log('\nError: ', err.stack);
92
+ throw err;
93
+ });
94
+
95
+ finder.on('complete', function() {
96
+ findingComplete = true;
97
+ });
98
+
99
+ finder.startSearch();
100
+}
101
+
102
+function transformFile(pathStr) {
103
+ fs.readFile(pathStr, 'utf8', function(err, data) {
104
+ if (err) {
105
+ err.message = err.message + ' (' + pathStr + ')';
106
+ throw err;
107
+ }
108
+
109
+ var transformedData;
110
+ try {
111
+ transformedData = _transformSource(data);
112
+ } catch (e) {
113
+ e.message = e.message + ' (' + pathStr + ')';
114
+ throw e;
115
+ }
116
+
117
+ if (transformedData !== data) {
118
+ fs.writeFile(pathStr, transformedData, function(err) {
119
+ if (err) {
120
+ err.message = err.message + ' (' + pathStr + ')';
121
+ throw err;
122
+ }
123
+ console.log('done!');
124
+ });
125
+ } else {
126
+ console.log('done!');
127
+ }
128
+ });
129
+}
130
+
131
+if (require.main === module) {
132
+ var argv = require('optimist')
133
+ .usage(USAGE)
134
+ .argv;
135
+
136
+ if (argv._.length === 0) {
137
+ throw new Error(
138
+ 'Please specify a file or directory path as the first arg!'
139
+ );
140
+ }
141
+
142
+ argv._.forEach(function(arg) {
143
+ var absPath = path.resolve(arg);
144
+
145
+ fs.stat(absPath, function(err, stat) {
146
+ if (err) throw err;
147
+
148
+ if (stat.isFile()) {
149
+ transformFile(absPath);
150
+ } else if (stat.isDirectory()) {
151
+ var exclude = null;
152
+ if (argv.exclude) {
153
+ exclude = new RegExp(argv.exclude);
154
+ }
155
+ transformDir(absPath, exclude);
156
+ } else {
157
+ throw new Error('Unknown filesystem node type: ' + absPath);
158
+ }
159
+ });
160
+ });
161
+}
162
+
163
+exports.transformDir = transformDir;
npm-jsx_orphaned_brackets_transformer/transforms/react.js
new
+58
@@ -0,0 +1,58 @@
1
+/**
2
+ * Copyright 2013-2014 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+/*global exports:true*/
17
+"use strict";
18
+var Syntax = require('esprima-fb').Syntax;
19
+var utils = require('jstransform/src/utils');
20
+
21
+function visitReactTag(traverse, object, path, state) {
22
+ object.attributes.forEach(function(attr, index) {
23
+ if (attr.value) {
24
+ traverse(attr.value, path, state);
25
+ }
26
+ });
27
+
28
+ object.children.forEach(function(child, index) {
29
+ if (child.type === Syntax.Literal) {
30
+ codemodXJSLiteral(child, state);
31
+ } else {
32
+ traverse(child, path, state);
33
+ }
34
+ });
35
+
36
+ return false;
37
+}
38
+
39
+visitReactTag.test = function(object, path, state) {
40
+ return object.type === Syntax.XJSElement;
41
+};
42
+
43
+function codemodXJSLiteral(object, state) {
44
+ var value = object.raw;
45
+
46
+ utils.catchup(object.range[0], state);
47
+
48
+ var rightCurlyBracket = '}'; // or {'}'}
49
+ var rightAngledBracket = '>'; // or {'>'}
50
+
51
+ value = value.replace(/\}/g, rightCurlyBracket);
52
+ value = value.replace(/\>/g, rightAngledBracket);
53
+
54
+ utils.append(value, state);
55
+ utils.move(object.range[1], state);
56
+}
57
+
58
+exports.visitReactTag = visitReactTag;