@tsrx/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@tsrx/core",
3
+ "description": "Core compiler infrastructure for tsrx-based frameworks",
4
+ "license": "MIT",
5
+ "author": "Dominic Gannaway",
6
+ "version": "0.0.1",
7
+ "type": "module",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Ripple-TS/ripple.git",
11
+ "directory": "packages/tsrx"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "default": "./src/index.js"
16
+ },
17
+ "./types": {
18
+ "types": "./types/index.d.ts"
19
+ },
20
+ "./types/estree": {
21
+ "types": "./types/estree.d.ts"
22
+ },
23
+ "./types/estree-jsx": {
24
+ "types": "./types/estree-jsx.d.ts"
25
+ },
26
+ "./types/acorn": {
27
+ "types": "./types/acorn.d.ts"
28
+ }
29
+ },
30
+ "dependencies": {
31
+ "@jridgewell/sourcemap-codec": "catalog:default",
32
+ "@sveltejs/acorn-typescript": "catalog:default",
33
+ "acorn": "catalog:default",
34
+ "esrap": "catalog:default",
35
+ "is-reference": "catalog:default",
36
+ "magic-string": "catalog:default",
37
+ "zimmerframe": "catalog:default",
38
+ "@types/estree": "catalog:default",
39
+ "@types/estree-jsx": "catalog:default"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "catalog:default",
43
+ "@typescript-eslint/types": "catalog:default",
44
+ "typescript": "catalog:default",
45
+ "@volar/language-core": "catalog:default",
46
+ "vscode-languageserver-types": "catalog:default"
47
+ },
48
+ "files": [
49
+ "src",
50
+ "types"
51
+ ]
52
+ }
@@ -0,0 +1,160 @@
1
+ /** @import * as AST from 'estree' */
2
+
3
+ import { walk } from 'zimmerframe';
4
+
5
+ /**
6
+ * True if is `:global` without arguments
7
+ * @param {AST.CSS.SimpleSelector} simple_selector
8
+ */
9
+ function is_global_block_selector(simple_selector) {
10
+ return (
11
+ simple_selector.type === 'PseudoClassSelector' &&
12
+ simple_selector.name === 'global' &&
13
+ simple_selector.args === null
14
+ );
15
+ }
16
+
17
+ /**
18
+ * True if is `:global(...)` or `:global` and no pseudo class that is scoped.
19
+ * @param {AST.CSS.RelativeSelector} relative_selector
20
+ */
21
+ function is_global(relative_selector) {
22
+ const first = relative_selector.selectors[0];
23
+
24
+ return (
25
+ first?.type === 'PseudoClassSelector' &&
26
+ first.name === 'global' &&
27
+ (first.args === null ||
28
+ // Only these two selector types keep the whole selector global, because e.g.
29
+ // :global(button).x means that the selector is still scoped because of the .x
30
+ relative_selector.selectors.every(
31
+ (selector) =>
32
+ selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector',
33
+ ))
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Analyze CSS and set metadata for global selectors
39
+ * @param {AST.CSS.Node} css - The CSS AST
40
+ */
41
+ export function analyze_css(css) {
42
+ walk(css, /** @type {{ rule: AST.CSS.Rule | null }} */ ({ rule: null }), {
43
+ Rule(node, context) {
44
+ node.metadata.parent_rule = context.state.rule;
45
+
46
+ // Check for :global blocks
47
+ // A global block is when the selector starts with :global and has no local selectors before it
48
+ for (const complex_selector of node.prelude.children) {
49
+ let is_global_block = false;
50
+
51
+ for (
52
+ let selector_idx = 0;
53
+ selector_idx < complex_selector.children.length;
54
+ selector_idx++
55
+ ) {
56
+ const child = complex_selector.children[selector_idx];
57
+ const idx = child.selectors.findIndex(is_global_block_selector);
58
+
59
+ if (is_global_block) {
60
+ // All selectors after :global are unscoped
61
+ child.metadata.is_global_like = true;
62
+ }
63
+
64
+ // Only set is_global_block if this is the FIRST RelativeSelector and it starts with :global
65
+ if (selector_idx === 0 && idx === 0) {
66
+ // `child` starts with `:global` and is the first selector in the chain
67
+ is_global_block = true;
68
+ node.metadata.is_global_block = is_global_block;
69
+ } else if (idx === 0) {
70
+ // :global appears later in the selector chain (e.g., `div :global p`)
71
+ // Set is_global_block for marking subsequent selectors as global-like
72
+ is_global_block = true;
73
+ } else if (idx !== -1) {
74
+ // `:global` is not at the start - this is invalid but we'll let it through for now
75
+ // The transform phase will handle removal
76
+ }
77
+ }
78
+ }
79
+
80
+ // Pass the current rule as state to nested nodes
81
+ const state = { rule: node };
82
+ context.visit(node.prelude, state);
83
+ context.visit(node.block, state);
84
+ },
85
+
86
+ ComplexSelector(node, context) {
87
+ // Set the rule metadata before analyzing children
88
+ node.metadata.rule = context.state.rule;
89
+
90
+ context.next(); // analyze relevant selectors first
91
+
92
+ {
93
+ const global = node.children.find(is_global);
94
+
95
+ if (global) {
96
+ const is_nested = context.path.at(-2)?.type === 'PseudoClassSelector';
97
+ if (
98
+ is_nested &&
99
+ !(/** @type {AST.CSS.PseudoClassSelector} */ (global.selectors[0]).args)
100
+ ) {
101
+ throw new Error(`A :global selector cannot be inside a pseudoclass.`);
102
+ }
103
+
104
+ const idx = node.children.indexOf(global);
105
+ const first = /** @type {AST.CSS.PseudoClassSelector} */ (global.selectors[0]);
106
+ if (first.args !== null && idx !== 0 && idx !== node.children.length - 1) {
107
+ // ensure `:global(...)` is not used in the middle of a selector (but multiple `global(...)` in sequence are ok)
108
+ for (let i = idx + 1; i < node.children.length; i++) {
109
+ if (!is_global(node.children[i])) {
110
+ throw new Error(
111
+ `:global(...) can be at the start or end of a selector sequence, but not in the middle.`,
112
+ );
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ // Set is_global metadata
120
+ node.metadata.is_global = node.children.every(
121
+ ({ metadata }) => metadata.is_global || metadata.is_global_like,
122
+ );
123
+
124
+ node.metadata.used ||= node.metadata.is_global;
125
+ },
126
+
127
+ PseudoClassSelector(node, context) {
128
+ // Walk into :is(), :where(), :has(), and :not() to initialize metadata for nested selectors
129
+ if (
130
+ (node.name === 'is' ||
131
+ node.name === 'where' ||
132
+ node.name === 'has' ||
133
+ node.name === 'not') &&
134
+ node.args
135
+ ) {
136
+ context.next();
137
+ }
138
+ },
139
+ RelativeSelector(node, context) {
140
+ // Check if this selector is a :global selector
141
+ node.metadata.is_global = node.selectors.length >= 1 && is_global(node);
142
+
143
+ // Check for :root and other global-like selectors
144
+ if (
145
+ node.selectors.length >= 1 &&
146
+ node.selectors.every(
147
+ (selector) =>
148
+ selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector',
149
+ )
150
+ ) {
151
+ const first = node.selectors[0];
152
+ node.metadata.is_global_like ||=
153
+ (first.type === 'PseudoClassSelector' && first.name === 'host') ||
154
+ (first.type === 'PseudoClassSelector' && first.name === 'root');
155
+ }
156
+
157
+ context.next();
158
+ },
159
+ });
160
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ @import * as AST from 'estree';
3
+ @import { AnalysisContext, CompileError } from '../../types/index';
4
+ */
5
+
6
+ import { error } from '../errors.js';
7
+
8
+ const invalid_nestings = {
9
+ // <p> cannot contain block-level elements
10
+ p: new Set([
11
+ 'address',
12
+ 'article',
13
+ 'aside',
14
+ 'blockquote',
15
+ 'details',
16
+ 'div',
17
+ 'dl',
18
+ 'fieldset',
19
+ 'figcaption',
20
+ 'figure',
21
+ 'footer',
22
+ 'form',
23
+ 'h1',
24
+ 'h2',
25
+ 'h3',
26
+ 'h4',
27
+ 'h5',
28
+ 'h6',
29
+ 'header',
30
+ 'hgroup',
31
+ 'hr',
32
+ 'main',
33
+ 'menu',
34
+ 'nav',
35
+ 'ol',
36
+ 'p',
37
+ 'pre',
38
+ 'section',
39
+ 'table',
40
+ 'ul',
41
+ ]),
42
+ // <span> cannot contain block-level elements
43
+ span: new Set([
44
+ 'address',
45
+ 'article',
46
+ 'aside',
47
+ 'blockquote',
48
+ 'details',
49
+ 'div',
50
+ 'dl',
51
+ 'fieldset',
52
+ 'figcaption',
53
+ 'figure',
54
+ 'footer',
55
+ 'form',
56
+ 'h1',
57
+ 'h2',
58
+ 'h3',
59
+ 'h4',
60
+ 'h5',
61
+ 'h6',
62
+ 'header',
63
+ 'hgroup',
64
+ 'hr',
65
+ 'main',
66
+ 'menu',
67
+ 'nav',
68
+ 'ol',
69
+ 'p',
70
+ 'pre',
71
+ 'section',
72
+ 'table',
73
+ 'ul',
74
+ ]),
75
+ // Interactive elements cannot be nested
76
+ a: new Set(['a', 'button']),
77
+ button: new Set(['a', 'button']),
78
+ // Form elements
79
+ label: new Set(['label']),
80
+ form: new Set(['form']),
81
+ // Headings cannot be nested within each other
82
+ h1: new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
83
+ h2: new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
84
+ h3: new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
85
+ h4: new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
86
+ h5: new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
87
+ h6: new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
88
+ // Table structure
89
+ table: new Set(['table', 'tr', 'td', 'th']), // Can only contain caption, colgroup, thead, tbody, tfoot
90
+ thead: new Set(['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'td', 'th']), // Can only contain tr
91
+ tbody: new Set(['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'td', 'th']), // Can only contain tr
92
+ tfoot: new Set(['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'td', 'th']), // Can only contain tr
93
+ tr: new Set(['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'tr']), // Can only contain td and th
94
+ td: new Set(['td', 'th']), // Cannot nest td/th elements
95
+ th: new Set(['td', 'th']), // Cannot nest td/th elements
96
+ // Media elements
97
+ picture: new Set(['picture']),
98
+ // Main landmark - only one per document, cannot be nested
99
+ main: new Set(['main']),
100
+ // Other semantic restrictions
101
+ figcaption: new Set(['figcaption']),
102
+ dt: new Set([
103
+ 'header',
104
+ 'footer',
105
+ 'article',
106
+ 'aside',
107
+ 'nav',
108
+ 'section',
109
+ 'h1',
110
+ 'h2',
111
+ 'h3',
112
+ 'h4',
113
+ 'h5',
114
+ 'h6',
115
+ ]),
116
+ // No interactive content inside summary
117
+ summary: new Set(['summary']),
118
+ };
119
+
120
+ /**
121
+ * @param {AST.Element} element
122
+ * @returns {string | null}
123
+ */
124
+ function get_element_tag(element) {
125
+ return element.id.type === 'Identifier' ? element.id.name : null;
126
+ }
127
+
128
+ /**
129
+ * @param {AST.Element} element
130
+ * @param {AnalysisContext} context
131
+ * @param {CompileError[]} [errors]
132
+ */
133
+ export function validate_nesting(element, context, errors) {
134
+ const tag = get_element_tag(element);
135
+
136
+ if (tag === null) {
137
+ return;
138
+ }
139
+
140
+ for (let i = context.path.length - 1; i >= 0; i--) {
141
+ const parent = context.path[i];
142
+ if (parent.type === 'Element') {
143
+ const parent_tag = get_element_tag(parent);
144
+ if (parent_tag === null) {
145
+ continue;
146
+ }
147
+
148
+ if (parent_tag in invalid_nestings) {
149
+ const validation_set =
150
+ invalid_nestings[/** @type {keyof typeof invalid_nestings} */ (parent_tag)];
151
+ if (validation_set.has(tag)) {
152
+ error(
153
+ `Invalid HTML nesting: <${tag}> cannot be a descendant of <${parent_tag}>.`,
154
+ context.state.analysis.module.filename,
155
+ element,
156
+ errors,
157
+ context.state.analysis.comments,
158
+ );
159
+ } else {
160
+ // if my parent has a set of invalid children
161
+ // and i'm not in it, then i'm valid
162
+ return;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @import * as AST from 'estree'
3
+ */
4
+
5
+ /**
6
+ * Check if a comment is a TypeScript pragma (line comment)
7
+ * @param {AST.CommentWithLocation} comment
8
+ * @returns {boolean}
9
+ */
10
+ export function is_ts_pragma(comment) {
11
+ if (comment.type !== 'Line') return false;
12
+
13
+ const pragmas = ['@ts-ignore', '@ts-expect-error', '@ts-nocheck', '@ts-check'];
14
+ return pragmas.some((pragma) => comment.value.trimStart().startsWith(pragma));
15
+ }
16
+
17
+ /**
18
+ * Check if a comment is a triple-slash directive
19
+ * /// <reference path="..." />
20
+ * @param {AST.CommentWithLocation} comment
21
+ * @returns {boolean}
22
+ */
23
+ export function is_triple_slash_directive(comment) {
24
+ if (comment.type !== 'Line') return false;
25
+
26
+ // Triple slash directives start with / after the // is stripped
27
+ // So the value should start with / followed by <reference, <amd-module, or <amd-dependency
28
+ const value = comment.value.trim();
29
+ return /^\/\s*<(reference|amd-module|amd-dependency)/.test(value);
30
+ }
31
+
32
+ /**
33
+ * Check if a comment is a JSDoc comment with TypeScript annotations
34
+ * Examples: block comments containing `@type`, `@typedef`, `@param`, `@returns`, etc.
35
+ * @param {AST.CommentWithLocation} comment
36
+ * @returns {boolean}
37
+ */
38
+ export function is_jsdoc_ts_annotation(comment) {
39
+ if (comment.type !== 'Block') return false;
40
+
41
+ // JSDoc comments start with /** which means the value starts with * after /* is stripped
42
+ if (!comment.value.startsWith('*')) return false;
43
+
44
+ // Check if it contains TypeScript-relevant tags
45
+ const tsAnnotations = [
46
+ '@type',
47
+ '@typedef',
48
+ '@param',
49
+ '@returns',
50
+ '@template',
51
+ '@extends',
52
+ '@implements',
53
+ '@satisfies',
54
+ '@overload',
55
+ '@import',
56
+ ];
57
+
58
+ return tsAnnotations.some((annotation) => comment.value.includes(annotation));
59
+ }
60
+
61
+ /**
62
+ * Check if a comment should be preserved in to_ts mode
63
+ * @param {AST.CommentWithLocation} comment
64
+ * @returns {boolean}
65
+ */
66
+ export function should_preserve_comment(comment) {
67
+ return (
68
+ is_ts_pragma(comment) || is_triple_slash_directive(comment) || is_jsdoc_ts_annotation(comment)
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Format a comment for output
74
+ * @param {AST.CommentWithLocation} comment
75
+ * @returns {string}
76
+ */
77
+ export function format_comment(comment) {
78
+ if (comment.type === 'Line') {
79
+ // Check if it's a triple-slash directive (value starts with /)
80
+ if (comment.value.trimStart().startsWith('/')) {
81
+ return `/// ${comment.value.trimStart().slice(1)}`;
82
+ }
83
+ return `// ${comment.value.trim()}`;
84
+ } else {
85
+ // Block comment - check if it's a JSDoc (value starts with *)
86
+ if (comment.value.startsWith('*')) {
87
+ return `/** ${comment.value.trim().slice(1)} */`;
88
+ }
89
+ return `/* ${comment.value.trim()} */`;
90
+ }
91
+ }
@@ -0,0 +1,21 @@
1
+ export const TEMPLATE_FRAGMENT = 1;
2
+ export const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
3
+ export const IS_CONTROLLED = 1 << 2;
4
+ export const IS_INDEXED = 1 << 3;
5
+ export const TEMPLATE_SVG_NAMESPACE = 1 << 5;
6
+ export const TEMPLATE_MATHML_NAMESPACE = 1 << 6;
7
+
8
+ export const HYDRATION_START = '[';
9
+ export const HYDRATION_END = ']';
10
+ export const HYDRATION_ERROR = {};
11
+
12
+ export const BLOCK_OPEN = `<!--${HYDRATION_START}-->`;
13
+ export const BLOCK_CLOSE = `<!--${HYDRATION_END}-->`;
14
+ export const EMPTY_COMMENT = `<!---->`;
15
+
16
+ export const ELEMENT_NODE = 1;
17
+ export const TEXT_NODE = 3;
18
+ export const COMMENT_NODE = 8;
19
+ export const DOCUMENT_FRAGMENT_NODE = 11;
20
+
21
+ export const DEFAULT_NAMESPACE = 'html';
package/src/errors.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ @import * as AST from 'estree';
3
+ @import { CompileError } from '../types/index';
4
+ */
5
+
6
+ /**
7
+ *
8
+ * @param {string} message
9
+ * @param {string | null} filename
10
+ * @param {AST.Node | AST.NodeWithLocation} node
11
+ * @param {CompileError[]} [errors]
12
+ * @param {AST.CommentWithLocation[]} [comments]
13
+ * @returns {void}
14
+ */
15
+ export function error(message, filename, node, errors, comments) {
16
+ if (errors && comments && is_error_suppressed(node, comments)) {
17
+ return;
18
+ }
19
+
20
+ const error = /** @type {CompileError} */ (new Error(message));
21
+
22
+ // same as the acorn compiler error
23
+ error.pos = node.start ?? undefined;
24
+ error.raisedAt = node.end ?? undefined;
25
+
26
+ // custom properties
27
+ error.fileName = filename;
28
+ error.end = node.end ?? undefined;
29
+ error.loc = !node.loc
30
+ ? undefined
31
+ : {
32
+ start: {
33
+ line: node.loc.start.line,
34
+ column: node.loc.start.column,
35
+ },
36
+ end: {
37
+ line: node.loc.end.line,
38
+ column: node.loc.end.column,
39
+ },
40
+ };
41
+
42
+ if (errors) {
43
+ error.type = 'usage';
44
+ errors.push(error);
45
+ return;
46
+ }
47
+
48
+ error.type = 'fatal';
49
+ throw error;
50
+ }
51
+
52
+ /**
53
+ * @param {AST.CommentWithLocation} comment
54
+ * @return {boolean}
55
+ */
56
+ function is_error_suppress_comment(comment) {
57
+ const text = comment.value.trim();
58
+ return (
59
+ text.startsWith('@tsrx-ignore') ||
60
+ text.startsWith('@tsrx-expect-error') ||
61
+ text.startsWith('@ripple-ignore') ||
62
+ text.startsWith('@ripple-expect-error')
63
+ );
64
+ }
65
+
66
+ /**
67
+ * @param {AST.Node | AST.NodeWithLocation} node
68
+ * @param {AST.CommentWithLocation[]} comments
69
+ */
70
+ function is_error_suppressed(node, comments) {
71
+ if (node.loc) {
72
+ const node_start_line = node.loc.start.line;
73
+ for (const comment of comments) {
74
+ if (comment.type === 'Line' && comment.loc.start.line === node_start_line - 1) {
75
+ if (is_error_suppress_comment(comment)) {
76
+ return true;
77
+ }
78
+ }
79
+ }
80
+ }
81
+ return false;
82
+ }
@@ -0,0 +1,11 @@
1
+ export type RequireAllOrNone<T, K extends keyof T> =
2
+ | (T & Required<Pick<T, K>>)
3
+ | (T & { [P in K]?: never });
4
+
5
+ export type RequiredPresent<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
6
+
7
+ export type Nullable<T> = T | null;
8
+
9
+ export type Nullish<T> = T | null | undefined;
10
+
11
+ export type NestedArray<T> = (T | NestedArray<T>)[];
@@ -0,0 +1,80 @@
1
+ export const IDENTIFIER_OBFUSCATION_PREFIX = '_$_';
2
+ export const STYLE_IDENTIFIER = IDENTIFIER_OBFUSCATION_PREFIX + encode_utf16_char('#') + 'style';
3
+ export const SERVER_IDENTIFIER = IDENTIFIER_OBFUSCATION_PREFIX + encode_utf16_char('#') + 'server';
4
+ export const CSS_HASH_IDENTIFIER = IDENTIFIER_OBFUSCATION_PREFIX + 'hash';
5
+
6
+ const DECODE_UTF16_REGEX = /_u([0-9a-fA-F]{4})_/g;
7
+
8
+ /**
9
+ * @param {string} char
10
+ * @returns {string}
11
+ */
12
+ function encode_utf16_char(char) {
13
+ return `_u${('0000' + char.charCodeAt(0).toString(16)).slice(-4)}_`;
14
+ }
15
+
16
+ /**
17
+ * Finds the next uppercase character or returns name.length
18
+ * @param {string} name
19
+ * @param {number} start
20
+ * @returns {number}
21
+ */
22
+ function find_next_uppercase(name, start) {
23
+ for (let i = start; i < name.length; i++) {
24
+ if (name[i] === name[i].toUpperCase()) {
25
+ return i;
26
+ }
27
+ }
28
+ return name.length;
29
+ }
30
+
31
+ /**
32
+ * @param {string} encoded
33
+ * @returns {string}
34
+ */
35
+ function decode_utf16_string(encoded) {
36
+ return encoded.replace(DECODE_UTF16_REGEX, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
37
+ }
38
+
39
+ /**
40
+ * @param {string} name
41
+ * @returns {string}
42
+ */
43
+ export function obfuscate_identifier(name) {
44
+ const first_char = name[0];
45
+ let start = 0;
46
+ if (first_char === '@' || first_char === '#') {
47
+ const encoded = encode_utf16_char(first_char);
48
+ name = encoded + name.slice(1);
49
+ start = encoded.length;
50
+ } else if (first_char === first_char.toUpperCase()) {
51
+ start = 1;
52
+ }
53
+ const index = find_next_uppercase(name, start);
54
+
55
+ const first_part = name.slice(0, index);
56
+ const second_part = name.slice(index);
57
+
58
+ return (
59
+ IDENTIFIER_OBFUSCATION_PREFIX +
60
+ (second_part ? second_part + '__' + first_part : first_part + '__')
61
+ );
62
+ }
63
+
64
+ /**
65
+ * @param {string} name
66
+ * @returns {boolean}
67
+ */
68
+ export function is_identifier_obfuscated(name) {
69
+ return name.startsWith(IDENTIFIER_OBFUSCATION_PREFIX);
70
+ }
71
+
72
+ /**
73
+ * @param {string} name
74
+ * @returns {string}
75
+ */
76
+ export function deobfuscate_identifier(name) {
77
+ name = name.replaceAll(IDENTIFIER_OBFUSCATION_PREFIX, '');
78
+ const parts = name.split('__');
79
+ return decode_utf16_string((parts[1] ? parts[1] : '') + parts[0]);
80
+ }