-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathutil.ts
More file actions
115 lines (106 loc) · 3.16 KB
/
util.ts
File metadata and controls
115 lines (106 loc) · 3.16 KB
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { AttachedScope, attachScopes } from '@rollup/pluginutils';
import { Node, walk } from 'estree-walker';
import isReference from 'is-reference';
import type { AstNode } from 'rollup';
import type { MemberExpression } from 'estree';
const META_START = '// ==UserScript==';
const META_END = '// ==/UserScript==';
const GRANTS_REGEXP = /^(unsafeWindow$|GM[._]\w+)/;
export function collectGrants(ast: AstNode) {
let scope = attachScopes(ast, 'scope');
const grantSetPerFile = new Set();
walk(ast as Node, {
enter(node: Node & { scope: AttachedScope }, parent) {
if (node.scope) scope = node.scope;
if (
node.type === 'MemberExpression' &&
isReference(node, parent)
) {
const fullName = getMemberExpressionFullNameRecursive(node);
const match = GRANTS_REGEXP.exec(fullName);
if (match) {
grantSetPerFile.add(match[0]);
this.skip();
}
}
if (
node.type === 'Identifier' &&
isReference(node, parent) &&
!scope.contains(node.name)
) {
const match = GRANTS_REGEXP.exec(node.name);
if (match) {
grantSetPerFile.add(match[0]);
}
}
},
leave(node: Node & { scope: AttachedScope }) {
if (node.scope) scope = scope.parent;
},
});
return grantSetPerFile;
}
function getMemberExpressionFullNameRecursive(astNode: MemberExpression): string | null {
if (astNode.property.type !== 'Identifier') {
return null;
}
switch (astNode.object.type) {
case 'MemberExpression': {
const nameSoFar = getMemberExpressionFullNameRecursive(astNode.object);
if (nameSoFar == null) {
return null;
}
return `${nameSoFar}.${astNode.property.name}`
}
case 'Identifier': {
return `${astNode.object.name}.${astNode.property.name}`;
}
default: {
return null;
}
}
}
export function getMetadata(
metaFileContent: string,
additionalGrantList: Set<string>,
) {
const lines = metaFileContent.split('\n').map((line) => line.trim());
const start = lines.indexOf(META_START);
const end = lines.indexOf(META_END);
if (start < 0 || end < 0) {
throw new Error(
'Invalid metadata block. For more details see https://violentmonkey.github.io/api/metadata-block/',
);
}
const grantSet = new Set<string>();
const entries = lines
.slice(start + 1, end)
.map((line) => {
if (!line.startsWith('// ')) return;
line = line.slice(3).trim();
const matches = line.match(/^(\S+)(\s.*)?$/);
if (!matches) return;
const key = matches[1];
const value = (matches[2] || '').trim();
if (key === '@grant') {
grantSet.add(value);
return;
}
return [key, value];
})
.filter(Boolean);
for (const item of additionalGrantList) {
grantSet.add(item);
}
const grantList = Array.from(grantSet);
grantList.sort();
for (const item of grantList) {
entries.push(['@grant', item]);
}
const maxKeyWidth = Math.max(...entries.map(([key]) => key.length));
return [
META_START,
...entries.map(([key, value]) => `// ${key.padEnd(maxKeyWidth)} ${value}`),
META_END,
].join('\n');
}