@nanoflags/cli 0.0.0-stage → 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.
@@ -0,0 +1,174 @@
1
+ // src/util.ts
2
+ import { lstatSync, readdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ var EXIT = { ok: 0, failed: 1, usage: 2, unavailable: 3 };
5
+ var CliError = class extends Error {
6
+ constructor(message, exitCode = EXIT.usage) {
7
+ super(message);
8
+ this.exitCode = exitCode;
9
+ this.name = "CliError";
10
+ }
11
+ exitCode;
12
+ };
13
+ function table(headers, rows) {
14
+ const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
15
+ const line = (cells) => cells.map((c, i) => i === cells.length - 1 ? c : c.padEnd(widths[i])).join(" ").trimEnd();
16
+ return [line(headers), line(widths.map((w) => "-".repeat(w))), ...rows.map(line)].join("\n");
17
+ }
18
+ function show(v) {
19
+ return typeof v === "string" ? v : v === void 0 ? "" : JSON.stringify(v);
20
+ }
21
+ function clip(s, max) {
22
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
23
+ }
24
+ var SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
25
+ var SKIP_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", ".next", ".git"]);
26
+ var DEFAULT_MAX_FILE_BYTES = 1e6;
27
+ function walk(root, maxBytes = DEFAULT_MAX_FILE_BYTES) {
28
+ const out = { files: [], tooLarge: [] };
29
+ const visit = (dir, rel) => {
30
+ let names;
31
+ try {
32
+ names = readdirSync(dir).sort();
33
+ } catch {
34
+ return;
35
+ }
36
+ for (const name of names) {
37
+ const full = join(dir, name);
38
+ const relPath = rel ? `${rel}/${name}` : name;
39
+ let st;
40
+ try {
41
+ st = lstatSync(full);
42
+ } catch {
43
+ continue;
44
+ }
45
+ if (st.isSymbolicLink()) continue;
46
+ if (st.isDirectory()) {
47
+ if (!SKIP_DIRECTORIES.has(name)) visit(full, relPath);
48
+ } else if (st.isFile() && SOURCE_EXTENSIONS.some((e) => name.endsWith(e)) && !name.endsWith(".d.ts")) {
49
+ if (st.size > maxBytes) out.tooLarge.push(relPath);
50
+ else out.files.push(relPath);
51
+ }
52
+ }
53
+ };
54
+ visit(root, "");
55
+ return out;
56
+ }
57
+ function diffLines(a, b) {
58
+ let start = 0;
59
+ while (start < a.length && start < b.length && a[start] === b[start]) start++;
60
+ let endA = a.length;
61
+ let endB = b.length;
62
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
63
+ endA--;
64
+ endB--;
65
+ }
66
+ const x = a.slice(start, endA);
67
+ const y = b.slice(start, endB);
68
+ const n = x.length;
69
+ const m = y.length;
70
+ const max = n + m;
71
+ const trace = [];
72
+ let v = /* @__PURE__ */ new Map([[1, 0]]);
73
+ let found = max === 0;
74
+ for (let d = 0; d <= max && !found; d++) {
75
+ const next = /* @__PURE__ */ new Map();
76
+ for (let k = -d; k <= d; k += 2) {
77
+ let i2 = k === -d || k !== d && (v.get(k - 1) ?? -1) < (v.get(k + 1) ?? -1) ? v.get(k + 1) ?? 0 : (v.get(k - 1) ?? 0) + 1;
78
+ let j2 = i2 - k;
79
+ while (i2 < n && j2 < m && x[i2] === y[j2]) {
80
+ i2++;
81
+ j2++;
82
+ }
83
+ next.set(k, i2);
84
+ if (i2 >= n && j2 >= m) {
85
+ found = true;
86
+ break;
87
+ }
88
+ }
89
+ trace.push(next);
90
+ v = next;
91
+ }
92
+ const middle = [];
93
+ let i = n;
94
+ let j = m;
95
+ for (let d = trace.length - 1; d > 0; d--) {
96
+ const prev = trace[d - 1];
97
+ const k = i - j;
98
+ const down = k === -d || k !== d && (prev.get(k - 1) ?? -1) < (prev.get(k + 1) ?? -1);
99
+ const prevK = down ? k + 1 : k - 1;
100
+ const prevI = prev.get(prevK) ?? 0;
101
+ const prevJ = prevI - prevK;
102
+ while (i > prevI && j > prevJ) {
103
+ middle.push({ kind: " ", line: x[--i] });
104
+ j--;
105
+ }
106
+ if (down) middle.push({ kind: "+", line: y[--j] });
107
+ else middle.push({ kind: "-", line: x[--i] });
108
+ }
109
+ while (i > 0 && j > 0) {
110
+ middle.push({ kind: " ", line: x[--i] });
111
+ j--;
112
+ }
113
+ middle.reverse();
114
+ return [...a.slice(0, start).map((line) => ({ kind: " ", line })), ...middle, ...a.slice(endA).map((line) => ({ kind: " ", line }))];
115
+ }
116
+ function unifiedDiff(path, before, after, context = 3) {
117
+ if (before === after) return "";
118
+ const split = (s) => {
119
+ const lines = s.split("\n");
120
+ if (lines.at(-1) === "") lines.pop();
121
+ return lines;
122
+ };
123
+ const ops = diffLines(split(before), split(after));
124
+ const out = [`--- a/${path}`, `+++ b/${path}`];
125
+ let i = 0;
126
+ let lineA = 1;
127
+ let lineB = 1;
128
+ while (i < ops.length) {
129
+ if (ops[i].kind === " ") {
130
+ i++;
131
+ lineA++;
132
+ lineB++;
133
+ continue;
134
+ }
135
+ const from = Math.max(0, i - context);
136
+ const startA = lineA - (i - from);
137
+ const startB = lineB - (i - from);
138
+ let end = i;
139
+ let quiet = 0;
140
+ let k = i;
141
+ while (k < ops.length && quiet <= context * 2) {
142
+ if (ops[k].kind === " ") quiet++;
143
+ else {
144
+ quiet = 0;
145
+ end = k;
146
+ }
147
+ k++;
148
+ }
149
+ const to = Math.min(ops.length, end + 1 + context);
150
+ const hunk = ops.slice(from, to);
151
+ const countA = hunk.filter((o) => o.kind !== "+").length;
152
+ const countB = hunk.filter((o) => o.kind !== "-").length;
153
+ out.push(`@@ -${countA === 0 ? startA - 1 : startA},${countA} +${countB === 0 ? startB - 1 : startB},${countB} @@`);
154
+ for (const o of hunk) out.push(`${o.kind}${o.line}`);
155
+ for (let q = i; q < to; q++) {
156
+ if (ops[q].kind !== "+") lineA++;
157
+ if (ops[q].kind !== "-") lineB++;
158
+ }
159
+ i = to;
160
+ }
161
+ return `${out.join("\n")}
162
+ `;
163
+ }
164
+
165
+ export {
166
+ EXIT,
167
+ CliError,
168
+ table,
169
+ show,
170
+ clip,
171
+ DEFAULT_MAX_FILE_BYTES,
172
+ walk,
173
+ unifiedDiff
174
+ };
@@ -0,0 +1,74 @@
1
+ import {
2
+ clip
3
+ } from "./chunk-KGDBDK5S.js";
4
+
5
+ // src/refs.ts
6
+ import ts from "typescript";
7
+ var VALUE_NAMES = ["boolean", "string", "number", "json", "evaluate", "useFlag", "getBooleanValue", "getStringValue", "getNumberValue", "getObjectValue"];
8
+ var DETAILS_NAMES = ["getBooleanDetails", "getStringDetails", "getNumberDetails", "getObjectDetails", "booleanDetails", "stringDetails", "numberDetails", "jsonDetails", "evaluateDetails", "useFlagDetails"];
9
+ var CALLEE_NAMES = /* @__PURE__ */ new Set([...VALUE_NAMES, ...DETAILS_NAMES]);
10
+ function scriptKind(path) {
11
+ if (path.endsWith(".tsx")) return ts.ScriptKind.TSX;
12
+ if (path.endsWith(".jsx")) return ts.ScriptKind.JSX;
13
+ if (path.endsWith(".ts") || path.endsWith(".mts") || path.endsWith(".cts")) return ts.ScriptKind.TS;
14
+ return ts.ScriptKind.JS;
15
+ }
16
+ function parse(path, text) {
17
+ return ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true, scriptKind(path));
18
+ }
19
+ function calleeName(callee) {
20
+ let e = callee;
21
+ while (ts.isParenthesizedExpression(e) || ts.isNonNullExpression(e)) e = e.expression;
22
+ if (ts.isIdentifier(e)) return e.text;
23
+ if (ts.isPropertyAccessExpression(e)) return e.name.text;
24
+ return null;
25
+ }
26
+ function findCallSites(source, keys) {
27
+ const out = [];
28
+ const visit = (node) => {
29
+ if (ts.isCallExpression(node)) {
30
+ const name = calleeName(node.expression);
31
+ const first = node.arguments[0];
32
+ if (name !== null && CALLEE_NAMES.has(name) && first && (ts.isStringLiteral(first) || ts.isNoSubstitutionTemplateLiteral(first)) && keys.has(first.text)) {
33
+ out.push({ call: node, name, flag: first.text });
34
+ }
35
+ }
36
+ ts.forEachChild(node, visit);
37
+ };
38
+ visit(source);
39
+ return out;
40
+ }
41
+ function lineOf(source, pos) {
42
+ return source.getLineAndCharacterOfPosition(pos).line + 1;
43
+ }
44
+ function snippetAt(text, source, pos) {
45
+ const line = source.getLineAndCharacterOfPosition(pos).line;
46
+ const starts = source.getLineStarts();
47
+ const end = line + 1 < starts.length ? starts[line + 1] : text.length;
48
+ return clip(text.slice(starts[line], end).trim(), 240);
49
+ }
50
+ function scanText(path, text, keys) {
51
+ let mentions = false;
52
+ for (const k of keys) {
53
+ if (text.includes(k)) {
54
+ mentions = true;
55
+ break;
56
+ }
57
+ }
58
+ if (!mentions) return [];
59
+ const source = parse(path, text);
60
+ return findCallSites(source, keys).map((s) => {
61
+ const pos = s.call.getStart(source);
62
+ return { flag: s.flag, path, line: lineOf(source, pos), snippet: snippetAt(text, source, pos) };
63
+ });
64
+ }
65
+
66
+ export {
67
+ CALLEE_NAMES,
68
+ scriptKind,
69
+ parse,
70
+ findCallSites,
71
+ lineOf,
72
+ snippetAt,
73
+ scanText
74
+ };
@@ -0,0 +1,371 @@
1
+ import {
2
+ findCallSites,
3
+ lineOf,
4
+ parse,
5
+ scriptKind,
6
+ snippetAt
7
+ } from "./chunk-TJEVOUI3.js";
8
+ import "./chunk-KGDBDK5S.js";
9
+
10
+ // src/cleanup.ts
11
+ import ts from "typescript";
12
+ var truthy = (c) => Boolean(c);
13
+ var typeName = (v) => v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
14
+ function skipOuter(node) {
15
+ let p = node.parent;
16
+ while (ts.isParenthesizedExpression(p)) p = p.parent;
17
+ return p;
18
+ }
19
+ function isPure(node) {
20
+ if (ts.isCallExpression(node) || ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node) || ts.isTaggedTemplateExpression(node) || ts.isDeleteExpression(node) || ts.isSpreadElement(node)) return false;
21
+ if (ts.isFunctionExpression(node) || ts.isArrowFunction(node)) return true;
22
+ if (ts.isClassExpression(node)) return false;
23
+ if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) && (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken)) return false;
24
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment) return false;
25
+ let pure = true;
26
+ ts.forEachChild(node, (child) => {
27
+ if (pure && !isPure(child)) pure = false;
28
+ });
29
+ return pure;
30
+ }
31
+ function fallbackKind(arg) {
32
+ if (!arg) return null;
33
+ let e = arg;
34
+ while (ts.isParenthesizedExpression(e)) e = e.expression;
35
+ if (e.kind === ts.SyntaxKind.TrueKeyword || e.kind === ts.SyntaxKind.FalseKeyword) return "boolean";
36
+ if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e)) return "string";
37
+ if (ts.isNumericLiteral(e) || ts.isPrefixUnaryExpression(e) && ts.isNumericLiteral(e.operand)) return "number";
38
+ if (ts.isObjectLiteralExpression(e) || ts.isArrayLiteralExpression(e) || e.kind === ts.SyntaxKind.NullKeyword) return "json";
39
+ return null;
40
+ }
41
+ function unsupportedReason(site, value) {
42
+ const { call, name } = site;
43
+ if (name === "evaluate" || name.endsWith("Details")) return `${name}() returns an evaluation object, not the value`;
44
+ if (call.questionDotToken || (call.flags & ts.NodeFlags.OptionalChain) !== 0) return "optional call (?.) may also yield undefined";
45
+ let accessor;
46
+ if (name === "boolean" || name === "getBooleanValue") accessor = "boolean";
47
+ else if (name === "string" || name === "getStringValue") accessor = "string";
48
+ else if (name === "number" || name === "getNumberValue") accessor = "number";
49
+ else if (name === "json" || name === "getObjectValue") accessor = "json";
50
+ else accessor = fallbackKind(call.arguments[1]);
51
+ if (accessor === null) return "the accessor type depends on a fallback that is not a literal";
52
+ if (accessor !== "json" && accessor !== typeName(value)) return `this is a ${accessor} read but the value is a ${typeName(value)}: today it returns its fallback, not the value`;
53
+ if (name.startsWith("get") && !ts.isAwaitExpression(skipOuter(call))) return `${name}() may return a promise (OpenFeature server SDK); only awaited calls are rewritten`;
54
+ if (!isPure(call.expression) || !call.arguments.every(isPure)) return "the call or its arguments may have side effects";
55
+ return null;
56
+ }
57
+ var src = (ctx, n) => ctx.text.slice(n.getStart(ctx.source), n.end);
58
+ function lineBounds(text, start, end) {
59
+ let lineStart = start;
60
+ while (lineStart > 0 && text[lineStart - 1] !== "\n") lineStart--;
61
+ let lineEnd = end;
62
+ while (lineEnd < text.length && text[lineEnd] !== "\n") lineEnd++;
63
+ const alone = text.slice(lineStart, start).trim() === "" && text.slice(end, lineEnd).trim() === "";
64
+ return { lineStart, lineEnd: lineEnd < text.length ? lineEnd + 1 : lineEnd, alone };
65
+ }
66
+ function removeStatement(ctx, node) {
67
+ const start = node.getStart(ctx.source);
68
+ const b = lineBounds(ctx.text, start, node.end);
69
+ return b.alone ? { start: b.lineStart, end: b.lineEnd, text: "" } : { start, end: node.end, text: "" };
70
+ }
71
+ function literalText(c, at) {
72
+ const parent = at.parent;
73
+ if (typeof c === "number") {
74
+ const s = JSON.stringify(c);
75
+ const tight = ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent) || ts.isCallExpression(parent);
76
+ const operand = ts.isBinaryExpression(parent) || ts.isPrefixUnaryExpression(parent) || ts.isPostfixUnaryExpression(parent);
77
+ return tight && parent.expression === at || c < 0 && (tight || operand) ? `(${s})` : s;
78
+ }
79
+ if (typeof c === "object" && c !== null) {
80
+ const s = JSON.stringify(c);
81
+ return !Array.isArray(c) && (ts.isExpressionStatement(parent) || ts.isArrowFunction(parent) && parent.body === at) ? `(${s})` : s;
82
+ }
83
+ return JSON.stringify(c);
84
+ }
85
+ function moved(ctx, from, target) {
86
+ const s = src(ctx, from);
87
+ const parent = target.parent;
88
+ const statementStart = ts.isExpressionStatement(parent) || ts.isArrowFunction(parent) && parent.body === target;
89
+ return statementStart && /^(\{|function\b|class\b|let\b)/.test(s) ? `(${s})` : s;
90
+ }
91
+ var ATOMIC = /^(?:[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*|\d+(?:\.\d+)?|"[^"\\\n]*")$/;
92
+ function place(ctx, target, text) {
93
+ let node = target;
94
+ if (ATOMIC.test(text) && ts.isParenthesizedExpression(node.parent)) {
95
+ const outer = node.parent.parent;
96
+ const memberOfNumber = /^\d/.test(text) && (ts.isPropertyAccessExpression(outer) || ts.isElementAccessExpression(outer)) && outer.expression === node.parent;
97
+ if (!memberOfNumber) node = node.parent;
98
+ }
99
+ const holder = node.parent;
100
+ if (ts.isJsxExpression(holder) && (ts.isJsxElement(holder.parent) || ts.isJsxFragment(holder.parent))) {
101
+ if (text.startsWith("<")) return { start: holder.getStart(ctx.source), end: holder.end, text };
102
+ if (text === "false" || text === "null") return removeStatement(ctx, holder);
103
+ }
104
+ return { start: node.getStart(ctx.source), end: node.end, text };
105
+ }
106
+ function inBooleanContext(node) {
107
+ const p = node.parent;
108
+ if (ts.isParenthesizedExpression(p)) return inBooleanContext(p);
109
+ if (ts.isPrefixUnaryExpression(p) && p.operator === ts.SyntaxKind.ExclamationToken) return true;
110
+ if ((ts.isIfStatement(p) || ts.isWhileStatement(p) || ts.isDoStatement(p)) && p.expression === node) return true;
111
+ if (ts.isForStatement(p) && p.condition === node) return true;
112
+ if (ts.isConditionalExpression(p) && p.condition === node) return true;
113
+ if (ts.isBinaryExpression(p) && (p.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || p.operatorToken.kind === ts.SyntaxKind.BarBarToken)) return inBooleanContext(p);
114
+ return false;
115
+ }
116
+ function literalValue(e) {
117
+ let x = e;
118
+ while (ts.isParenthesizedExpression(x)) x = x.expression;
119
+ if (ts.isStringLiteral(x) || ts.isNoSubstitutionTemplateLiteral(x)) return { value: x.text };
120
+ if (ts.isNumericLiteral(x)) return { value: Number(x.text) };
121
+ if (ts.isPrefixUnaryExpression(x) && x.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(x.operand)) return { value: -Number(x.operand.text) };
122
+ if (x.kind === ts.SyntaxKind.TrueKeyword) return { value: true };
123
+ if (x.kind === ts.SyntaxKind.FalseKeyword) return { value: false };
124
+ if (x.kind === ts.SyntaxKind.NullKeyword) return { value: null };
125
+ return null;
126
+ }
127
+ function hasMultilineTemplate(ctx, node) {
128
+ let found = false;
129
+ const visit = (n) => {
130
+ if (found) return;
131
+ if ((ts.isNoSubstitutionTemplateLiteral(n) || ts.isTemplateExpression(n)) && src(ctx, n).includes("\n")) found = true;
132
+ else ts.forEachChild(n, visit);
133
+ };
134
+ visit(node);
135
+ return found;
136
+ }
137
+ function canUnwrap(ctx, block) {
138
+ if (hasMultilineTemplate(ctx, block)) return false;
139
+ const declared = /* @__PURE__ */ new Set();
140
+ const bind = (name) => {
141
+ if (ts.isIdentifier(name)) declared.add(name.text);
142
+ else for (const el of name.elements) if (!ts.isOmittedExpression(el)) bind(el.name);
143
+ };
144
+ for (const s of block.statements) {
145
+ if (ts.isVariableStatement(s) && (s.declarationList.flags & ts.NodeFlags.BlockScoped) !== 0) s.declarationList.declarations.forEach((d) => bind(d.name));
146
+ else if ((ts.isClassDeclaration(s) || ts.isFunctionDeclaration(s) || ts.isEnumDeclaration(s)) && s.name) declared.add(s.name.text);
147
+ }
148
+ if (declared.size === 0) return true;
149
+ let clash = false;
150
+ const visit = (n) => {
151
+ if (clash || n === block) return;
152
+ if (ts.isIdentifier(n) && declared.has(n.text)) clash = true;
153
+ else ts.forEachChild(n, visit);
154
+ };
155
+ visit(ctx.source);
156
+ return !clash;
157
+ }
158
+ function isStatementList(n) {
159
+ return ts.isBlock(n) || ts.isSourceFile(n) || ts.isModuleBlock(n) || ts.isCaseClause(n) || ts.isDefaultClause(n);
160
+ }
161
+ function rewriteIf(ctx, stmt, c) {
162
+ const taken = truthy(c) ? stmt.thenStatement : stmt.elseStatement;
163
+ const start = stmt.getStart(ctx.source);
164
+ const inList = isStatementList(stmt.parent);
165
+ if (!taken) {
166
+ if (inList) return removeStatement(ctx, stmt);
167
+ if (ts.isIfStatement(stmt.parent) && stmt.parent.elseStatement === stmt) return { start: stmt.parent.thenStatement.end, end: stmt.end, text: "" };
168
+ return { start, end: stmt.end, text: "{}" };
169
+ }
170
+ const bounds = lineBounds(ctx.text, start, stmt.end);
171
+ const startsLine = ctx.text.slice(bounds.lineStart, start).trim() === "";
172
+ if (ts.isBlock(taken) && inList && startsLine && canUnwrap(ctx, taken)) {
173
+ const inner = ctx.text.slice(taken.getStart(ctx.source) + 1, taken.end - 1);
174
+ const lines = inner.split("\n");
175
+ while (lines.length > 0 && lines[0].trim() === "") lines.shift();
176
+ while (lines.length > 0 && lines.at(-1).trim() === "") lines.pop();
177
+ if (lines.length === 0) return removeStatement(ctx, stmt);
178
+ const outerIndent = ctx.text.slice(bounds.lineStart, start);
179
+ const innerIndent = /^[ \t]*/.exec(lines[0])[0];
180
+ const out = lines.map((l, i) => {
181
+ const body = l.startsWith(innerIndent) ? l.slice(innerIndent.length) : l.trimStart();
182
+ return body === "" ? "" : (i === 0 ? "" : outerIndent) + body;
183
+ });
184
+ return { start, end: stmt.end, text: out.join("\n") };
185
+ }
186
+ return { start, end: stmt.end, text: src(ctx, taken) };
187
+ }
188
+ function referencesOf(ctx, decl) {
189
+ const checker = ctx.checker();
190
+ const symbol = checker.getSymbolAtLocation(decl.name);
191
+ const refs = [];
192
+ let exported = false;
193
+ if (!symbol) return { refs, exported: true };
194
+ const visit = (n) => {
195
+ if (ts.isIdentifier(n) && n !== decl.name && n.text === decl.name.text) {
196
+ const p = n.parent;
197
+ if (ts.isExportSpecifier(p)) {
198
+ if (checker.getExportSpecifierLocalTargetSymbol(p) === symbol) exported = true;
199
+ } else if (ts.isShorthandPropertyAssignment(p)) {
200
+ if (checker.getShorthandAssignmentValueSymbol(p) === symbol) refs.push(n);
201
+ } else if (checker.getSymbolAtLocation(n) === symbol) refs.push(n);
202
+ }
203
+ ts.forEachChild(n, visit);
204
+ };
205
+ visit(ctx.source);
206
+ return { refs, exported };
207
+ }
208
+ var AND = ts.SyntaxKind.AmpersandAmpersandToken;
209
+ var OR = ts.SyntaxKind.BarBarToken;
210
+ function editFor(ctx, start, value, depth = 0) {
211
+ let node = start;
212
+ let c = value;
213
+ for (; ; ) {
214
+ const p = node.parent;
215
+ if (ts.isParenthesizedExpression(p) || ts.isAwaitExpression(p) || ts.isNonNullExpression(p) || ts.isAsExpression(p) || ts.isSatisfiesExpression(p)) {
216
+ node = p;
217
+ continue;
218
+ }
219
+ if (ts.isPrefixUnaryExpression(p) && p.operator === ts.SyntaxKind.ExclamationToken) {
220
+ node = p;
221
+ c = !truthy(c);
222
+ continue;
223
+ }
224
+ if (ts.isBinaryExpression(p)) {
225
+ const op = p.operatorToken.kind;
226
+ const isLeft = p.left === node;
227
+ if (op === AND || op === OR) {
228
+ const decides = op === AND ? !truthy(c) : truthy(c);
229
+ if (isLeft) {
230
+ if (!decides) return place(ctx, p, moved(ctx, p.right, p));
231
+ node = p;
232
+ continue;
233
+ }
234
+ if (!inBooleanContext(p)) break;
235
+ if (!decides) return place(ctx, p, moved(ctx, p.left, p));
236
+ if (!isPure(p.left)) break;
237
+ node = p;
238
+ c = op === OR;
239
+ continue;
240
+ }
241
+ if (op === ts.SyntaxKind.QuestionQuestionToken && isLeft) {
242
+ if (c === null) return place(ctx, p, moved(ctx, p.right, p));
243
+ node = p;
244
+ continue;
245
+ }
246
+ const strict = op === ts.SyntaxKind.EqualsEqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
247
+ const loose = op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken;
248
+ if ((strict || loose) && (typeof c !== "object" || c === null)) {
249
+ const other = literalValue(isLeft ? p.right : p.left);
250
+ if (other && (strict || typeName(other.value) === typeName(c))) {
251
+ const equal = other.value === c;
252
+ node = p;
253
+ c = op === ts.SyntaxKind.EqualsEqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsToken ? equal : !equal;
254
+ continue;
255
+ }
256
+ }
257
+ break;
258
+ }
259
+ if (ts.isConditionalExpression(p) && p.condition === node) {
260
+ return place(ctx, p, moved(ctx, truthy(c) ? p.whenTrue : p.whenFalse, p));
261
+ }
262
+ if (ts.isIfStatement(p) && p.expression === node) return rewriteIf(ctx, p, c);
263
+ if (ts.isVariableDeclaration(p) && p.initializer === node && ts.isIdentifier(p.name) && ts.isVariableDeclarationList(p.parent) && (p.parent.flags & ts.NodeFlags.Const) !== 0 && depth < 20) {
264
+ const decl = p;
265
+ const { refs, exported } = referencesOf(ctx, decl);
266
+ const first = refs[0];
267
+ if (first) {
268
+ if (ts.isShorthandPropertyAssignment(first.parent)) return { start: first.parent.getStart(ctx.source), end: first.parent.end, text: `${first.text}: ${literalText(c, first.parent)}` };
269
+ return editFor(ctx, first, c, depth + 1);
270
+ }
271
+ const list = p.parent;
272
+ const statement = list.parent;
273
+ const isExported = exported || ts.isVariableStatement(statement) && (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Export) !== 0;
274
+ if (!isExported && list.declarations.length === 1 && ts.isVariableStatement(statement) && isStatementList(statement.parent)) return removeStatement(ctx, statement);
275
+ }
276
+ break;
277
+ }
278
+ return place(ctx, node, literalText(c, node));
279
+ }
280
+ function syntaxErrors(source) {
281
+ return (source.parseDiagnostics ?? []).length;
282
+ }
283
+ function checkerFor(source) {
284
+ const fileName = source.fileName;
285
+ const host = {
286
+ getSourceFile: (f) => f === fileName ? source : void 0,
287
+ getDefaultLibFileName: () => "lib.d.ts",
288
+ writeFile: () => void 0,
289
+ getCurrentDirectory: () => "/nanoflags-cleanup",
290
+ getCanonicalFileName: (f) => f,
291
+ useCaseSensitiveFileNames: () => true,
292
+ getNewLine: () => "\n",
293
+ fileExists: (f) => f === fileName,
294
+ readFile: () => void 0
295
+ };
296
+ const program = ts.createProgram([fileName], { noLib: true, noResolve: true, allowJs: true, jsx: ts.JsxEmit.Preserve, target: ts.ScriptTarget.Latest, noEmit: true, types: [] }, host);
297
+ return program.getTypeChecker();
298
+ }
299
+ function step(path, text, keys, value) {
300
+ const ext = /\.[a-z]+$/.exec(path)?.[0] ?? ".ts";
301
+ const source = ts.createSourceFile(`/nanoflags-cleanup/file${ext}`, text, ts.ScriptTarget.Latest, true, scriptKind(path));
302
+ const site = findCallSites(source, keys).find((s) => unsupportedReason(s, value) === null);
303
+ if (!site) return null;
304
+ let checker = null;
305
+ const ctx = { path, text, source, checker: () => checker ??= checkerFor(source) };
306
+ const edit = editFor(ctx, site.call, value);
307
+ return text.slice(0, edit.start) + edit.text + text.slice(edit.end);
308
+ }
309
+ function unreachableStatements(text, source) {
310
+ const out = [];
311
+ const visit = (n) => {
312
+ if (isStatementList(n)) {
313
+ const list = n.statements;
314
+ for (let i = 0; i + 1 < list.length; i++) {
315
+ const s = list[i];
316
+ const next = list[i + 1];
317
+ if ((ts.isReturnStatement(s) || ts.isThrowStatement(s) || ts.isBreakStatement(s) || ts.isContinueStatement(s)) && !ts.isFunctionDeclaration(next)) {
318
+ const pos = next.getStart(source);
319
+ out.push({ line: lineOf(source, pos), snippet: snippetAt(text, source, pos) });
320
+ break;
321
+ }
322
+ }
323
+ }
324
+ ts.forEachChild(n, visit);
325
+ };
326
+ visit(source);
327
+ return out;
328
+ }
329
+ function cleanupText(path, text, flag, value) {
330
+ const keys = /* @__PURE__ */ new Set([flag]);
331
+ const original = parse(path, text);
332
+ const sites = findCallSites(original, keys).map((s) => {
333
+ const pos = s.call.getStart(original);
334
+ const reason = unsupportedReason(s, value);
335
+ return { line: lineOf(original, pos), snippet: snippetAt(text, original, pos), ...reason === null ? {} : { reason } };
336
+ });
337
+ if (sites.every((s) => s.reason !== void 0)) return { output: text, transformed: [], skipped: sites, unreachable: [] };
338
+ let current = text;
339
+ for (let i = 0; i < 2e3; i++) {
340
+ const next = step(path, current, keys, value);
341
+ if (next === null || next === current) break;
342
+ current = next;
343
+ }
344
+ const final = parse(path, current);
345
+ const left = findCallSites(final, keys);
346
+ const failed = syntaxErrors(final) > syntaxErrors(original) || left.some((s) => unsupportedReason(s, value) === null);
347
+ if (failed) {
348
+ return { output: text, transformed: [], skipped: sites.map((s) => ({ ...s, reason: s.reason ?? "the rewrite could not be completed safely; the file was left unchanged" })), unreachable: [] };
349
+ }
350
+ const remaining = /* @__PURE__ */ new Map();
351
+ for (const s of left) {
352
+ const snip = snippetAt(current, final, s.call.getStart(final));
353
+ remaining.set(snip, (remaining.get(snip) ?? 0) + 1);
354
+ }
355
+ const unmatched = [];
356
+ const skippedBefore = sites.filter((s) => s.reason !== void 0);
357
+ for (const s of skippedBefore) {
358
+ const n = remaining.get(s.snippet) ?? 0;
359
+ if (n > 0) remaining.set(s.snippet, n - 1);
360
+ else unmatched.push(s);
361
+ }
362
+ const gone = skippedBefore.length - left.length;
363
+ const skipped = skippedBefore.map((s) => gone > 0 && gone === unmatched.length && unmatched.includes(s) ? { ...s, removed: true } : s);
364
+ const already = new Set(unreachableStatements(text, original).map((u) => u.snippet));
365
+ const unreachable = unreachableStatements(current, final).filter((u) => !already.has(u.snippet));
366
+ return { output: current, transformed: sites.filter((s) => s.reason === void 0), skipped, unreachable };
367
+ }
368
+ export {
369
+ cleanupText,
370
+ unsupportedReason
371
+ };
@@ -0,0 +1,19 @@
1
+ import {
2
+ CALLEE_NAMES,
3
+ findCallSites,
4
+ lineOf,
5
+ parse,
6
+ scanText,
7
+ scriptKind,
8
+ snippetAt
9
+ } from "./chunk-TJEVOUI3.js";
10
+ import "./chunk-KGDBDK5S.js";
11
+ export {
12
+ CALLEE_NAMES,
13
+ findCallSites,
14
+ lineOf,
15
+ parse,
16
+ scanText,
17
+ scriptKind,
18
+ snippetAt
19
+ };