Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
View | Details | Raw Unified | Return to bug 490629
Collapse All | Expand All

(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/astManager.js (-419 / +9 lines)
Lines 16-437 Link Here
16
	'javascript/lru',
16
	'javascript/lru',
17
	"javascript/util",
17
	"javascript/util",
18
	"acorn/dist/acorn",
18
	"acorn/dist/acorn",
19
	"acorn/dist/acorn_loose"
19
	"acorn/dist/acorn_loose",
20
], function(Deferred, Objects, LRU, Util, acorn, acorn_loose) {
20
	"javascript/orionAcorn",
21
	var registry,
21
], function(Deferred, Objects, LRU, Util, acorn, acorn_loose, OrionAcorn) {
22
		dependencies,
22
	var registry;
23
		environments,
24
		comments,
25
		tokens,
26
		leadingCommentsIndex,
27
		trailingCommentsIndex,
28
		needReset,
29
		currentOptions,
30
		options,
31
		error;
32
33
	/**
34
	 * @name onToken
35
	 * @description Function called when recording a token
36
	 * @param token the given token to record
37
	 */
38
	function onToken(token) {
39
		var type = "Punctuator";
40
		var label = token.type.label;
41
		var eof = false;
42
		var value = token.value;
43
		switch(label) {
44
			case "num" :
45
				//num: new TokenType("num", startsExpr),
46
				type = "Numeric";
47
				break;
48
			case "regexp" :
49
				//regexp: new TokenType("regexp", startsExpr),
50
				type = "RegularExpression";
51
				token.value = "/" + value.pattern + "/" + (value.flags ? value.flags : "");
52
				break;
53
			case "string" :
54
				//string: new TokenType("string", startsExpr),
55
				type = "String";
56
				break;
57
			case "name" :
58
				// name: new TokenType("name", startsExpr),
59
				type = "Identifier";
60
				break;
61
			case "eof" :
62
				//eof: new TokenType("eof)
63
				eof = true;
64
				break;
65
			default:
66
				var keyword = token.type.keyword;
67
				if (keyword) {
68
					switch(keyword) {
69
						case "null" :
70
							type = "Null";
71
							break;
72
						case "true" :
73
						case "false" :
74
							type = "Boolean";
75
							break;
76
						default: 
77
							type = "Keyword";
78
					}
79
				}
80
		}
81
		if (!eof) {
82
			if (typeof value === "undefined") {
83
				token.value = label;
84
			}
85
			token.type = type;
86
			token.index = tokens.length;
87
			tokens.push(token);
88
		}
89
	}
90
91
	/**
92
	 * @name onComment
93
	 * @description function called when a comment is recorded
94
	 * @param block a boolean to indicate if this is a block comment (true) or a line comment (false)
95
	 * @param text the given comment contents
96
	 * @param start the given start position
97
	 * @param end the given end position
98
	 * @param startLoc the given start location
99
	 * @param endLoc the given end location
100
	 */
101
	function onComment(block, text, start, end, startLoc, endLoc) {
102
		var comment = {
103
			type: block ? 'Block' : 'Line',
104
			value: text,
105
			start: start,
106
			end: end
107
		};
108
		if (currentOptions.locations) {
109
			comment.loc = {
110
				start: startLoc,
111
				end: endLoc,
112
				sourceFile: currentOptions.sourceFile
113
			};
114
		}
115
		if (currentOptions.ranges) {
116
			comment.range = [start, end];
117
		}
118
		comments.push(comment);
119
	}
120
121
	/**
122
	 * Define an acorn plugin to record the comments even if there are syntax errors (incomplete block comments),
123
	 * it linked comments and nodes (leadingComments and trailingComments) and it records environments and dependencies
124
	 */
125
	function acornPlugin (instance, opts) {
126
		if (!opts) {
127
			return;
128
		}
129
		/**
130
		 * Returns a deep copy of the given obj
131
		 */
132
		function deepCopy(obj) {
133
			var ret = {}, key, val;
134
			for (key in obj) {
135
				if (obj.hasOwnProperty(key)) {
136
					val = obj[key];
137
					if (typeof val === 'object' && val !== null) {
138
						ret[key] = deepCopy(val);
139
					} else {
140
						ret[key] = val;
141
					}
142
				}
143
			}
144
			return ret;
145
		}
146
147
		instance.extend("raise", function(nextMethod) {
148
			return function (pos, message) {
149
				try {
150
					return nextMethod.call(this, pos, message);
151
				} catch(err) {
152
					if (err instanceof SyntaxError) {
153
						if (this.pos === this.input.length) {
154
							err.type = Util.ErrorTypes.EndOfInput;
155
						} else {
156
							err.type = Util.ErrorTypes.Unexpected;
157
						}
158
						if (needReset) {
159
							// we only reset tokens once. We don't want to reset them again when the syntax error is thrown during acorn_loose parsing
160
							reset();
161
							needReset = false;
162
						}
163
					}
164
					error = err;
165
					err.index = pos;
166
					throw err;
167
				}
168
			};
169
		});
170
		instance.extend("startNode", function(nextMethod) {
171
			return function () {
172
				var node = nextMethod.call(this);
173
				// attach leading comments
174
				var max = comments.length;
175
				if (max !== leadingCommentsIndex) {
176
					// we got new comments since the last node
177
					var i = leadingCommentsIndex;
178
					loop: for (; i< max; i++) {
179
						var comment = comments[i];
180
						if (node.range[0] >= comment.range[1]) {
181
							// attach the comment to the node
182
							if (!node.leadingComments) {
183
								node.leadingComments = [];
184
							}
185
							node.leadingComments.push(deepCopy(comments[i]));
186
						} else {
187
							break loop;
188
						}
189
					}
190
					leadingCommentsIndex = i;
191
				}
192
				return node;
193
			};
194
		});
195
		instance.extend("finishNode", function(nextMethod) {
196
			/**
197
			 * @description Collects the dependencies from call expressions and new expressions
198
			 * @param {Node} callee The named callee node 
199
			 * @param {Array.<Node>} args The list of arguments for the expression
200
			 * ORION
201
			 */
202
			function collectDeps(callee, args, extra) {
203
				if(extra.deps) {
204
					var len = args.length;
205
					if(callee.name === 'importScripts') {
206
						addArrayDeps(args, extra); //importScripts('foo', 'bar'...)
207
					} else if(callee.name === 'Worker') {
208
						addDep(args[0], extra);
209
					} else if(callee.name === 'require') {
210
						var _a = args[0];
211
						if(_a.type === "ArrayExpression") {
212
							extra.envs.node = true;
213
							addArrayDeps(_a.elements, extra); //require([foo])
214
						} else if(_a.type === "Literal") {
215
							extra.envs.node = true;
216
							addDep(_a, extra); // require('foo')
217
						}
218
						if(len > 1) {
219
							_a = args[1];
220
							if(_a.type === "ArrayExpression") {
221
								extra.envs.node = true;
222
								addArrayDeps(_a.elements, extra);
223
							}
224
						}
225
					} else if(callee.name === 'requirejs') {
226
						_a = args[0];
227
						if(_a.type === "ArrayExpression") {
228
							extra.envs.amd = true;
229
							addArrayDeps(_a.elements, extra); //requirejs([foo])
230
						}
231
					} else if(callee.name === 'define' && len > 1) {//second arg must be array
232
						_a = args[0];
233
						if(_a.type === "Literal") {
234
							_a = args[1];
235
						}
236
						if(_a.type === "ArrayExpression") {
237
							extra.envs.amd = true;
238
							addArrayDeps(_a.elements, extra);
239
						}
240
					}
241
				}
242
			}
243
			
244
				/**
245
			 * @description Adds all of the entries from the array of deps to the global state
246
			 * @param {Array} array The array of deps to add
247
			 * ORION
248
			 */
249
			function addArrayDeps(array, extra) {
250
				if(extra.deps) {
251
					var len = array.length;
252
					for(var i = 0; i < len; i++) {
253
						addDep(array[i], extra);
254
					}
255
				}
256
			}
257
			
258
				/**
259
			 * @description Adds a dependency if it has not already been added
260
			 * @param {Object} node The AST node
261
			 */
262
			function addDep(node, extra) {
263
				if(extra.deps && node.type === "Literal") {
264
					if (!extra.deps[node.value]) {
265
						extra.deps[node.value] = 1;
266
					}
267
				}
268
			}
269
			return function(node, type) {
270
				if (type === "CallExpression" || type === "NewExpression") {
271
					var extra = {
272
						deps: {},
273
						envs: {}
274
					};
275
					collectDeps(node.callee, node.arguments, extra);
276
					// copy all properties from extra.envs into environments
277
					var env = extra.envs;
278
					for (var prop in env) {
279
						if (env.hasOwnProperty(prop)) {
280
							environments[prop] = env[prop];
281
						}
282
					}
283
					var deps = extra.deps;
284
					// copy all properties from extra.deps into dependencies
285
					for (var dep in deps) {
286
						if (deps.hasOwnProperty(dep) && !dependencies.hasOwnProperty(dep)) {
287
							dependencies[dep] = {type: "Literal", value: dep };
288
						}
289
					}
290
				}
291
				var result = nextMethod.call(this, node, type);
292
				// attach trailing comments
293
				var max = comments.length;
294
				if (max !== trailingCommentsIndex) {
295
					// we got new comments since the last node
296
					var i = trailingCommentsIndex;
297
					loop: for (; i< max; i++) {
298
						var comment = comments[i];
299
						if (result.range[1] <= comment.range[0]) {
300
							// attach the comment to the node
301
							if (!result.trailingComments) {
302
								result.trailingComments = [];
303
							}
304
							result.trailingComments.push(deepCopy(comments[i]));
305
						} else {
306
							break loop;
307
						}
308
					}
309
					trailingCommentsIndex = i;
310
				}
311
				return result;
312
			};
313
		});
314
		instance.extend("skipBlockComment", function(nextMethod) {
315
			return function() {
316
				var lineBreak = /\r\n?|\n|\u2028|\u2029/;
317
				var lineBreakG = new RegExp(lineBreak.source, "g");
318
319
				var startLoc = this.curPosition();
320
				var start = this.pos, end = this.input.indexOf("*/", this.pos += 2);
321
				if (end !== -1) {
322
					this.pos -= 2;
323
					return nextMethod.call(this);
324
				}
325
				this.pos += 2;
326
				// error recovery: the block comment is not complete
327
				if (this.options.locations) {
328
					lineBreakG.lastIndex = start;
329
					var match;
330
					while ((match = lineBreakG.exec(this.input)) && match.index < this.pos) {
331
						++this.curLine;
332
						this.lineStart = match.index + match[0].length;
333
					}
334
				}
335
				if (this.options.onComment) {
336
					var current = this.input.length;
337
					this.pos = current;
338
					this.options.onComment(true, this.input.slice(start + 2, current), start, current, startLoc, this.curPosition());
339
				}
340
			};
341
		});
342
	}
343
	
344
	/**
345
	 * Reset all variables
346
	 */
347
	function reset() {
348
		comments = [];
349
		tokens = [];
350
		leadingCommentsIndex = 0;
351
		trailingCommentsIndex = 0;
352
	}
353
354
	/**
355
	 * Initialize all variables for a new parsing
356
	 */
357
	function initialize() {
358
		dependencies = {};
359
		environments = {};
360
		reset();
361
		error = null;
362
		needReset = true;
363
		currentOptions = Object.create(null);
364
		options = Object.create(null);
365
	}
366
367
	/**
368
	 * @description setup all the given options to set up the acorn parsing
369
	 * @param {String} text The given source code
370
	 * @param {Object} options The given options
371
	 * @param {Object} acorn The acorn object
372
	 * @param {Object} acornloose The acorn loose object
373
	 */
374
	function preParse(text, acorn, acornloose, file) {
375
		initialize();
376
		if (!acorn.plugins) {
377
			acorn.plugins = Object.create(null); 
378
		}
379
		acorn.plugins.acornPlugin = acornPlugin;
380
		// enabled plugins
381
		options.plugins = {
382
			"acornPlugin" : true
383
		};
384
385
		if (!acornloose.pluginsLoose) {
386
			acornloose.pluginsLoose = Object.create(null);
387
		}
388
		acornloose.pluginsLoose.acornPlugin = acornPlugin;
389
390
		// enabled plugins
391
		options.pluginsLoose = {
392
			"acornPlugin" : true
393
		};
394
		options.onToken = onToken;
395
		options.onComment = onComment;
396
		options.locations = true;
397
		options.ranges = true;
398
		options.sourceFile = true;
399
		options.ecmaVersion = 6;
400
		options.directSourceFile = {
401
			name: file,
402
			text: text
403
		};
404
		currentOptions = {
405
			locations : options.locations,
406
			sourceFile : options.sourceFile,
407
			ranges : options.ranges
408
		};
409
	}
410
411
	/**
412
	 * @description set all the values in the postParse phase
413
	 * @param {Object} the given ast tree
414
	 * @param {String} text The given source code
415
	 */
416
	function postParse(ast, text, file) {
417
		if (error) {
418
			if (ast.errors) {
419
				ast.errors.push(error);
420
			} else {
421
				ast.errors = [error];
422
			}
423
		}
424
		ast.comments = comments;
425
		ast.tokens = tokens;
426
		ast.dependencies = [];
427
		for (var prop in dependencies) {
428
			if (dependencies.hasOwnProperty(prop)) {
429
				ast.dependencies.push(dependencies[prop]);
430
			}
431
		}
432
		ast.environments = environments;
433
		ast.errors = Util.serializeAstErrors(ast);
434
	}
435
23
436
	/**
24
	/**
437
	 * Provides a shared AST.
25
	 * Provides a shared AST.
Lines 442-447 Link Here
442
	 */
30
	 */
443
	function ASTManager(serviceRegistry) {
31
	function ASTManager(serviceRegistry) {
444
		this.cache = new LRU(10);
32
		this.cache = new LRU(10);
33
		this.orionAcorn = new OrionAcorn();
445
		registry = serviceRegistry;
34
		registry = serviceRegistry;
446
	}
35
	}
447
	
36
	
Lines 497-512 Link Here
497
		 * @returns {Object} The AST.
86
		 * @returns {Object} The AST.
498
		 */
87
		 */
499
		parse: function(text, file) {
88
		parse: function(text, file) {
500
			initialize();
89
			this.orionAcorn.initialize();
501
			var start = Date.now();
90
			var start = Date.now();
502
			var ast;
91
			var ast;
503
			preParse(text, acorn, acorn_loose, file);
92
			var options = Object.create(null);
93
			this.orionAcorn.preParse(text, options, acorn, acorn_loose, file);
504
			try {
94
			try {
505
				ast = acorn.parse(text, options);
95
				ast = acorn.parse(text, options);
506
			} catch(e) {
96
			} catch(e) {
507
				ast = acorn_loose.parse_dammit(text, options);
97
				ast = acorn_loose.parse_dammit(text, options);
508
			}
98
			}
509
			postParse(ast, text, file);
99
			this.orionAcorn.postParse(ast, text);
510
			logTiming(Date.now() - start);
100
			logTiming(Date.now() - start);
511
			return ast;
101
			return ast;
512
		},
102
		},
(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/orionAcorn.js (+432 lines)
Added Link Here
1
/*******************************************************************************
2
 * @license
3
 * Copyright (c) 2015, 2016 IBM Corporation and others.
4
 * All rights reserved. This program and the accompanying materials are made 
5
 * available under the terms of the Eclipse Public License v1.0 
6
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
7
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
8
 *
9
 * Contributors:
10
 *     IBM Corporation - initial API and implementation
11
 *******************************************************************************/
12
/*eslint-env amd*/
13
define([
14
	'javascript/util',
15
], function( Util) {
16
	
17
	function OrionAcorn() {
18
		this.dependencies = {};
19
		this.environments = {};
20
		this.comments = [];
21
		this.tokens = [];
22
		this.leadingCommentsIndex = 0;
23
		this.trailingCommentsIndex = 0;
24
		this.error = null;
25
		this.needReset = true;
26
		this.currentOptions = {};
27
	}
28
29
	OrionAcorn.prototype.reset = function reset() {
30
		this.comments = [];
31
		this.tokens = [];
32
		this.leadingCommentsIndex = 0;
33
		this.trailingCommentsIndex = 0;
34
	};
35
	
36
	OrionAcorn.prototype.initialize = function initialize() {
37
		this.dependencies = {};
38
		this.environments = {};
39
		this.reset();
40
		this.error = null;
41
		this.needReset = true;
42
		this.currentOptions = {};
43
	};
44
45
	/**
46
	 * @name onToken
47
	 * @description Function called when recording a token
48
	 * @param token the given token to record
49
	 */
50
	OrionAcorn.prototype.onToken = function onToken(token) {
51
		var type = "Punctuator";
52
		var label = token.type.label;
53
		var eof = false;
54
		var value = token.value;
55
		switch(label) {
56
			case "num" :
57
				//num: new TokenType("num", startsExpr),
58
				type = "Numeric";
59
				break;
60
			case "regexp" :
61
				//regexp: new TokenType("regexp", startsExpr),
62
				type = "RegularExpression";
63
				token.value = "/" + value.pattern + "/" + (value.flags ? value.flags : "");
64
				break;
65
			case "string" :
66
				//string: new TokenType("string", startsExpr),
67
				type = "String";
68
				break;
69
			case "name" :
70
				// name: new TokenType("name", startsExpr),
71
				type = "Identifier";
72
				break;
73
			case "eof" :
74
				//eof: new TokenType("eof)
75
				eof = true;
76
				break;
77
			default:
78
				var keyword = token.type.keyword;
79
				if (keyword) {
80
					switch(keyword) {
81
						case "null" :
82
							type = "Null";
83
							break;
84
						case "true" :
85
						case "false" :
86
							type = "Boolean";
87
							break;
88
						default: 
89
							type = "Keyword";
90
					}
91
				}
92
		}
93
		if (!eof) {
94
			if (typeof value === "undefined") {
95
				token.value = label;
96
			}
97
			token.type = type;
98
			token.index = this.tokens.length;
99
			this.tokens.push(token);
100
		}
101
	};
102
	
103
	/**
104
	 * @name onComment
105
	 * @description function called when a comment is recorded
106
	 * @param block a boolean to indicate if this is a block comment (true) or a line comment (false)
107
	 * @param text the given comment contents
108
	 * @param start the given start position
109
	 * @param end the given end position
110
	 * @param startLoc the given start location
111
	 * @param endLoc the given end location
112
	 */
113
	OrionAcorn.prototype.onComment = function onComment(block, text, start, end, startLoc, endLoc) {
114
		var comment = {
115
			type: block ? 'Block' : 'Line',
116
			value: text,
117
			start: start,
118
			end: end
119
		};
120
		if (this.currentOptions.locations) {
121
			comment.loc = {
122
				start: startLoc,
123
				end: endLoc,
124
				sourceFile: this.currentOptions.sourceFile
125
			};
126
		}
127
		if (this.currentOptions.ranges) {
128
			comment.range = [start, end];
129
		}
130
		this.comments.push(comment);
131
	};
132
133
	/**
134
	 * Define an acorn plugin to record the comments even if there are syntax errors (incomplete block comments),
135
	 * it linked comments and nodes (leadingComments and trailingComments) and it records environments and dependencies
136
	 */
137
	OrionAcorn.prototype.acornPlugin = function acornPlugin(instance, opts) {
138
		if (!opts) {
139
			return;
140
		}
141
		var that = this;
142
		/**
143
		 * Returns a deep copy of the given obj
144
		 */
145
		function deepCopy(obj) {
146
			var ret = {}, key, val;
147
			for (key in obj) {
148
				if (obj.hasOwnProperty(key)) {
149
					val = obj[key];
150
					if (typeof val === 'object' && val !== null) {
151
						ret[key] = deepCopy(val);
152
					} else {
153
						ret[key] = val;
154
					}
155
				}
156
			}
157
			return ret;
158
		}
159
160
		instance.extend("raise", function(nextMethod) {
161
			return function (pos, message) {
162
				try {
163
					return nextMethod.call(this, pos, message);
164
				} catch(err) {
165
					if (err instanceof SyntaxError) {
166
						if (this.pos === this.input.length) {
167
							err.type = Util.ErrorTypes.EndOfInput;
168
						} else {
169
							err.type = Util.ErrorTypes.Unexpected;
170
						}
171
						if (that.needReset) {
172
							// we only reset tokens once. We don't want to reset them again when the syntax error is thrown during acorn_loose parsing
173
							that.reset();
174
							that.needReset = false;
175
						}
176
					}
177
					that.error = err;
178
					err.index = pos;
179
					throw err;
180
				}
181
			};
182
		});
183
		instance.extend("startNode", function(nextMethod) {
184
			return function () {
185
				var node = nextMethod.call(this);
186
				// attach leading comments
187
				var max = that.comments.length;
188
				if (max !== that.leadingCommentsIndex) {
189
					// we got new comments since the last node
190
					var i = that.leadingCommentsIndex;
191
					loop: for (; i< max; i++) {
192
						var comment = that.comments[i];
193
						if (node.range[0] >= comment.range[1]) {
194
							// attach the comment to the node
195
							if (!node.leadingComments) {
196
								node.leadingComments = [];
197
							}
198
							node.leadingComments.push(deepCopy(that.comments[i]));
199
						} else {
200
							break loop;
201
						}
202
					}
203
					that.leadingCommentsIndex = i;
204
				}
205
				return node;
206
			};
207
		});
208
		instance.extend("finishNode", function(nextMethod) {
209
			/**
210
			 * @description Collects the dependencies from call expressions and new expressions
211
			 * @param {Node} callee The named callee node 
212
			 * @param {Array.<Node>} args The list of arguments for the expression
213
			 * ORION
214
			 */
215
			function collectDeps(callee, args, extra) {
216
				if(extra.deps) {
217
					var len = args.length;
218
					if(callee.name === 'importScripts') {
219
						addArrayDeps(args, extra); //importScripts('foo', 'bar'...)
220
					} else if(callee.name === 'Worker') {
221
						addDep(args[0], extra);
222
					} else if(callee.name === 'require') {
223
						var _a = args[0];
224
						if(_a.type === "ArrayExpression") {
225
							extra.envs.node = true;
226
							addArrayDeps(_a.elements, extra); //require([foo])
227
						} else if(_a.type === "Literal") {
228
							extra.envs.node = true;
229
							addDep(_a, extra); // require('foo')
230
						}
231
						if(len > 1) {
232
							_a = args[1];
233
							if(_a.type === "ArrayExpression") {
234
								extra.envs.node = true;
235
								addArrayDeps(_a.elements, extra);
236
							}
237
						}
238
					} else if(callee.name === 'requirejs') {
239
						_a = args[0];
240
						if(_a.type === "ArrayExpression") {
241
							extra.envs.amd = true;
242
							addArrayDeps(_a.elements, extra); //requirejs([foo])
243
						}
244
					} else if(callee.name === 'define' && len > 1) {//second arg must be array
245
						_a = args[0];
246
						if(_a.type === "Literal") {
247
							_a = args[1];
248
						}
249
						if(_a.type === "ArrayExpression") {
250
							extra.envs.amd = true;
251
							addArrayDeps(_a.elements, extra);
252
						}
253
					}
254
				}
255
			}
256
			
257
				/**
258
			 * @description Adds all of the entries from the array of deps to the global state
259
			 * @param {Array} array The array of deps to add
260
			 * ORION
261
			 */
262
			function addArrayDeps(array, extra) {
263
				if(extra.deps) {
264
					var len = array.length;
265
					for(var i = 0; i < len; i++) {
266
						addDep(array[i], extra);
267
					}
268
				}
269
			}
270
			
271
				/**
272
			 * @description Adds a dependency if it has not already been added
273
			 * @param {Object} node The AST node
274
			 */
275
			function addDep(node, extra) {
276
				if(extra.deps && node.type === "Literal") {
277
					if (!extra.deps[node.value]) {
278
						extra.deps[node.value] = 1;
279
					}
280
				}
281
			}
282
			return function(node, type) {
283
				if (type === "CallExpression" || type === "NewExpression") {
284
					var extra = {
285
						deps: {},
286
						envs: {}
287
					};
288
					collectDeps(node.callee, node.arguments, extra);
289
					// copy all properties from extra.envs into environments
290
					var env = extra.envs;
291
					for (var prop in env) {
292
						if (env.hasOwnProperty(prop)) {
293
							that.environments[prop] = env[prop];
294
						}
295
					}
296
					var deps = extra.deps;
297
					// copy all properties from extra.deps into dependencies
298
					for (var dep in deps) {
299
						if (deps.hasOwnProperty(dep) && !that.dependencies.hasOwnProperty(dep)) {
300
							that.dependencies[dep] = {type: "Literal", value: dep };
301
						}
302
					}
303
				}
304
				var result = nextMethod.call(this, node, type);
305
				// attach trailing comments
306
				var max = that.comments.length;
307
				if (max !== that.trailingCommentsIndex) {
308
					// we got new comments since the last node
309
					var i = that.trailingCommentsIndex;
310
					loop: for (; i< max; i++) {
311
						var comment = that.comments[i];
312
						if (result.range[1] <= comment.range[0]) {
313
							// attach the comment to the node
314
							if (!result.trailingComments) {
315
								result.trailingComments = [];
316
							}
317
							result.trailingComments.push(deepCopy(that.comments[i]));
318
						} else {
319
							break loop;
320
						}
321
					}
322
					that.trailingCommentsIndex = i;
323
				}
324
				return result;
325
			};
326
		});
327
		instance.extend("skipBlockComment", function(nextMethod) {
328
			return function() {
329
				var lineBreak = /\r\n?|\n|\u2028|\u2029/;
330
				var lineBreakG = new RegExp(lineBreak.source, "g");
331
332
				var startLoc = this.curPosition();
333
				var start = this.pos, end = this.input.indexOf("*/", this.pos += 2);
334
				if (end !== -1) {
335
					this.pos -= 2;
336
					return nextMethod.call(this);
337
				}
338
				this.pos += 2;
339
				// error recovery: the block comment is not complete
340
				if (this.options.locations) {
341
					lineBreakG.lastIndex = start;
342
					var match;
343
					while ((match = lineBreakG.exec(this.input)) && match.index < this.pos) {
344
						++this.curLine;
345
						this.lineStart = match.index + match[0].length;
346
					}
347
				}
348
				if (this.options.onComment) {
349
					var current = this.input.length;
350
					this.pos = current;
351
					this.options.onComment(true, this.input.slice(start + 2, current), start, current, startLoc, this.curPosition());
352
				}
353
			};
354
		});
355
	};
356
357
	/**
358
	 * @description setup all the given options to set up the acorn parsing
359
	 * @param {String} text The given source code
360
	 * @param {Object} options The given options
361
	 * @param {Object} acorn The acorn object
362
	 * @param {Object} acornloose The acorn loose object
363
	 * @param {Object} file the given file
364
	 */
365
	OrionAcorn.prototype.preParse = function preParse(text, options, acorn, acornloose, file) {
366
		this.initialize();
367
		if (!acorn.plugins) {
368
			acorn.plugins = Object.create(null); 
369
		}
370
		acorn.plugins.acornPlugin = this.acornPlugin.bind(this);
371
		// enabled plugins
372
		options.plugins = {
373
			"acornPlugin" : true
374
		};
375
376
		if (!acornloose.pluginsLoose) {
377
			acornloose.pluginsLoose = Object.create(null);
378
		}
379
		acornloose.pluginsLoose.acornPlugin = this.acornPlugin.bind(this);
380
381
		// enabled plugins
382
		options.pluginsLoose = {
383
			"acornPlugin" : true
384
		};
385
		options.onToken = this.onToken.bind(this);
386
		options.onComment = this.onComment.bind(this);
387
		options.locations = true;
388
		options.ranges = true;
389
		options.sourceFile = false;
390
		options.sourceType = "script";
391
		options.ecmaVersion = 6;
392
		if (!options.directSourceFile && file) {
393
			options.directSourceFile = {
394
				name: file,
395
				text: text
396
			};
397
		}
398
399
		this.currentOptions = {
400
			locations : options.locations,
401
			sourceFile : options.sourceFile,
402
			ranges : options.ranges
403
		};
404
	};
405
406
	/**
407
	 * @description set all the values in the postParse phase
408
	 * @param {Object} the given ast tree
409
	 * @param {String} text The given source code
410
	 */
411
	OrionAcorn.prototype.postParse = function postParse(ast, text) {
412
		if (this.error) {
413
			if (ast.errors) {
414
				ast.errors.push(this.error);
415
			} else {
416
				ast.errors = [this.error];
417
			}
418
		}
419
		ast.comments = this.comments;
420
		ast.tokens = this.tokens;
421
		ast.dependencies = [];
422
		for (var prop in this.dependencies) {
423
			if (this.dependencies.hasOwnProperty(prop)) {
424
				ast.dependencies.push(this.dependencies[prop]);
425
			}
426
		}
427
		ast.environments = this.environments;
428
		ast.errors = Util.serializeAstErrors(ast);
429
	};
430
431
	return OrionAcorn;
432
});
(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/ternPlugins/ast.js (-408 / +8 lines)
Lines 12-425 Link Here
12
/*eslint-env amd */
12
/*eslint-env amd */
13
define([
13
define([
14
	"tern/lib/tern",
14
	"tern/lib/tern",
15
	"javascript/util"
15
	"javascript/orionAcorn"
16
], function(tern, Util) {
16
], function(tern, OrionAcorn) {
17
18
	var dependencies,
19
		environments,
20
		comments,
21
		tokens,
22
		leadingCommentsIndex,
23
		trailingCommentsIndex,
24
		needReset,
25
		currentOptions,
26
		error;
27
28
	/**
29
	 * @name onToken
30
	 * @description Function called when recording a token
31
	 * @param token the given token to record
32
	 */
33
	function onToken(token) {
34
		var type = "Punctuator";
35
		var label = token.type.label;
36
		var eof = false;
37
		var value = token.value;
38
		switch(label) {
39
			case "num" :
40
				//num: new TokenType("num", startsExpr),
41
				type = "Numeric";
42
				break;
43
			case "regexp" :
44
				//regexp: new TokenType("regexp", startsExpr),
45
				type = "RegularExpression";
46
				token.value = "/" + value.pattern + "/" + (value.flags ? value.flags : "");
47
				break;
48
			case "string" :
49
				//string: new TokenType("string", startsExpr),
50
				type = "String";
51
				break;
52
			case "name" :
53
				// name: new TokenType("name", startsExpr),
54
				type = "Identifier";
55
				break;
56
			case "eof" :
57
				//eof: new TokenType("eof)
58
				eof = true;
59
				break;
60
			default:
61
				var keyword = token.type.keyword;
62
				if (keyword) {
63
					switch(keyword) {
64
						case "null" :
65
							type = "Null";
66
							break;
67
						case "true" :
68
						case "false" :
69
							type = "Boolean";
70
							break;
71
						default: 
72
							type = "Keyword";
73
					}
74
				}
75
		}
76
		if (!eof) {
77
			if (typeof value === "undefined") {
78
				token.value = label;
79
			}
80
			token.type = type;
81
			token.index = tokens.length;
82
			tokens.push(token);
83
		}
84
	}
85
86
	/**
87
	 * @name onComment
88
	 * @description function called when a comment is recorded
89
	 * @param block a boolean to indicate if this is a block comment (true) or a line comment (false)
90
	 * @param text the given comment contents
91
	 * @param start the given start position
92
	 * @param end the given end position
93
	 * @param startLoc the given start location
94
	 * @param endLoc the given end location
95
	 */
96
	function onComment(block, text, start, end, startLoc, endLoc) {
97
		var comment = {
98
			type: block ? 'Block' : 'Line',
99
			value: text,
100
			start: start,
101
			end: end
102
		};
103
		if (currentOptions.locations) {
104
			comment.loc = {
105
				start: startLoc,
106
				end: endLoc,
107
				sourceFile: currentOptions.sourceFile
108
			};
109
		}
110
		if (currentOptions.ranges) {
111
			comment.range = [start, end];
112
		}
113
		comments.push(comment);
114
	}
115
116
	/**
117
	 * Define an acorn plugin to record the comments even if there are syntax errors (incomplete block comments),
118
	 * it linked comments and nodes (leadingComments and trailingComments) and it records environments and dependencies
119
	 */
120
	function acornPlugin (instance, opts) {
121
		if (!opts) {
122
			return;
123
		}
124
		/**
125
		 * Returns a deep copy of the given obj
126
		 */
127
		function deepCopy(obj) {
128
			var ret = {}, key, val;
129
			for (key in obj) {
130
				if (obj.hasOwnProperty(key)) {
131
					val = obj[key];
132
					if (typeof val === 'object' && val !== null) {
133
						ret[key] = deepCopy(val);
134
					} else {
135
						ret[key] = val;
136
					}
137
				}
138
			}
139
			return ret;
140
		}
141
142
		instance.extend("raise", function(nextMethod) {
143
			return function (pos, message) {
144
				try {
145
					return nextMethod.call(this, pos, message);
146
				} catch(err) {
147
					if (err instanceof SyntaxError) {
148
						if (this.pos === this.input.length) {
149
							err.type = Util.ErrorTypes.EndOfInput;
150
						} else {
151
							err.type = Util.ErrorTypes.Unexpected;
152
						}
153
						if (needReset) {
154
							// we only reset tokens once. We don't want to reset them again when the syntax error is thrown during acorn_loose parsing
155
							reset();
156
							needReset = false;
157
						}
158
					}
159
					error = err;
160
					err.index = pos;
161
					throw err;
162
				}
163
			};
164
		});
165
		instance.extend("startNode", function(nextMethod) {
166
			return function () {
167
				var node = nextMethod.call(this);
168
				// attach leading comments
169
				var max = comments.length;
170
				if (max !== leadingCommentsIndex) {
171
					// we got new comments since the last node
172
					var i = leadingCommentsIndex;
173
					loop: for (; i< max; i++) {
174
						var comment = comments[i];
175
						if (node.range[0] >= comment.range[1]) {
176
							// attach the comment to the node
177
							if (!node.leadingComments) {
178
								node.leadingComments = [];
179
							}
180
							node.leadingComments.push(deepCopy(comments[i]));
181
						} else {
182
							break loop;
183
						}
184
					}
185
					leadingCommentsIndex = i;
186
				}
187
				return node;
188
			};
189
		});
190
		instance.extend("finishNode", function(nextMethod) {
191
			/**
192
			 * @description Collects the dependencies from call expressions and new expressions
193
			 * @param {Node} callee The named callee node 
194
			 * @param {Array.<Node>} args The list of arguments for the expression
195
			 * ORION
196
			 */
197
			function collectDeps(callee, args, extra) {
198
				if(extra.deps) {
199
					var len = args.length;
200
					if(callee.name === 'importScripts') {
201
						addArrayDeps(args, extra); //importScripts('foo', 'bar'...)
202
					} else if(callee.name === 'Worker') {
203
						addDep(args[0], extra);
204
					} else if(callee.name === 'require') {
205
						var _a = args[0];
206
						if(_a.type === "ArrayExpression") {
207
							extra.envs.node = true;
208
							addArrayDeps(_a.elements, extra); //require([foo])
209
						} else if(_a.type === "Literal") {
210
							extra.envs.node = true;
211
							addDep(_a, extra); // require('foo')
212
						}
213
						if(len > 1) {
214
							_a = args[1];
215
							if(_a.type === "ArrayExpression") {
216
								extra.envs.node = true;
217
								addArrayDeps(_a.elements, extra);
218
							}
219
						}
220
					} else if(callee.name === 'requirejs') {
221
						_a = args[0];
222
						if(_a.type === "ArrayExpression") {
223
							extra.envs.amd = true;
224
							addArrayDeps(_a.elements, extra); //requirejs([foo])
225
						}
226
					} else if(callee.name === 'define' && len > 1) {//second arg must be array
227
						_a = args[0];
228
						if(_a.type === "Literal") {
229
							_a = args[1];
230
						}
231
						if(_a.type === "ArrayExpression") {
232
							extra.envs.amd = true;
233
							addArrayDeps(_a.elements, extra);
234
						}
235
					}
236
				}
237
			}
238
			
239
				/**
240
			 * @description Adds all of the entries from the array of deps to the global state
241
			 * @param {Array} array The array of deps to add
242
			 * ORION
243
			 */
244
			function addArrayDeps(array, extra) {
245
				if(extra.deps) {
246
					var len = array.length;
247
					for(var i = 0; i < len; i++) {
248
						addDep(array[i], extra);
249
					}
250
				}
251
			}
252
			
253
				/**
254
			 * @description Adds a dependency if it has not already been added
255
			 * @param {Object} node The AST node
256
			 */
257
			function addDep(node, extra) {
258
				if(extra.deps && node.type === "Literal") {
259
					if (!extra.deps[node.value]) {
260
						extra.deps[node.value] = 1;
261
					}
262
				}
263
			}
264
			return function(node, type) {
265
				if (type === "CallExpression" || type === "NewExpression") {
266
					var extra = {
267
						deps: {},
268
						envs: {}
269
					};
270
					collectDeps(node.callee, node.arguments, extra);
271
					// copy all properties from extra.envs into environments
272
					var env = extra.envs;
273
					for (var prop in env) {
274
						if (env.hasOwnProperty(prop)) {
275
							environments[prop] = env[prop];
276
						}
277
					}
278
					var deps = extra.deps;
279
					// copy all properties from extra.deps into dependencies
280
					for (var dep in deps) {
281
						if (deps.hasOwnProperty(dep) && !dependencies.hasOwnProperty(dep)) {
282
							dependencies[dep] = {type: "Literal", value: dep };
283
						}
284
					}
285
				}
286
				var result = nextMethod.call(this, node, type);
287
				// attach trailing comments
288
				var max = comments.length;
289
				if (max !== trailingCommentsIndex) {
290
					// we got new comments since the last node
291
					var i = trailingCommentsIndex;
292
					loop: for (; i< max; i++) {
293
						var comment = comments[i];
294
						if (result.range[1] <= comment.range[0]) {
295
							// attach the comment to the node
296
							if (!result.trailingComments) {
297
								result.trailingComments = [];
298
							}
299
							result.trailingComments.push(deepCopy(comments[i]));
300
						} else {
301
							break loop;
302
						}
303
					}
304
					trailingCommentsIndex = i;
305
				}
306
				return result;
307
			};
308
		});
309
		instance.extend("skipBlockComment", function(nextMethod) {
310
			return function() {
311
				var lineBreak = /\r\n?|\n|\u2028|\u2029/;
312
				var lineBreakG = new RegExp(lineBreak.source, "g");
313
314
				var startLoc = this.curPosition();
315
				var start = this.pos, end = this.input.indexOf("*/", this.pos += 2);
316
				if (end !== -1) {
317
					this.pos -= 2;
318
					return nextMethod.call(this);
319
				}
320
				this.pos += 2;
321
				// error recovery: the block comment is not complete
322
				if (this.options.locations) {
323
					lineBreakG.lastIndex = start;
324
					var match;
325
					while ((match = lineBreakG.exec(this.input)) && match.index < this.pos) {
326
						++this.curLine;
327
						this.lineStart = match.index + match[0].length;
328
					}
329
				}
330
				if (this.options.onComment) {
331
					var current = this.input.length;
332
					this.pos = current;
333
					this.options.onComment(true, this.input.slice(start + 2, current), start, current, startLoc, this.curPosition());
334
				}
335
			};
336
		});
337
	}
338
	
17
	
339
	function reset() {
18
	var orionAcorn = new OrionAcorn();
340
		comments = [];
341
		tokens = [];
342
		leadingCommentsIndex = 0;
343
		trailingCommentsIndex = 0;
344
	}
345
346
	function initialize() {
347
		dependencies = {};
348
		environments = {};
349
		reset();
350
		error = null;
351
		needReset = true;
352
		currentOptions = {};
353
	}
354
355
	/**
356
	 * @description setup all the given options to set up the acorn parsing
357
	 * @param {String} text The given source code
358
	 * @param {Object} options The given options
359
	 * @param {Object} acorn The acorn object
360
	 * @param {Object} acornloose The acorn loose object
361
	 */
362
	function preParse(text, options, acorn, acornloose) {
363
		initialize();
364
		if (!acorn.plugins) {
365
			acorn.plugins = Object.create(null); 
366
		}
367
		acorn.plugins.acornPlugin = acornPlugin;
368
		// enabled plugins
369
		options.plugins = {
370
			"acornPlugin" : true
371
		};
372
373
		if (!acornloose.pluginsLoose) {
374
			acornloose.pluginsLoose = Object.create(null);
375
		}
376
		acornloose.pluginsLoose.acornPlugin = acornPlugin;
377
378
		// enabled plugins
379
		options.pluginsLoose = {
380
			"acornPlugin" : true
381
		};
382
		options.onToken = onToken;
383
		options.onComment = onComment;
384
		options.locations = true;
385
		options.ranges = true;
386
		options.sourceFile = true;
387
		options.ecmaVersion = 6;
388
		currentOptions = {
389
			locations : options.locations,
390
			sourceFile : options.sourceFile,
391
			ranges : options.ranges
392
		};
393
	}
394
395
	/**
396
	 * @description set all the values in the postParse phase
397
	 * @param {Object} the given ast tree
398
	 * @param {String} text The given source code
399
	 */
400
	function postParse(ast, text) {
401
		if (error) {
402
			if (ast.errors) {
403
				ast.errors.push(error);
404
			} else {
405
				ast.errors = [error];
406
			}
407
		}
408
		ast.comments = comments;
409
		ast.tokens = tokens;
410
		ast.dependencies = [];
411
		for (var prop in dependencies) {
412
			if (dependencies.hasOwnProperty(prop)) {
413
				ast.dependencies.push(dependencies[prop]);
414
			}
415
		}
416
		ast.environments = environments;
417
		ast.errors = Util.serializeAstErrors(ast);
418
	}
419
19
420
	tern.registerPlugin("ast", /* @callback */ function(server, options) { //$NON-NLS-1$
20
	tern.registerPlugin("ast", /* @callback */ function(server, options) { //$NON-NLS-1$
421
		server.on("reset", function() { //$NON-NLS-1$
21
		server.on("reset", function() { //$NON-NLS-1$
422
			initialize();
22
			orionAcorn.initialize();
423
		});
23
		});
424
24
425
		return {
25
		return {
Lines 427-438 Link Here
427
				/**
27
				/**
428
				 * @callback
28
				 * @callback
429
				 */
29
				 */
430
				preParse: preParse,
30
				preParse: orionAcorn.preParse.bind(orionAcorn),
431
				/**
31
				/**
432
				 * @callback
32
				 * @callback
433
				 */
33
				 */
434
				postParse: postParse
34
				postParse: orionAcorn.postParse.bind(orionAcorn)
435
				}
35
			}
436
			};
36
		};
437
	});
37
	});
438
});
38
});

Return to bug 490629