/
pureComments.ts
87 lines (80 loc) · 2.5 KB
/
pureComments.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import * as acorn from 'acorn';
import { base as basicWalker, BaseWalker } from 'acorn-walk';
import {
CallExpression,
ChainExpression,
ExpressionStatement,
NewExpression
} from '../ast/nodes/NodeType';
import { Annotation } from '../ast/nodes/shared/Node';
// patch up acorn-walk until class-fields are officially supported
basicWalker.PropertyDefinition = function (node: any, st: any, c: any) {
if (node.computed) {
c(node.key, st, 'Expression');
}
if (node.value) {
c(node.value, st, 'Expression');
}
};
interface CommentState {
code: string;
commentIndex: number;
commentNodes: acorn.Comment[];
}
function isOnlyWhitespaceOrComments(code: string) {
// streamline the typical case
if (/^\s*$/.test(code)) return true;
try {
// successful only if it's a valid Program without statements
const ast = acorn.parse(code, { ecmaVersion: 'latest' }) as any;
return ast.body && ast.body.length === 0;
} catch {
// should never be reached -
// the entire module was previously successfully parsed
}
return false;
}
function handlePureAnnotationsOfNode(
node: acorn.Node,
state: CommentState,
type: string = node.type
) {
let commentNode = state.commentNodes[state.commentIndex];
while (commentNode && node.start >= commentNode.end) {
const between = state.code.substring(commentNode.end, node.start);
if (isOnlyWhitespaceOrComments(between)) markPureNode(node, commentNode);
commentNode = state.commentNodes[++state.commentIndex];
}
if (commentNode && commentNode.end <= node.end) {
(basicWalker as BaseWalker<CommentState>)[type](node, state, handlePureAnnotationsOfNode);
}
}
function markPureNode(
node: acorn.Node & { _rollupAnnotations?: Annotation[] },
comment: acorn.Comment
) {
if (node._rollupAnnotations) {
node._rollupAnnotations.push({ comment });
} else {
node._rollupAnnotations = [{ comment }];
}
while (node.type === ExpressionStatement || node.type === ChainExpression) {
node = (node as any).expression;
}
if (node.type === CallExpression || node.type === NewExpression) {
if (node._rollupAnnotations) {
node._rollupAnnotations.push({ pure: true });
} else {
node._rollupAnnotations = [{ pure: true }];
}
}
}
const pureCommentRegex = /[@#]__PURE__/;
const isPureComment = (comment: acorn.Comment) => pureCommentRegex.test(comment.value);
export function markPureCallExpressions(comments: acorn.Comment[], esTreeAst: acorn.Node, code: string) {
handlePureAnnotationsOfNode(esTreeAst, {
code,
commentIndex: 0,
commentNodes: comments.filter(isPureComment)
});
}