@saidksi/localizer-core 0.1.0
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/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/ai/anthropic.d.ts +17 -0
- package/dist/ai/anthropic.d.ts.map +1 -0
- package/dist/ai/anthropic.js +58 -0
- package/dist/ai/anthropic.js.map +1 -0
- package/dist/ai/dedup.d.ts +19 -0
- package/dist/ai/dedup.d.ts.map +1 -0
- package/dist/ai/dedup.js +119 -0
- package/dist/ai/dedup.js.map +1 -0
- package/dist/ai/index.d.ts +65 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +464 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/openai.d.ts +11 -0
- package/dist/ai/openai.d.ts.map +1 -0
- package/dist/ai/openai.js +62 -0
- package/dist/ai/openai.js.map +1 -0
- package/dist/ai/prompts.d.ts +20 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +151 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/cache/index.d.ts +69 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +129 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/rewriter/index.d.ts +31 -0
- package/dist/rewriter/index.d.ts.map +1 -0
- package/dist/rewriter/index.js +128 -0
- package/dist/rewriter/index.js.map +1 -0
- package/dist/rewriter/transforms.d.ts +38 -0
- package/dist/rewriter/transforms.d.ts.map +1 -0
- package/dist/rewriter/transforms.js +189 -0
- package/dist/rewriter/transforms.js.map +1 -0
- package/dist/rewriter/ts-morph.d.ts +19 -0
- package/dist/rewriter/ts-morph.d.ts.map +1 -0
- package/dist/rewriter/ts-morph.js +121 -0
- package/dist/rewriter/ts-morph.js.map +1 -0
- package/dist/scanner/babel.d.ts +3 -0
- package/dist/scanner/babel.d.ts.map +1 -0
- package/dist/scanner/babel.js +504 -0
- package/dist/scanner/babel.js.map +1 -0
- package/dist/scanner/filters.d.ts +38 -0
- package/dist/scanner/filters.d.ts.map +1 -0
- package/dist/scanner/filters.js +133 -0
- package/dist/scanner/filters.js.map +1 -0
- package/dist/scanner/index.d.ts +22 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +82 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/typescript.d.ts +3 -0
- package/dist/scanner/typescript.d.ts.map +1 -0
- package/dist/scanner/typescript.js +542 -0
- package/dist/scanner/typescript.js.map +1 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validator/index.d.ts +65 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +237 -0
- package/dist/validator/index.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
export const LIBRARY_ADAPTERS = {
|
|
2
|
+
"react-i18next": {
|
|
3
|
+
importSource: "react-i18next",
|
|
4
|
+
importSpecifier: "useTranslation",
|
|
5
|
+
importStatement: 'import { useTranslation } from "react-i18next";',
|
|
6
|
+
hookStatement: "const { t } = useTranslation();",
|
|
7
|
+
hookDetectionString: "useTranslation(",
|
|
8
|
+
callFor: (key) => `t('${key}')`,
|
|
9
|
+
},
|
|
10
|
+
"i18next": {
|
|
11
|
+
importSource: "react-i18next",
|
|
12
|
+
importSpecifier: "useTranslation",
|
|
13
|
+
importStatement: 'import { useTranslation } from "react-i18next";',
|
|
14
|
+
hookStatement: "const { t } = useTranslation();",
|
|
15
|
+
hookDetectionString: "useTranslation(",
|
|
16
|
+
callFor: (key) => `t('${key}')`,
|
|
17
|
+
},
|
|
18
|
+
"next-intl": {
|
|
19
|
+
importSource: "next-intl",
|
|
20
|
+
importSpecifier: "useTranslations",
|
|
21
|
+
importStatement: 'import { useTranslations } from "next-intl";',
|
|
22
|
+
hookStatement: "const t = useTranslations();",
|
|
23
|
+
hookDetectionString: "useTranslations(",
|
|
24
|
+
callFor: (key) => `t('${key}')`,
|
|
25
|
+
},
|
|
26
|
+
"react-intl": {
|
|
27
|
+
importSource: "react-intl",
|
|
28
|
+
importSpecifier: "useIntl",
|
|
29
|
+
importStatement: 'import { useIntl } from "react-intl";',
|
|
30
|
+
hookStatement: "const intl = useIntl();",
|
|
31
|
+
hookDetectionString: "useIntl(",
|
|
32
|
+
callFor: (key) => `intl.formatMessage({ id: '${key}' })`,
|
|
33
|
+
},
|
|
34
|
+
"vue-i18n": {
|
|
35
|
+
importSource: "vue-i18n",
|
|
36
|
+
importSpecifier: "useI18n",
|
|
37
|
+
importStatement: 'import { useI18n } from "vue-i18n";',
|
|
38
|
+
hookStatement: "const { t } = useI18n();",
|
|
39
|
+
hookDetectionString: "useI18n(",
|
|
40
|
+
callFor: (key) => `t('${key}')`,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export function getAdapter(library) {
|
|
44
|
+
return LIBRARY_ADAPTERS[library];
|
|
45
|
+
}
|
|
46
|
+
// ─── Positional string replacement ───────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Apply all resolved string replacements to the source text.
|
|
49
|
+
*
|
|
50
|
+
* Strategy:
|
|
51
|
+
* - Sort results bottom-to-top, right-to-left so earlier replacements
|
|
52
|
+
* don't shift the positions of later ones on the same line.
|
|
53
|
+
* - For each result, locate the exact source span from its NodeType and
|
|
54
|
+
* replace it with the appropriate t() call expression.
|
|
55
|
+
*/
|
|
56
|
+
export function applyStringReplacements(source, results, adapter) {
|
|
57
|
+
const resolved = results.filter((r) => r.resolvedKey !== null);
|
|
58
|
+
if (resolved.length === 0)
|
|
59
|
+
return { modified: source, count: 0 };
|
|
60
|
+
// Sort: bottom → top, right → left
|
|
61
|
+
const sorted = [...resolved].sort((a, b) => {
|
|
62
|
+
if (b.line !== a.line)
|
|
63
|
+
return b.line - a.line;
|
|
64
|
+
return b.column - a.column;
|
|
65
|
+
});
|
|
66
|
+
const lines = source.split("\n");
|
|
67
|
+
let count = 0;
|
|
68
|
+
for (const result of sorted) {
|
|
69
|
+
const lineIdx = result.line - 1; // convert to 0-based
|
|
70
|
+
const lineStr = lines[lineIdx];
|
|
71
|
+
if (lineStr === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
const key = result.resolvedKey;
|
|
74
|
+
const call = adapter.callFor(key);
|
|
75
|
+
let replaced = null;
|
|
76
|
+
switch (result.nodeType) {
|
|
77
|
+
case "JSXText": {
|
|
78
|
+
// Bare text: Welcome back → {t('key')}
|
|
79
|
+
// Search for the trimmed value starting at (or after) the column
|
|
80
|
+
const idx = lineStr.indexOf(result.value, result.column);
|
|
81
|
+
if (idx !== -1) {
|
|
82
|
+
replaced =
|
|
83
|
+
lineStr.substring(0, idx) +
|
|
84
|
+
`{${call}}` +
|
|
85
|
+
lineStr.substring(idx + result.value.length);
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "JSXAttribute": {
|
|
90
|
+
// Quoted attribute value: "Enter email" or 'Enter email' → {t('key')}
|
|
91
|
+
// Try both quote styles; column points to the opening quote
|
|
92
|
+
for (const q of ['"', "'"]) {
|
|
93
|
+
const target = `${q}${result.value}${q}`;
|
|
94
|
+
const idx = lineStr.indexOf(target, result.column);
|
|
95
|
+
if (idx !== -1) {
|
|
96
|
+
replaced =
|
|
97
|
+
lineStr.substring(0, idx) +
|
|
98
|
+
`{${call}}` +
|
|
99
|
+
lineStr.substring(idx + target.length);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case "TemplateLiteral": {
|
|
106
|
+
if (result.context === "Template literal (static part)") {
|
|
107
|
+
// Static quasi from a dynamic template: `prefix ${expr} suffix`
|
|
108
|
+
// Replace just the static text span with ${t('key')} (stays inside backticks)
|
|
109
|
+
const idx = lineStr.indexOf(result.value, result.column);
|
|
110
|
+
if (idx !== -1) {
|
|
111
|
+
replaced =
|
|
112
|
+
lineStr.substring(0, idx) +
|
|
113
|
+
`\${${call}}` +
|
|
114
|
+
lineStr.substring(idx + result.value.length);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Standalone static template literal: `Hello world` → t('key')
|
|
119
|
+
const target = `\`${result.value}\``;
|
|
120
|
+
const idx = lineStr.indexOf(target, result.column);
|
|
121
|
+
if (idx !== -1) {
|
|
122
|
+
replaced =
|
|
123
|
+
lineStr.substring(0, idx) +
|
|
124
|
+
call +
|
|
125
|
+
lineStr.substring(idx + target.length);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "StringLiteral": {
|
|
131
|
+
// Skip module-level strings — t() is not available outside component functions
|
|
132
|
+
if (result.isModuleLevel)
|
|
133
|
+
break;
|
|
134
|
+
// Non-JSX string inside component: setError("..."), return "..." etc. → t('key')
|
|
135
|
+
for (const q of ['"', "'"]) {
|
|
136
|
+
const target = `${q}${result.value}${q}`;
|
|
137
|
+
const idx = lineStr.indexOf(target, result.column);
|
|
138
|
+
if (idx !== -1) {
|
|
139
|
+
replaced =
|
|
140
|
+
lineStr.substring(0, idx) +
|
|
141
|
+
call +
|
|
142
|
+
lineStr.substring(idx + target.length);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "JSXInterpolation": {
|
|
149
|
+
// Replace the entire children span with a single t() call that passes
|
|
150
|
+
// the interpolation variables as an object:
|
|
151
|
+
// You have {taskCount} pending tasks
|
|
152
|
+
// → {t('tasks.pending_count', { taskCount: taskCount })}
|
|
153
|
+
if (!result.rawSpan || !result.interpolations?.length)
|
|
154
|
+
break;
|
|
155
|
+
// Build the options object: { taskCount: taskCount, userName: userName }
|
|
156
|
+
const argsStr = result.interpolations
|
|
157
|
+
.map(({ placeholder, expression }) => placeholder === expression
|
|
158
|
+
? placeholder // shorthand: { taskCount }
|
|
159
|
+
: `${placeholder}: ${expression}`)
|
|
160
|
+
.join(", ");
|
|
161
|
+
const callWithArgs = adapter.callFor(key).replace(/\)$/, `, { ${argsStr} })`);
|
|
162
|
+
// rawSpan may include surrounding newlines/whitespace from JSX formatting
|
|
163
|
+
// (the scanner's firstChild.getStart() lands on the '\n' after the opening
|
|
164
|
+
// tag's '>'). Trim first, then search on the reported line.
|
|
165
|
+
// Try column-anchored search first; fall back to scanning from col 0
|
|
166
|
+
// because result.column may point to the prior line's position.
|
|
167
|
+
const rawSpanTrimmed = result.rawSpan.trim();
|
|
168
|
+
// If trimmed span still contains a newline the content truly spans
|
|
169
|
+
// multiple lines — indexOf on a single lineStr will return -1 safely.
|
|
170
|
+
let idx = lineStr.indexOf(rawSpanTrimmed, result.column);
|
|
171
|
+
if (idx === -1)
|
|
172
|
+
idx = lineStr.indexOf(rawSpanTrimmed, 0);
|
|
173
|
+
if (idx !== -1) {
|
|
174
|
+
replaced =
|
|
175
|
+
lineStr.substring(0, idx) +
|
|
176
|
+
`{${callWithArgs}}` +
|
|
177
|
+
lineStr.substring(idx + rawSpanTrimmed.length);
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (replaced !== null) {
|
|
183
|
+
lines[lineIdx] = replaced;
|
|
184
|
+
count++;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { modified: lines.join("\n"), count };
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=transforms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transforms.js","sourceRoot":"","sources":["../../src/rewriter/transforms.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LibraryAdapter } from "./transforms.js";
|
|
2
|
+
/**
|
|
3
|
+
* Ensure the i18n library import exists in the file.
|
|
4
|
+
* If the import (or specifier) is already present, the source is returned unchanged.
|
|
5
|
+
* Otherwise the import is added after the last existing import declaration.
|
|
6
|
+
*/
|
|
7
|
+
export declare function ensureImport(source: string, filePath: string, adapter: LibraryAdapter): string;
|
|
8
|
+
/**
|
|
9
|
+
* Ensure the translation hook declaration exists in the first component function.
|
|
10
|
+
* If the hook call is already present anywhere in the file, the source is returned unchanged.
|
|
11
|
+
* Otherwise the hook statement is inserted at the top of the component function body.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureHook(source: string, filePath: string, adapter: LibraryAdapter, namespace?: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Apply both import and hook injection in a single pass.
|
|
16
|
+
* Import is added first so the hook injection sees the updated source.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ensureTranslationBoilerplate(source: string, filePath: string, adapter: LibraryAdapter, namespace?: string): string;
|
|
19
|
+
//# sourceMappingURL=ts-morph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts-morph.d.ts","sourceRoot":"","sources":["../../src/rewriter/ts-morph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,GACtB,MAAM,CAoCR;AA8CD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAkCR;AAID;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAIR"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Project, Node } from "ts-morph";
|
|
2
|
+
// ─── Import injection ─────────────────────────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* Ensure the i18n library import exists in the file.
|
|
5
|
+
* If the import (or specifier) is already present, the source is returned unchanged.
|
|
6
|
+
* Otherwise the import is added after the last existing import declaration.
|
|
7
|
+
*/
|
|
8
|
+
export function ensureImport(source, filePath, adapter) {
|
|
9
|
+
const project = new Project({ useInMemoryFileSystem: true });
|
|
10
|
+
const sf = project.createSourceFile(filePath, source, { overwrite: true });
|
|
11
|
+
// Check if the import already exists
|
|
12
|
+
const existing = sf.getImportDeclaration((d) => d.getModuleSpecifierValue() === adapter.importSource &&
|
|
13
|
+
d.getNamedImports().some((n) => n.getName() === adapter.importSpecifier));
|
|
14
|
+
if (existing)
|
|
15
|
+
return source; // already imported
|
|
16
|
+
// Check if the module is imported but our specifier is missing
|
|
17
|
+
const partialImport = sf.getImportDeclaration((d) => d.getModuleSpecifierValue() === adapter.importSource);
|
|
18
|
+
if (partialImport) {
|
|
19
|
+
// Add our specifier to the existing import
|
|
20
|
+
partialImport.addNamedImport(adapter.importSpecifier);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
// Add a brand new import declaration after all existing imports
|
|
24
|
+
const imports = sf.getImportDeclarations();
|
|
25
|
+
const insertIndex = imports.length > 0
|
|
26
|
+
? imports[imports.length - 1].getChildIndex() + 1
|
|
27
|
+
: 0;
|
|
28
|
+
sf.insertImportDeclaration(insertIndex, {
|
|
29
|
+
namedImports: [adapter.importSpecifier],
|
|
30
|
+
moduleSpecifier: adapter.importSource,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return sf.getFullText();
|
|
34
|
+
}
|
|
35
|
+
// ─── Hook injection ───────────────────────────────────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Find the first component function in the source file.
|
|
38
|
+
* Looks for:
|
|
39
|
+
* 1. Exported function declarations: `export function Login()`
|
|
40
|
+
* 2. Default export functions: `export default function()`
|
|
41
|
+
* 3. Exported arrow function variable declarations: `export const Login = () => {}`
|
|
42
|
+
* 4. Any arrow function variable declaration: `const Login = () => {}`
|
|
43
|
+
* 5. Fallback: any function declaration
|
|
44
|
+
*
|
|
45
|
+
* Returns null if no suitable function is found.
|
|
46
|
+
*/
|
|
47
|
+
function findComponentFunction(sf) {
|
|
48
|
+
// 1. Named exported function declarations
|
|
49
|
+
for (const fn of sf.getFunctions()) {
|
|
50
|
+
if (fn.isExported() || fn.isDefaultExport())
|
|
51
|
+
return fn;
|
|
52
|
+
}
|
|
53
|
+
// 2. Exported arrow function variable declarations
|
|
54
|
+
for (const varStmt of sf.getVariableStatements()) {
|
|
55
|
+
if (!varStmt.isExported())
|
|
56
|
+
continue;
|
|
57
|
+
for (const decl of varStmt.getDeclarations()) {
|
|
58
|
+
const init = decl.getInitializer();
|
|
59
|
+
if (init && Node.isArrowFunction(init)) {
|
|
60
|
+
return init;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// 3. Any arrow function variable declaration
|
|
65
|
+
for (const varStmt of sf.getVariableStatements()) {
|
|
66
|
+
for (const decl of varStmt.getDeclarations()) {
|
|
67
|
+
const init = decl.getInitializer();
|
|
68
|
+
if (init && Node.isArrowFunction(init)) {
|
|
69
|
+
return init;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// 4. Fallback: first function declaration in file
|
|
74
|
+
return sf.getFunctions()[0] ?? null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Ensure the translation hook declaration exists in the first component function.
|
|
78
|
+
* If the hook call is already present anywhere in the file, the source is returned unchanged.
|
|
79
|
+
* Otherwise the hook statement is inserted at the top of the component function body.
|
|
80
|
+
*/
|
|
81
|
+
export function ensureHook(source, filePath, adapter, namespace) {
|
|
82
|
+
// Fast path: hook already present in source text
|
|
83
|
+
if (source.includes(adapter.hookDetectionString))
|
|
84
|
+
return source;
|
|
85
|
+
const project = new Project({ useInMemoryFileSystem: true });
|
|
86
|
+
const sf = project.createSourceFile(filePath, source, { overwrite: true });
|
|
87
|
+
const fn = findComponentFunction(sf);
|
|
88
|
+
if (!fn)
|
|
89
|
+
return source; // no function found — leave as-is
|
|
90
|
+
const body = Node.isArrowFunction(fn)
|
|
91
|
+
? fn.getBody()
|
|
92
|
+
: fn.getBody();
|
|
93
|
+
if (!body)
|
|
94
|
+
return source;
|
|
95
|
+
// For arrow functions with expression bodies (no braces), convert to block first
|
|
96
|
+
if (!Node.isBlock(body)) {
|
|
97
|
+
// e.g. `const X = () => <div/>` — not common in real components, skip
|
|
98
|
+
return source;
|
|
99
|
+
}
|
|
100
|
+
// Build the hook statement, using namespace if provided and if the hook accepts it
|
|
101
|
+
// Only useTranslation, useTranslations, and useI18n accept namespace argument.
|
|
102
|
+
// react-intl's useIntl() does not accept a namespace argument.
|
|
103
|
+
const supportsNamespace = adapter.importSpecifier !== "useIntl";
|
|
104
|
+
const hookStatement = namespace && supportsNamespace
|
|
105
|
+
? adapter.hookStatement.replace("()", `('${namespace}')`)
|
|
106
|
+
: adapter.hookStatement;
|
|
107
|
+
// Insert hook at the very beginning of the function body (index 0)
|
|
108
|
+
body.insertStatements(0, [hookStatement]);
|
|
109
|
+
return sf.getFullText();
|
|
110
|
+
}
|
|
111
|
+
// ─── Combined ────────────────────────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* Apply both import and hook injection in a single pass.
|
|
114
|
+
* Import is added first so the hook injection sees the updated source.
|
|
115
|
+
*/
|
|
116
|
+
export function ensureTranslationBoilerplate(source, filePath, adapter, namespace) {
|
|
117
|
+
let result = ensureImport(source, filePath, adapter);
|
|
118
|
+
result = ensureHook(result, filePath, adapter, namespace);
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=ts-morph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ts-morph.js","sourceRoot":"","sources":["../../src/rewriter/ts-morph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"babel.d.ts","sourceRoot":"","sources":["../../src/scanner/babel.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAY,cAAc,EAAoB,MAAM,aAAa,CAAC;AA2R1F,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,GACrB,UAAU,EAAE,CAsTd"}
|