1 |
|
2 | var cst = require('cst');
|
3 | var Parser = cst.Parser;
|
4 | var Token = cst.Token;
|
5 | var Program = cst.types.Program;
|
6 | var Fragment = cst.Fragment;
|
7 | var ScopesApi = cst.api.ScopesApi;
|
8 |
|
9 | var treeIterator = require('./tree-iterator');
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | var KEYWORD_OPERATORS = {
|
17 | 'instanceof': true,
|
18 | 'in': true
|
19 | };
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | var JsFile = function(params) {
|
31 | params = params || {};
|
32 | this._parseErrors = [];
|
33 | this._filename = params.filename;
|
34 | this._source = params.source;
|
35 |
|
36 | this._es3 = params.es3 || false;
|
37 |
|
38 | this._lineBreaks = null;
|
39 | this._lines = this._source.split(/\r\n|\r|\n/);
|
40 |
|
41 | var parser = new Parser({
|
42 | strictMode: false,
|
43 | languageExtensions: {
|
44 | gritDirectives: true,
|
45 | appleInstrumentationDirectives: true
|
46 | }
|
47 | });
|
48 |
|
49 | try {
|
50 | this._program = parser.parse(this._source);
|
51 | } catch (e) {
|
52 | this._parseErrors.push(e);
|
53 | this._program = new Program([
|
54 | new Token('EOF', '')
|
55 | ]);
|
56 | }
|
57 |
|
58 |
|
59 | this._scopes = null;
|
60 | };
|
61 |
|
62 | JsFile.prototype = {
|
63 | |
64 |
|
65 |
|
66 | getProgram: function() {
|
67 | return this._program;
|
68 | },
|
69 |
|
70 | |
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | getLineBreakStyle: function() {
|
77 | var lineBreaks = this.getLineBreaks();
|
78 | return lineBreaks.length ? lineBreaks[0] : '\n';
|
79 | },
|
80 |
|
81 | |
82 |
|
83 |
|
84 |
|
85 |
|
86 | getLineBreaks: function() {
|
87 | if (this._lineBreaks === null) {
|
88 | this._lineBreaks = this._source.match(/\r\n|\r|\n/g) || [];
|
89 | }
|
90 | return this._lineBreaks;
|
91 | },
|
92 |
|
93 | |
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 | setWhitespaceBefore: function(token, whitespace) {
|
100 | var prevToken = token.getPreviousToken();
|
101 | var ws = new Token('Whitespace', whitespace);
|
102 | var fragment = new Fragment(ws);
|
103 |
|
104 | if (prevToken && prevToken.isWhitespace) {
|
105 | if (whitespace === '') {
|
106 | prevToken.remove();
|
107 | return;
|
108 | }
|
109 |
|
110 | prevToken.parentElement.replaceChild(fragment, prevToken);
|
111 | return;
|
112 | }
|
113 |
|
114 | this._setTokenBefore(token, fragment);
|
115 | },
|
116 |
|
117 | _setTokenBefore: function(token, fragment) {
|
118 | var parent = token;
|
119 | var grandpa = parent.parentElement;
|
120 |
|
121 | while (grandpa) {
|
122 | try {
|
123 | grandpa.insertChildBefore(fragment, parent);
|
124 | break;
|
125 | } catch (e) {}
|
126 |
|
127 | parent = grandpa;
|
128 | grandpa = parent.parentElement;
|
129 | }
|
130 | },
|
131 |
|
132 | |
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 | getWhitespaceBefore: function(token) {
|
139 | if (!token.getPreviousToken) {
|
140 | console.log(token);
|
141 | }
|
142 | var prev = token.getPreviousToken();
|
143 |
|
144 | if (prev && prev.isWhitespace) {
|
145 | return prev.getSourceCode();
|
146 | }
|
147 |
|
148 | return '';
|
149 | },
|
150 |
|
151 | |
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | getFirstNodeToken: function(node) {
|
158 | return node.getFirstToken();
|
159 | },
|
160 |
|
161 | |
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 | getLastNodeToken: function(node) {
|
168 | return node.getLastToken();
|
169 | },
|
170 |
|
171 | |
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | getFirstToken: function(/*options*/) {
|
180 | return this._program.getFirstToken();
|
181 | },
|
182 |
|
183 | |
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 | getLastToken: function(/*options*/) {
|
192 | return this._program.getLastToken();
|
193 | },
|
194 |
|
195 | |
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | getPrevToken: function(token, options) {
|
204 | if (options && options.includeComments) {
|
205 | return token.getPreviousNonWhitespaceToken();
|
206 | }
|
207 |
|
208 | return token.getPreviousCodeToken();
|
209 | },
|
210 |
|
211 | |
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 | getNextToken: function(token, options) {
|
220 | if (options && options.includeComments) {
|
221 | return token.getNextNonWhitespaceToken();
|
222 | } else {
|
223 | return token.getNextCodeToken();
|
224 | }
|
225 | },
|
226 |
|
227 | |
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 | findPrevToken: function(token, type, value) {
|
236 | var prevToken = this.getPrevToken(token);
|
237 | while (prevToken) {
|
238 | if (prevToken.type === type && (value === undefined || prevToken.value === value)) {
|
239 | return prevToken;
|
240 | }
|
241 |
|
242 | prevToken = this.getPrevToken(prevToken);
|
243 | }
|
244 | return prevToken;
|
245 | },
|
246 |
|
247 | |
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 | findNextToken: function(token, type, value) {
|
256 | var nextToken = token.getNextToken();
|
257 |
|
258 | while (nextToken) {
|
259 | if (nextToken.type === type && (value === undefined || nextToken.value === value)) {
|
260 | return nextToken;
|
261 | }
|
262 |
|
263 | nextToken = nextToken.getNextToken();
|
264 | }
|
265 | return nextToken;
|
266 | },
|
267 |
|
268 | |
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 | findPrevOperatorToken: function(token, value) {
|
276 | return this.findPrevToken(token, value in KEYWORD_OPERATORS ? 'Keyword' : 'Punctuator', value);
|
277 | },
|
278 |
|
279 | |
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 | findNextOperatorToken: function(token, value) {
|
287 | return this.findNextToken(token, value in KEYWORD_OPERATORS ? 'Keyword' : 'Punctuator', value);
|
288 | },
|
289 |
|
290 | |
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 | iterate: function(cb, tree) {
|
298 | return treeIterator.iterate(tree || this._program, cb);
|
299 | },
|
300 |
|
301 | |
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 | getNodesByType: function(type) {
|
308 | type = Array.isArray(type) ? type : [type];
|
309 | var result = [];
|
310 |
|
311 | for (var i = 0, l = type.length; i < l; i++) {
|
312 | var nodes = this._program.selectNodesByType(type[i]);
|
313 |
|
314 | if (nodes) {
|
315 | result = result.concat(nodes);
|
316 | }
|
317 | }
|
318 |
|
319 | return result;
|
320 | },
|
321 |
|
322 | |
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 | iterateNodesByType: function(type, cb, context) {
|
331 | return this.getNodesByType(type).forEach(cb, context || this);
|
332 | },
|
333 |
|
334 | |
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 | iterateTokensByType: function(type, cb) {
|
342 | var tokens;
|
343 |
|
344 | if (Array.isArray(type)) {
|
345 | tokens = [];
|
346 | for (var i = 0; i < type.length; i++) {
|
347 | var items = this._program.selectTokensByType(type[i]);
|
348 | tokens = tokens.concat(items);
|
349 | }
|
350 | } else {
|
351 | tokens = this._program.selectTokensByType(type);
|
352 | }
|
353 |
|
354 | tokens.forEach(cb);
|
355 | },
|
356 |
|
357 | |
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 | iterateTokensByTypeAndValue: function(type, value, cb) {
|
366 | var values = (typeof value === 'string') ? [value] : value;
|
367 | var valueIndex = {};
|
368 | values.forEach(function(type) {
|
369 | valueIndex[type] = true;
|
370 | });
|
371 |
|
372 | this.iterateTokensByType(type, function(token) {
|
373 | if (valueIndex[token.value]) {
|
374 | cb(token);
|
375 | }
|
376 | });
|
377 | },
|
378 |
|
379 | getFirstTokenOnLineWith: function(element, options) {
|
380 | options = options || {};
|
381 | var firstToken = element;
|
382 |
|
383 | if (element.isComment && !options.includeComments) {
|
384 | firstToken = null;
|
385 | }
|
386 |
|
387 | if (element.isWhitespace && !options.includeWhitespace) {
|
388 | firstToken = null;
|
389 | }
|
390 |
|
391 | var currentToken = element.getPreviousToken();
|
392 | while (currentToken) {
|
393 | if (currentToken.isWhitespace) {
|
394 | if (currentToken.getNewlineCount() > 0 || !currentToken.getPreviousToken()) {
|
395 | if (options.includeWhitespace) {
|
396 | firstToken = currentToken;
|
397 | }
|
398 | break;
|
399 | }
|
400 | } else if (currentToken.isComment) {
|
401 | if (options.includeComments) {
|
402 | firstToken = currentToken;
|
403 | break;
|
404 | }
|
405 | if (currentToken.getNewlineCount() > 0) {
|
406 | break;
|
407 | }
|
408 | } else {
|
409 | firstToken = currentToken;
|
410 | }
|
411 |
|
412 | currentToken = currentToken.getPreviousToken();
|
413 | }
|
414 |
|
415 | if (firstToken) {
|
416 | return firstToken;
|
417 | }
|
418 |
|
419 | currentToken = element.getNextToken();
|
420 | while (currentToken) {
|
421 | if (currentToken.isWhitespace) {
|
422 | if (currentToken.getNewlineCount() > 0 || !currentToken.getNextToken()) {
|
423 | if (options.includeWhitespace) {
|
424 | firstToken = currentToken;
|
425 | }
|
426 | break;
|
427 | }
|
428 | } else if (currentToken.isComment) {
|
429 | if (options.includeComments) {
|
430 | firstToken = currentToken;
|
431 | break;
|
432 | }
|
433 | if (currentToken.getNewlineCount() > 0) {
|
434 | break;
|
435 | }
|
436 | } else {
|
437 | firstToken = currentToken;
|
438 | }
|
439 |
|
440 | currentToken = currentToken.getNextToken();
|
441 | }
|
442 |
|
443 | return firstToken;
|
444 | },
|
445 |
|
446 | |
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 | getLastTokenOnLine: function(lineNumber, options) {
|
457 | options = options || {};
|
458 |
|
459 | var loc;
|
460 | var token = this._program.getLastToken();
|
461 | var currentToken;
|
462 |
|
463 | while (token) {
|
464 | loc = token.getLoc();
|
465 | currentToken = token;
|
466 | token = token.getPreviousToken();
|
467 |
|
468 | if (loc.start.line <= lineNumber && loc.end.line >= lineNumber) {
|
469 |
|
470 |
|
471 |
|
472 | if (currentToken.isWhitespace && !options.includeWhitespace) {
|
473 | continue;
|
474 | }
|
475 | }
|
476 |
|
477 | if (loc.start.line === lineNumber || loc.end.line === lineNumber) {
|
478 | if (currentToken.isComment && !options.includeComments) {
|
479 | continue;
|
480 | }
|
481 |
|
482 | return currentToken;
|
483 | }
|
484 | }
|
485 |
|
486 | return null;
|
487 | },
|
488 |
|
489 | |
490 |
|
491 |
|
492 |
|
493 |
|
494 | getDialect: function() {
|
495 | if (this._es3) {
|
496 | return 'es3';
|
497 | }
|
498 |
|
499 | return 'es6';
|
500 | },
|
501 |
|
502 | |
503 |
|
504 |
|
505 |
|
506 |
|
507 | getSource: function() {
|
508 | return this._source;
|
509 | },
|
510 |
|
511 | |
512 |
|
513 |
|
514 |
|
515 |
|
516 | getTree: function() {
|
517 | return this._program || {};
|
518 | },
|
519 |
|
520 | |
521 |
|
522 |
|
523 | getComments: function() {
|
524 | var comments = [];
|
525 | var token = this._program.getFirstToken();
|
526 | while (token) {
|
527 | if (token.isComment) {
|
528 | comments[comments.length] = token;
|
529 | }
|
530 | token = token.getNextToken();
|
531 | }
|
532 | return comments;
|
533 | },
|
534 |
|
535 | |
536 |
|
537 |
|
538 |
|
539 |
|
540 | getFilename: function() {
|
541 | return this._filename;
|
542 | },
|
543 |
|
544 | |
545 |
|
546 |
|
547 |
|
548 |
|
549 | getLines: function() {
|
550 | return this._lines;
|
551 | },
|
552 |
|
553 | |
554 |
|
555 |
|
556 |
|
557 |
|
558 | getScopes: function() {
|
559 | if (!this._scopes) {
|
560 | this._scopes = new ScopesApi(this._program);
|
561 | }
|
562 |
|
563 | return this._scopes;
|
564 | },
|
565 |
|
566 | |
567 |
|
568 |
|
569 |
|
570 |
|
571 |
|
572 |
|
573 | isOnTheSameLine: function(tokenBefore, tokenAfter) {
|
574 | if (tokenBefore === tokenAfter) {
|
575 | return true;
|
576 | }
|
577 | tokenBefore = tokenBefore instanceof Token ? tokenBefore : tokenBefore.getLastToken();
|
578 | tokenAfter = tokenAfter instanceof Token ? tokenAfter : tokenAfter.getFirstToken();
|
579 | var currentToken = tokenBefore;
|
580 | while (currentToken) {
|
581 | if (currentToken === tokenAfter) {
|
582 | return true;
|
583 | }
|
584 | if (currentToken !== tokenBefore && currentToken.getNewlineCount() > 0) {
|
585 | return false;
|
586 | }
|
587 | currentToken = currentToken.getNextToken();
|
588 | }
|
589 | return false;
|
590 | },
|
591 |
|
592 | getDistanceBetween: function(tokenBefore, tokenAfter) {
|
593 | if (tokenBefore === tokenAfter) {
|
594 | return 0;
|
595 | }
|
596 | tokenBefore = tokenBefore instanceof Token ? tokenBefore : tokenBefore.getLastToken();
|
597 | tokenAfter = tokenAfter instanceof Token ? tokenAfter : tokenAfter.getFirstToken();
|
598 | var currentToken = tokenBefore.getNextToken();
|
599 | var distance = 0;
|
600 | while (currentToken) {
|
601 | if (currentToken === tokenAfter) {
|
602 | break;
|
603 | }
|
604 |
|
605 | distance += currentToken.getSourceCodeLength();
|
606 | currentToken = currentToken.getNextToken();
|
607 | }
|
608 | return distance;
|
609 | },
|
610 |
|
611 | getLineCountBetween: function(tokenBefore, tokenAfter) {
|
612 | if (tokenBefore === tokenAfter) {
|
613 | return 0;
|
614 | }
|
615 | tokenBefore = tokenBefore instanceof Token ? tokenBefore : tokenBefore.getLastToken();
|
616 | tokenAfter = tokenAfter instanceof Token ? tokenAfter : tokenAfter.getFirstToken();
|
617 |
|
618 | var currentToken = tokenBefore.getNextToken();
|
619 | var lineCount = 0;
|
620 | while (currentToken) {
|
621 | if (currentToken === tokenAfter) {
|
622 | break;
|
623 | }
|
624 |
|
625 | lineCount += currentToken.getNewlineCount();
|
626 | currentToken = currentToken.getNextToken();
|
627 | }
|
628 | return lineCount;
|
629 | },
|
630 |
|
631 | |
632 |
|
633 |
|
634 |
|
635 |
|
636 | getLinesWithCommentsRemoved: function() {
|
637 | var lines = this.getLines().concat();
|
638 |
|
639 | this.getComments().concat().reverse().forEach(function(comment) {
|
640 | var loc = comment.getLoc();
|
641 | var startLine = loc.start.line;
|
642 | var startCol = loc.start.column;
|
643 | var endLine = loc.end.line;
|
644 | var endCol = loc.end.column;
|
645 | var i = startLine - 1;
|
646 |
|
647 | if (startLine === endLine) {
|
648 |
|
649 | lines[i] = lines[i].replace(/\*\/\s+/, '\*\/');
|
650 | lines[i] = lines[i].substring(0, startCol) + lines[i].substring(endCol);
|
651 | } else {
|
652 | lines[i] = lines[i].substring(0, startCol);
|
653 | for (var x = i + 1; x < endLine - 1; x++) {
|
654 | lines[x] = '';
|
655 | }
|
656 |
|
657 | lines[x] = lines[x].substring(endCol);
|
658 | }
|
659 | });
|
660 |
|
661 | return lines;
|
662 | },
|
663 |
|
664 | |
665 |
|
666 |
|
667 |
|
668 |
|
669 | render: function() {
|
670 | return this._program.getSourceCode();
|
671 | },
|
672 |
|
673 | |
674 |
|
675 |
|
676 |
|
677 |
|
678 | getParseErrors: function() {
|
679 | return this._parseErrors;
|
680 | }
|
681 | };
|
682 |
|
683 | module.exports = JsFile;
|