UNPKG

18.3 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2013 Palantir Technologies, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17"use strict";
18Object.defineProperty(exports, "__esModule", { value: true });
19var path = require("path");
20var ts = require("typescript");
21function getSourceFile(fileName, source) {
22 var normalizedName = path.normalize(fileName).replace(/\\/g, "/");
23 var compilerOptions = createCompilerOptions();
24 var compilerHost = {
25 fileExists: function () { return true; },
26 getCanonicalFileName: function (filename) { return filename; },
27 getCurrentDirectory: function () { return ""; },
28 getDefaultLibFileName: function () { return "lib.d.ts"; },
29 getDirectories: function (_path) { return []; },
30 getNewLine: function () { return "\n"; },
31 getSourceFile: function (filenameToGet) {
32 var target = compilerOptions.target == null ? ts.ScriptTarget.ES5 : compilerOptions.target;
33 return ts.createSourceFile(filenameToGet, source, target, true);
34 },
35 readFile: function (x) { return x; },
36 useCaseSensitiveFileNames: function () { return true; },
37 writeFile: function (x) { return x; },
38 };
39 var program = ts.createProgram([normalizedName], compilerOptions, compilerHost);
40 return program.getSourceFile(normalizedName);
41}
42exports.getSourceFile = getSourceFile;
43function createCompilerOptions() {
44 return {
45 allowJs: true,
46 noResolve: true,
47 target: ts.ScriptTarget.ES5,
48 };
49}
50exports.createCompilerOptions = createCompilerOptions;
51function doesIntersect(failure, disabledIntervals) {
52 return disabledIntervals.some(function (interval) {
53 var maxStart = Math.max(interval.startPosition, failure.getStartPosition().getPosition());
54 var minEnd = Math.min(interval.endPosition, failure.getEndPosition().getPosition());
55 return maxStart <= minEnd;
56 });
57}
58exports.doesIntersect = doesIntersect;
59/** @deprecated use forEachToken instead */
60function scanAllTokens(scanner, callback) {
61 var lastStartPos = -1;
62 while (scanner.scan() !== ts.SyntaxKind.EndOfFileToken) {
63 var startPos = scanner.getStartPos();
64 if (startPos === lastStartPos) {
65 break;
66 }
67 lastStartPos = startPos;
68 callback(scanner);
69 }
70}
71exports.scanAllTokens = scanAllTokens;
72/**
73 * @returns true if any modifier kinds passed along exist in the given modifiers array
74 */
75function hasModifier(modifiers) {
76 var modifierKinds = [];
77 for (var _i = 1; _i < arguments.length; _i++) {
78 modifierKinds[_i - 1] = arguments[_i];
79 }
80 if (modifiers === undefined || modifierKinds.length === 0) {
81 return false;
82 }
83 return modifiers.some(function (m) {
84 return modifierKinds.some(function (k) { return m.kind === k; });
85 });
86}
87exports.hasModifier = hasModifier;
88/**
89 * Determines if the appropriate bit in the parent (VariableDeclarationList) is set,
90 * which indicates this is a "let" or "const".
91 */
92function isBlockScopedVariable(node) {
93 var parentNode = (node.kind === ts.SyntaxKind.VariableDeclaration)
94 ? node.parent
95 : node.declarationList;
96 return isNodeFlagSet(parentNode, ts.NodeFlags.Let)
97 || isNodeFlagSet(parentNode, ts.NodeFlags.Const);
98}
99exports.isBlockScopedVariable = isBlockScopedVariable;
100function isBlockScopedBindingElement(node) {
101 var variableDeclaration = getBindingElementVariableDeclaration(node);
102 // if no variable declaration, it must be a function param, which is block scoped
103 return (variableDeclaration == null) || isBlockScopedVariable(variableDeclaration);
104}
105exports.isBlockScopedBindingElement = isBlockScopedBindingElement;
106function getBindingElementVariableDeclaration(node) {
107 var currentParent = node.parent;
108 while (currentParent.kind !== ts.SyntaxKind.VariableDeclaration) {
109 if (currentParent.parent == null) {
110 return null; // function parameter, no variable declaration
111 }
112 else {
113 currentParent = currentParent.parent;
114 }
115 }
116 return currentParent;
117}
118exports.getBindingElementVariableDeclaration = getBindingElementVariableDeclaration;
119/**
120 * Finds a child of a given node with a given kind.
121 * Note: This uses `node.getChildren()`, which does extra parsing work to include tokens.
122 */
123function childOfKind(node, kind) {
124 return node.getChildren().find(function (child) { return child.kind === kind; });
125}
126exports.childOfKind = childOfKind;
127/**
128 * @returns true if some ancestor of `node` satisfies `predicate`, including `node` itself.
129 */
130function someAncestor(node, predicate) {
131 return predicate(node) || (node.parent != null && someAncestor(node.parent, predicate));
132}
133exports.someAncestor = someAncestor;
134function isAssignment(node) {
135 if (node.kind === ts.SyntaxKind.BinaryExpression) {
136 var binaryExpression = node;
137 return binaryExpression.operatorToken.kind >= ts.SyntaxKind.FirstAssignment
138 && binaryExpression.operatorToken.kind <= ts.SyntaxKind.LastAssignment;
139 }
140 else {
141 return false;
142 }
143}
144exports.isAssignment = isAssignment;
145/**
146 * Bitwise check for node flags.
147 */
148function isNodeFlagSet(node, flagToCheck) {
149 // tslint:disable-next-line:no-bitwise
150 return (node.flags & flagToCheck) !== 0;
151}
152exports.isNodeFlagSet = isNodeFlagSet;
153/**
154 * Bitwise check for combined node flags.
155 */
156function isCombinedNodeFlagSet(node, flagToCheck) {
157 // tslint:disable-next-line:no-bitwise
158 return (ts.getCombinedNodeFlags(node) & flagToCheck) !== 0;
159}
160exports.isCombinedNodeFlagSet = isCombinedNodeFlagSet;
161/**
162 * Bitwise check for combined modifier flags.
163 */
164function isCombinedModifierFlagSet(node, flagToCheck) {
165 // tslint:disable-next-line:no-bitwise
166 return (ts.getCombinedModifierFlags(node) & flagToCheck) !== 0;
167}
168exports.isCombinedModifierFlagSet = isCombinedModifierFlagSet;
169/**
170 * Bitwise check for type flags.
171 */
172function isTypeFlagSet(type, flagToCheck) {
173 // tslint:disable-next-line:no-bitwise
174 return (type.flags & flagToCheck) !== 0;
175}
176exports.isTypeFlagSet = isTypeFlagSet;
177/**
178 * Bitwise check for symbol flags.
179 */
180function isSymbolFlagSet(symbol, flagToCheck) {
181 // tslint:disable-next-line:no-bitwise
182 return (symbol.flags & flagToCheck) !== 0;
183}
184exports.isSymbolFlagSet = isSymbolFlagSet;
185/**
186 * Bitwise check for object flags.
187 * Does not work with TypeScript 2.0.x
188 */
189function isObjectFlagSet(objectType, flagToCheck) {
190 // tslint:disable-next-line:no-bitwise
191 return (objectType.objectFlags & flagToCheck) !== 0;
192}
193exports.isObjectFlagSet = isObjectFlagSet;
194/**
195 * @returns true if decl is a nested module declaration, i.e. represents a segment of a dotted module path.
196 */
197function isNestedModuleDeclaration(decl) {
198 // in a declaration expression like 'module a.b.c' - 'a' is the top level module declaration node and 'b' and 'c'
199 // are nested therefore we can depend that a node's position will only match with its name's position for nested
200 // nodes
201 return decl.name.pos === decl.pos;
202}
203exports.isNestedModuleDeclaration = isNestedModuleDeclaration;
204function unwrapParentheses(node) {
205 while (node.kind === ts.SyntaxKind.ParenthesizedExpression) {
206 node = node.expression;
207 }
208 return node;
209}
210exports.unwrapParentheses = unwrapParentheses;
211function isScopeBoundary(node) {
212 return node.kind === ts.SyntaxKind.FunctionDeclaration
213 || node.kind === ts.SyntaxKind.FunctionExpression
214 || node.kind === ts.SyntaxKind.PropertyAssignment
215 || node.kind === ts.SyntaxKind.ShorthandPropertyAssignment
216 || node.kind === ts.SyntaxKind.MethodDeclaration
217 || node.kind === ts.SyntaxKind.Constructor
218 || node.kind === ts.SyntaxKind.ModuleDeclaration
219 || node.kind === ts.SyntaxKind.ArrowFunction
220 || node.kind === ts.SyntaxKind.ParenthesizedExpression
221 || node.kind === ts.SyntaxKind.ClassDeclaration
222 || node.kind === ts.SyntaxKind.ClassExpression
223 || node.kind === ts.SyntaxKind.InterfaceDeclaration
224 || node.kind === ts.SyntaxKind.GetAccessor
225 || node.kind === ts.SyntaxKind.SetAccessor
226 || node.kind === ts.SyntaxKind.SourceFile && ts.isExternalModule(node);
227}
228exports.isScopeBoundary = isScopeBoundary;
229function isBlockScopeBoundary(node) {
230 return isScopeBoundary(node)
231 || node.kind === ts.SyntaxKind.Block
232 || isLoop(node)
233 || node.kind === ts.SyntaxKind.WithStatement
234 || node.kind === ts.SyntaxKind.SwitchStatement
235 || node.parent !== undefined
236 && (node.parent.kind === ts.SyntaxKind.TryStatement
237 || node.parent.kind === ts.SyntaxKind.IfStatement);
238}
239exports.isBlockScopeBoundary = isBlockScopeBoundary;
240function isLoop(node) {
241 return node.kind === ts.SyntaxKind.DoStatement
242 || node.kind === ts.SyntaxKind.WhileStatement
243 || node.kind === ts.SyntaxKind.ForStatement
244 || node.kind === ts.SyntaxKind.ForInStatement
245 || node.kind === ts.SyntaxKind.ForOfStatement;
246}
247exports.isLoop = isLoop;
248/**
249 * Iterate over all tokens of `node`
250 *
251 * @description JsDoc comments are treated like regular comments and only visited if `skipTrivia` === false.
252 *
253 * @param node The node whose tokens should be visited
254 * @param skipTrivia If set to false all trivia preceeding `node` or any of its children is included
255 * @param cb Is called for every token of `node`. It gets the full text of the SourceFile and the position of the token within that text.
256 * @param filter If provided, will be called for every Node and Token found. If it returns false `cb` will not be called for this subtree.
257 */
258function forEachToken(node, skipTrivia, cb, filter) {
259 // this function will most likely be called with SourceFile anyways, so there is no need for an additional parameter
260 var sourceFile = node.getSourceFile();
261 var fullText = sourceFile.text;
262 var iterateFn = filter === undefined ? iterateChildren : iterateWithFilter;
263 var handleTrivia = skipTrivia ? undefined : createTriviaHandler(sourceFile, cb);
264 iterateFn(node);
265 // this function is used to save the if condition for the common case where no filter is provided
266 function iterateWithFilter(child) {
267 if (filter(child)) {
268 return iterateChildren(child);
269 }
270 }
271 function iterateChildren(child) {
272 if (child.kind < ts.SyntaxKind.FirstNode ||
273 // for backwards compatibility to typescript 2.0.10
274 // JsxText was no Token, but a Node in that version
275 child.kind === ts.SyntaxKind.JsxText) {
276 // we found a token, tokens have no children, stop recursing here
277 return callback(child);
278 }
279 /* Exclude everything contained in JsDoc, it will be handled with the other trivia anyway.
280 * When we would handle JsDoc tokens like regular ones, we would scan some trivia multiple times.
281 * Even worse, we would scan for trivia inside the JsDoc comment, which yields unexpected results.*/
282 if (child.kind !== ts.SyntaxKind.JSDocComment) {
283 // recurse into Node's children to find tokens
284 return child.getChildren(sourceFile).forEach(iterateFn);
285 }
286 }
287 function callback(token) {
288 var tokenStart = token.getStart(sourceFile);
289 if (!skipTrivia && tokenStart !== token.pos) {
290 // we only have to handle trivia before each token, because there is nothing after EndOfFileToken
291 handleTrivia(token.pos, tokenStart, token);
292 }
293 return cb(fullText, token.kind, { tokenStart: tokenStart, fullStart: token.pos, end: token.end }, token.parent);
294 }
295}
296exports.forEachToken = forEachToken;
297function createTriviaHandler(sourceFile, cb) {
298 var fullText = sourceFile.text;
299 var scanner = ts.createScanner(sourceFile.languageVersion, false, sourceFile.languageVariant, fullText);
300 /**
301 * Scan the specified range to get all trivia tokens.
302 * This includes trailing trivia of the last token and the leading trivia of the current token
303 */
304 function handleTrivia(start, end, token) {
305 var parent = token.parent;
306 // prevent false positives by not scanning inside JsxText
307 if (!canHaveLeadingTrivia(token.kind, parent)) {
308 return;
309 }
310 scanner.setTextPos(start);
311 var position;
312 // we only get here if start !== end, so we can scan at least one time
313 do {
314 var kind = scanner.scan();
315 position = scanner.getTextPos();
316 cb(fullText, kind, { tokenStart: scanner.getTokenPos(), end: position, fullStart: start }, parent);
317 } while (position < end);
318 }
319 return handleTrivia;
320}
321/** Iterate over all comments owned by `node` or its children */
322function forEachComment(node, cb) {
323 /* Visit all tokens and skip trivia.
324 Comment ranges between tokens are parsed without the need of a scanner.
325 forEachToken also does intentionally not pay attention to the correct comment ownership of nodes as it always
326 scans all trivia before each token, which could include trailing comments of the previous token.
327 Comment onwership is done right in this function*/
328 return forEachToken(node, true, function (fullText, tokenKind, pos, parent) {
329 // don't search for comments inside JsxText
330 if (canHaveLeadingTrivia(tokenKind, parent)) {
331 // Comments before the first token (pos.fullStart === 0) are all considered leading comments, so no need for special treatment
332 var comments = ts.getLeadingCommentRanges(fullText, pos.fullStart);
333 if (comments !== undefined) {
334 for (var _i = 0, comments_1 = comments; _i < comments_1.length; _i++) {
335 var comment = comments_1[_i];
336 cb(fullText, comment.kind, { fullStart: pos.fullStart, tokenStart: comment.pos, end: comment.end });
337 }
338 }
339 }
340 if (canHaveTrailingTrivia(tokenKind, parent)) {
341 var comments = ts.getTrailingCommentRanges(fullText, pos.end);
342 if (comments !== undefined) {
343 for (var _a = 0, comments_2 = comments; _a < comments_2.length; _a++) {
344 var comment = comments_2[_a];
345 cb(fullText, comment.kind, { fullStart: pos.fullStart, tokenStart: comment.pos, end: comment.end });
346 }
347 }
348 }
349 });
350}
351exports.forEachComment = forEachComment;
352/** Exclude leading positions that would lead to scanning for trivia inside JsxText */
353function canHaveLeadingTrivia(tokenKind, parent) {
354 if (tokenKind === ts.SyntaxKind.JsxText) {
355 return false; // there is no trivia before JsxText
356 }
357 if (tokenKind === ts.SyntaxKind.OpenBraceToken) {
358 // before a JsxExpression inside a JsxElement's body can only be other JsxChild, but no trivia
359 return parent.kind !== ts.SyntaxKind.JsxExpression || parent.parent.kind !== ts.SyntaxKind.JsxElement;
360 }
361 if (tokenKind === ts.SyntaxKind.LessThanToken) {
362 if (parent.kind === ts.SyntaxKind.JsxClosingElement) {
363 return false; // would be inside the element body
364 }
365 if (parent.kind === ts.SyntaxKind.JsxOpeningElement || parent.kind === ts.SyntaxKind.JsxSelfClosingElement) {
366 // there can only be leading trivia if we are at the end of the top level element
367 return parent.parent.parent.kind !== ts.SyntaxKind.JsxElement;
368 }
369 }
370 return true;
371}
372/** Exclude trailing positions that would lead to scanning for trivia inside JsxText */
373function canHaveTrailingTrivia(tokenKind, parent) {
374 if (tokenKind === ts.SyntaxKind.JsxText) {
375 return false; // there is no trivia after JsxText
376 }
377 if (tokenKind === ts.SyntaxKind.CloseBraceToken) {
378 // after a JsxExpression inside a JsxElement's body can only be other JsxChild, but no trivia
379 return parent.kind !== ts.SyntaxKind.JsxExpression || parent.parent.kind !== ts.SyntaxKind.JsxElement;
380 }
381 if (tokenKind === ts.SyntaxKind.GreaterThanToken) {
382 if (parent.kind === ts.SyntaxKind.JsxOpeningElement) {
383 return false; // would be inside the element
384 }
385 if (parent.kind === ts.SyntaxKind.JsxClosingElement || parent.kind === ts.SyntaxKind.JsxSelfClosingElement) {
386 // there can only be trailing trivia if we are at the end of the top level element
387 return parent.parent.parent.kind !== ts.SyntaxKind.JsxElement;
388 }
389 }
390 return true;
391}
392/**
393 * Checks if there are any comments between `position` and the next non-trivia token
394 *
395 * @param text The text to scan
396 * @param position The position inside `text` where to start scanning. Make sure that this is a valid start position.
397 * This value is typically obtained from `node.getFullStart()` or `node.getEnd()`
398 */
399function hasCommentAfterPosition(text, position) {
400 return ts.getTrailingCommentRanges(text, position) !== undefined ||
401 ts.getLeadingCommentRanges(text, position) !== undefined;
402}
403exports.hasCommentAfterPosition = hasCommentAfterPosition;
404function getEqualsKind(node) {
405 switch (node.kind) {
406 case ts.SyntaxKind.EqualsEqualsToken:
407 return { isPositive: true, isStrict: false };
408 case ts.SyntaxKind.EqualsEqualsEqualsToken:
409 return { isPositive: true, isStrict: true };
410 case ts.SyntaxKind.ExclamationEqualsToken:
411 return { isPositive: false, isStrict: false };
412 case ts.SyntaxKind.ExclamationEqualsEqualsToken:
413 return { isPositive: false, isStrict: true };
414 default:
415 return undefined;
416 }
417}
418exports.getEqualsKind = getEqualsKind;