deslop-js 0.0.13 → 0.0.14-dev.ae0f67a

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/dist/index.cjs CHANGED
@@ -33,10 +33,10 @@ let fast_glob = require("fast-glob");
33
33
  fast_glob = __toESM(fast_glob, 1);
34
34
  let node_fs_promises = require("node:fs/promises");
35
35
  let oxc_parser = require("oxc-parser");
36
- let oxc_resolver = require("oxc-resolver");
37
- let minimatch = require("minimatch");
38
36
  let typescript = require("typescript");
39
37
  typescript = __toESM(typescript, 1);
38
+ let oxc_resolver = require("oxc-resolver");
39
+ let minimatch = require("minimatch");
40
40
 
41
41
  //#region src/constants.ts
42
42
  const DEFAULT_EXTENSIONS = [
@@ -1575,10 +1575,24 @@ const extractMdxImportsExports = (sourceText) => {
1575
1575
  return statements.join("\n");
1576
1576
  };
1577
1577
  const ASTRO_FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
1578
- const extractAstroFrontmatter = (sourceText) => {
1578
+ const ASTRO_SCRIPT_TAG_PATTERN = /<script\b([^>]*?)\/>|<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
1579
+ const ASTRO_SCRIPT_SRC_ATTRIBUTE_PATTERN = /\bsrc\s*=\s*["']([^"']+)["']/i;
1580
+ const extractAstroSources = (sourceText) => {
1581
+ const sections = [];
1579
1582
  const frontmatterMatch = sourceText.match(ASTRO_FRONTMATTER_PATTERN);
1580
- if (!frontmatterMatch) return "";
1581
- return frontmatterMatch[1];
1583
+ if (frontmatterMatch) sections.push(frontmatterMatch[1]);
1584
+ ASTRO_SCRIPT_TAG_PATTERN.lastIndex = 0;
1585
+ let scriptMatch;
1586
+ while ((scriptMatch = ASTRO_SCRIPT_TAG_PATTERN.exec(sourceText)) !== null) {
1587
+ const selfClosingAttributes = scriptMatch[1];
1588
+ const pairedAttributes = scriptMatch[2];
1589
+ const attributes = selfClosingAttributes ?? pairedAttributes ?? "";
1590
+ const body = selfClosingAttributes === void 0 ? scriptMatch[3] ?? "" : "";
1591
+ const srcMatch = attributes.match(ASTRO_SCRIPT_SRC_ATTRIBUTE_PATTERN);
1592
+ if (srcMatch) sections.push(`import ${JSON.stringify(srcMatch[1])};`);
1593
+ if (body) sections.push(body);
1594
+ }
1595
+ return sections.join("\n");
1582
1596
  };
1583
1597
  const VUE_SCRIPT_PATTERN = /<script[^>]*(?:lang=["'](?:ts|tsx)["'][^>]*)?>([\s\S]*?)<\/script>/gi;
1584
1598
  const extractVueScriptContent = (sourceText) => {
@@ -1796,7 +1810,8 @@ const parseSourceFile = (filePath) => {
1796
1810
  const isAstro = filePath.endsWith(".astro");
1797
1811
  const isVue = filePath.endsWith(".vue");
1798
1812
  const isSvelte = filePath.endsWith(".svelte");
1799
- const textToParse = isMdx ? extractMdxImportsExports(sourceText) : isAstro ? extractAstroFrontmatter(sourceText) : isVue ? extractVueScriptContent(sourceText) : isSvelte ? extractSvelteScriptContent(sourceText) : sourceText;
1813
+ const isPreprocessed = isMdx || isAstro || isVue || isSvelte;
1814
+ const textToParse = isMdx ? extractMdxImportsExports(sourceText) : isAstro ? extractAstroSources(sourceText) : isVue ? extractVueScriptContent(sourceText) : isSvelte ? extractSvelteScriptContent(sourceText) : sourceText;
1800
1815
  const parseFileName = isMdx || isAstro || isVue || isSvelte ? filePath.replace(/\.(mdx|astro|vue|svelte)$/, ".tsx") : filePath;
1801
1816
  let result;
1802
1817
  try {
@@ -1820,7 +1835,7 @@ const parseSourceFile = (filePath) => {
1820
1835
  if (tsxResult.errors.length === 0) result = tsxResult;
1821
1836
  }
1822
1837
  } catch {}
1823
- if (result.errors.length > 0) return {
1838
+ if (result.errors.length > 0 && !isPreprocessed) return {
1824
1839
  ...createEmptyParsedSource(),
1825
1840
  imports,
1826
1841
  exports,
@@ -1832,6 +1847,12 @@ const parseSourceFile = (filePath) => {
1832
1847
  path: filePath
1833
1848
  })]
1834
1849
  };
1850
+ if (result.errors.length > 0) earlyErrors.push(new ParseError({
1851
+ code: "parse-recovered-partial",
1852
+ severity: "info",
1853
+ message: `oxc-parser reported ${result.errors.length} syntax issue(s) in extracted ${isAstro ? "Astro" : isVue ? "Vue" : isSvelte ? "Svelte" : "MDX"} sources; continuing with partial AST`,
1854
+ path: filePath
1855
+ }));
1835
1856
  const program = result.program;
1836
1857
  if (!program?.body) return {
1837
1858
  ...createEmptyParsedSource(),
@@ -3124,6 +3145,7 @@ const EXPO_ENTRY_PATTERNS = [
3124
3145
  ];
3125
3146
  const EXPO_ROUTER_ENTRY_PATTERNS = [
3126
3147
  "app/**/*.{ts,tsx,js,jsx}",
3148
+ "src/app/**/*.{ts,tsx,js,jsx}",
3127
3149
  "app.config.{ts,js,mjs,cjs}",
3128
3150
  "metro.config.{ts,js,mjs,cjs}",
3129
3151
  "babel.config.{ts,js,mjs,cjs}"
@@ -3166,6 +3188,196 @@ const discoverMobileEntryPoints = (directory) => {
3166
3188
  }
3167
3189
  };
3168
3190
 
3191
+ //#endregion
3192
+ //#region src/collect/expo-config-plugin-entries.ts
3193
+ const EXPO_CONFIG_FILE_GLOBS = ["app.config.{ts,mts,cts,js,mjs,cjs}", "app.json"];
3194
+ const NESTED_EXPO_CONFIG_FILE_GLOBS = [
3195
+ ...EXPO_CONFIG_FILE_GLOBS,
3196
+ "**/app.config.{ts,mts,cts,js,mjs,cjs}",
3197
+ "**/app.json"
3198
+ ];
3199
+ const EXPO_REACT_NATIVE_DEPENDENCIES = new Set(["expo", "react-native"]);
3200
+ const EXPO_PLUGIN_RESOLVABLE_EXTENSIONS = SOURCE_EXTENSIONS$3.map((sourceExtension) => `.${sourceExtension}`);
3201
+ const isRecord = (value) => typeof value === "object" && value !== null;
3202
+ const isExpoOrReactNativeWorkspace = (dependencies) => [...EXPO_REACT_NATIVE_DEPENDENCIES].some((dependencyName) => dependencyName in dependencies);
3203
+ const isLocalExpoPluginPath = (value) => (value.startsWith("./") || value.startsWith("../")) && !value.includes("*") && !value.includes("?");
3204
+ const isFile = (filePath) => {
3205
+ try {
3206
+ return (0, node_fs.statSync)(filePath).isFile();
3207
+ } catch {
3208
+ return false;
3209
+ }
3210
+ };
3211
+ const resolveExpoPluginPath = (configDirectory, pluginPath) => {
3212
+ const candidatePath = (0, node_path.resolve)(configDirectory, pluginPath);
3213
+ if (isFile(candidatePath)) return candidatePath;
3214
+ for (const extension of EXPO_PLUGIN_RESOLVABLE_EXTENSIONS) {
3215
+ const candidatePathWithExtension = `${candidatePath}${extension}`;
3216
+ if (isFile(candidatePathWithExtension)) return candidatePathWithExtension;
3217
+ }
3218
+ for (const extension of EXPO_PLUGIN_RESOLVABLE_EXTENSIONS) {
3219
+ const indexCandidatePath = (0, node_path.join)(candidatePath, `index${extension}`);
3220
+ if (isFile(indexCandidatePath)) return indexCandidatePath;
3221
+ }
3222
+ };
3223
+ const addExpoPluginEntry = (entries, rootDirectory, configDirectory, pluginPath) => {
3224
+ if (!isLocalExpoPluginPath(pluginPath)) return;
3225
+ const resolvedPath = resolveExpoPluginPath(configDirectory, pluginPath);
3226
+ if (!resolvedPath) return;
3227
+ const relativePath = (0, node_path.relative)(rootDirectory, resolvedPath);
3228
+ if (relativePath !== "" && (relativePath.startsWith("..") || (0, node_path.isAbsolute)(relativePath))) return;
3229
+ entries.add(resolvedPath);
3230
+ };
3231
+ const getPropertyName = (name) => {
3232
+ if (typescript.default.isIdentifier(name) || typescript.default.isStringLiteral(name) || typescript.default.isNumericLiteral(name)) return name.text;
3233
+ };
3234
+ const unwrapExpression = (expression) => {
3235
+ let currentExpression = expression;
3236
+ while (typescript.default.isParenthesizedExpression(currentExpression)) currentExpression = currentExpression.expression;
3237
+ return currentExpression;
3238
+ };
3239
+ const collectExpoPluginPathsFromArray = (array, entries, rootDirectory, configDirectory) => {
3240
+ for (const element of array.elements) {
3241
+ if (typescript.default.isStringLiteral(element) || typescript.default.isNoSubstitutionTemplateLiteral(element)) {
3242
+ addExpoPluginEntry(entries, rootDirectory, configDirectory, element.text);
3243
+ continue;
3244
+ }
3245
+ if (typescript.default.isArrayLiteralExpression(element)) {
3246
+ const [pluginName] = element.elements;
3247
+ if (pluginName && (typescript.default.isStringLiteral(pluginName) || typescript.default.isNoSubstitutionTemplateLiteral(pluginName))) addExpoPluginEntry(entries, rootDirectory, configDirectory, pluginName.text);
3248
+ }
3249
+ }
3250
+ };
3251
+ const collectExpoPluginPathsFromConfigObject = (objectLiteral, entries, rootDirectory, configDirectory) => {
3252
+ for (const property of objectLiteral.properties) if (typescript.default.isPropertyAssignment(property) && getPropertyName(property.name) === "plugins" && typescript.default.isArrayLiteralExpression(property.initializer)) collectExpoPluginPathsFromArray(property.initializer, entries, rootDirectory, configDirectory);
3253
+ };
3254
+ const collectReturnedExpoConfigPluginPaths = (body, entries, rootDirectory, configDirectory) => {
3255
+ if (!typescript.default.isBlock(body)) {
3256
+ const expression = unwrapExpression(body);
3257
+ if (typescript.default.isObjectLiteralExpression(expression)) collectExpoPluginPathsFromConfigObject(expression, entries, rootDirectory, configDirectory);
3258
+ return;
3259
+ }
3260
+ const visit = (node) => {
3261
+ if (typescript.default.isFunctionDeclaration(node) || typescript.default.isFunctionExpression(node) || typescript.default.isArrowFunction(node)) return;
3262
+ if (typescript.default.isReturnStatement(node) && node.expression) {
3263
+ const expression = unwrapExpression(node.expression);
3264
+ if (typescript.default.isObjectLiteralExpression(expression)) collectExpoPluginPathsFromConfigObject(expression, entries, rootDirectory, configDirectory);
3265
+ return;
3266
+ }
3267
+ typescript.default.forEachChild(node, visit);
3268
+ };
3269
+ visit(body);
3270
+ };
3271
+ const collectExpoPluginPathsFromConfigExpression = (expression, entries, rootDirectory, configDirectory, bindings, seenIdentifiers = /* @__PURE__ */ new Set()) => {
3272
+ const configExpression = unwrapExpression(expression);
3273
+ if (typescript.default.isObjectLiteralExpression(configExpression)) {
3274
+ collectExpoPluginPathsFromConfigObject(configExpression, entries, rootDirectory, configDirectory);
3275
+ return;
3276
+ }
3277
+ if (typescript.default.isIdentifier(configExpression)) {
3278
+ if (seenIdentifiers.has(configExpression.text)) return;
3279
+ seenIdentifiers.add(configExpression.text);
3280
+ const boundExpression = bindings.expressions.get(configExpression.text);
3281
+ if (boundExpression) {
3282
+ collectExpoPluginPathsFromConfigExpression(boundExpression, entries, rootDirectory, configDirectory, bindings, seenIdentifiers);
3283
+ return;
3284
+ }
3285
+ const boundFunction = bindings.functions.get(configExpression.text);
3286
+ if (boundFunction?.body) collectReturnedExpoConfigPluginPaths(boundFunction.body, entries, rootDirectory, configDirectory);
3287
+ return;
3288
+ }
3289
+ if (typescript.default.isArrowFunction(configExpression)) {
3290
+ collectReturnedExpoConfigPluginPaths(configExpression.body, entries, rootDirectory, configDirectory);
3291
+ return;
3292
+ }
3293
+ if (typescript.default.isFunctionExpression(configExpression) && configExpression.body) collectReturnedExpoConfigPluginPaths(configExpression.body, entries, rootDirectory, configDirectory);
3294
+ };
3295
+ const hasDefaultExportModifier = (node) => Boolean(typescript.default.canHaveModifiers(node) && typescript.default.getModifiers(node)?.some((modifier) => modifier.kind === typescript.default.SyntaxKind.DefaultKeyword));
3296
+ const isModuleExportsAssignmentTarget = (node) => typescript.default.isPropertyAccessExpression(node) && typescript.default.isIdentifier(node.expression) && node.expression.text === "module" && node.name.text === "exports";
3297
+ const collectStaticConfigBindings = (sourceFile) => {
3298
+ const expressions = /* @__PURE__ */ new Map();
3299
+ const functions = /* @__PURE__ */ new Map();
3300
+ for (const statement of sourceFile.statements) {
3301
+ if (typescript.default.isVariableStatement(statement)) {
3302
+ for (const declaration of statement.declarationList.declarations) if (typescript.default.isIdentifier(declaration.name) && declaration.initializer) expressions.set(declaration.name.text, declaration.initializer);
3303
+ continue;
3304
+ }
3305
+ if (typescript.default.isFunctionDeclaration(statement) && statement.name) functions.set(statement.name.text, statement);
3306
+ }
3307
+ return {
3308
+ expressions,
3309
+ functions
3310
+ };
3311
+ };
3312
+ const collectExpoPluginPathsFromAppConfig = (configPath, entries, rootDirectory) => {
3313
+ const extension = (0, node_path.extname)(configPath);
3314
+ const sourceFile = typescript.default.createSourceFile(configPath, (0, node_fs.readFileSync)(configPath, "utf8"), typescript.default.ScriptTarget.Latest, true, extension === ".ts" || extension === ".mts" || extension === ".cts" ? typescript.default.ScriptKind.TS : typescript.default.ScriptKind.JS);
3315
+ const configDirectory = (0, node_path.dirname)(configPath);
3316
+ const bindings = collectStaticConfigBindings(sourceFile);
3317
+ const visit = (node) => {
3318
+ if (typescript.default.isExportAssignment(node)) {
3319
+ collectExpoPluginPathsFromConfigExpression(node.expression, entries, rootDirectory, configDirectory, bindings);
3320
+ return;
3321
+ }
3322
+ if (typescript.default.isFunctionDeclaration(node) && hasDefaultExportModifier(node) && node.body) {
3323
+ collectReturnedExpoConfigPluginPaths(node.body, entries, rootDirectory, configDirectory);
3324
+ return;
3325
+ }
3326
+ if (typescript.default.isBinaryExpression(node) && node.operatorToken.kind === typescript.default.SyntaxKind.EqualsToken && isModuleExportsAssignmentTarget(node.left)) {
3327
+ collectExpoPluginPathsFromConfigExpression(node.right, entries, rootDirectory, configDirectory, bindings);
3328
+ return;
3329
+ }
3330
+ typescript.default.forEachChild(node, visit);
3331
+ };
3332
+ visit(sourceFile);
3333
+ };
3334
+ const collectPluginPathsFromJsonValue = (value) => {
3335
+ if (!Array.isArray(value)) return [];
3336
+ const pluginPaths = [];
3337
+ for (const plugin of value) {
3338
+ if (typeof plugin === "string") {
3339
+ pluginPaths.push(plugin);
3340
+ continue;
3341
+ }
3342
+ if (Array.isArray(plugin) && typeof plugin[0] === "string") pluginPaths.push(plugin[0]);
3343
+ }
3344
+ return pluginPaths;
3345
+ };
3346
+ const collectExpoPluginPathsFromAppJson = (configPath, entries, rootDirectory) => {
3347
+ const parsedJson = JSON.parse((0, node_fs.readFileSync)(configPath, "utf8"));
3348
+ const configDirectory = (0, node_path.dirname)(configPath);
3349
+ if (!isRecord(parsedJson)) return;
3350
+ const expoConfig = parsedJson.expo;
3351
+ const expoPluginPaths = isRecord(expoConfig) ? collectPluginPathsFromJsonValue(expoConfig.plugins) : [];
3352
+ for (const pluginPath of [...expoPluginPaths, ...collectPluginPathsFromJsonValue(parsedJson.plugins)]) addExpoPluginEntry(entries, rootDirectory, configDirectory, pluginPath);
3353
+ };
3354
+ const collectExpoPluginPathsFromConfig = (configPath, entries, rootDirectory) => {
3355
+ try {
3356
+ if ((0, node_path.basename)(configPath) === "app.json") {
3357
+ collectExpoPluginPathsFromAppJson(configPath, entries, rootDirectory);
3358
+ return;
3359
+ }
3360
+ collectExpoPluginPathsFromAppConfig(configPath, entries, rootDirectory);
3361
+ } catch {}
3362
+ };
3363
+ const extractExpoConfigPluginEntries = (directory, dependencies, rootDirectory = directory, includeNestedConfigs = true) => {
3364
+ if (!isExpoOrReactNativeWorkspace(dependencies)) return [];
3365
+ const entries = /* @__PURE__ */ new Set();
3366
+ const configPaths = fast_glob.default.sync(includeNestedConfigs ? NESTED_EXPO_CONFIG_FILE_GLOBS : EXPO_CONFIG_FILE_GLOBS, {
3367
+ cwd: directory,
3368
+ absolute: true,
3369
+ onlyFiles: true,
3370
+ ignore: [
3371
+ "**/node_modules/**",
3372
+ "**/dist/**",
3373
+ "**/build/**"
3374
+ ],
3375
+ deep: 6
3376
+ });
3377
+ for (const configPath of configPaths) collectExpoPluginPathsFromConfig(configPath, entries, rootDirectory);
3378
+ return [...entries];
3379
+ };
3380
+
3169
3381
  //#endregion
3170
3382
  //#region src/resolver/source-path.ts
3171
3383
  const SOURCE_EXTENSIONS$1 = [
@@ -3473,6 +3685,10 @@ const extractSectionsModuleEntries = (directory) => {
3473
3685
  return [...entries];
3474
3686
  };
3475
3687
 
3688
+ //#endregion
3689
+ //#region src/utils/to-posix-path.ts
3690
+ const toPosixPath = (filePath) => filePath.replace(/\\/g, "/");
3691
+
3476
3692
  //#endregion
3477
3693
  //#region src/collect/entries.ts
3478
3694
  const collectSourceFiles = async (config) => {
@@ -3589,6 +3805,11 @@ const resolveEntries = async (config) => {
3589
3805
  for (const workspacePackage of entryEligiblePackages) tsConfigIncludeEntries.push(...extractTsConfigIncludeFilesEntries(workspacePackage.directory));
3590
3806
  const configStringEntries = extractConfigStringReferencedEntries(absoluteRoot);
3591
3807
  for (const workspacePackage of entryEligiblePackages) configStringEntries.push(...extractConfigStringReferencedEntries(workspacePackage.directory));
3808
+ const expoConfigPluginEntries = extractExpoConfigPluginEntries(absoluteRoot, readPackageJsonDependencies((0, node_path.join)(absoluteRoot, "package.json")), absoluteRoot, false);
3809
+ for (const workspacePackage of entryEligiblePackages) {
3810
+ const workspacePackageDependencies = readPackageJsonDependencies((0, node_path.join)(workspacePackage.directory, "package.json"));
3811
+ expoConfigPluginEntries.push(...extractExpoConfigPluginEntries(workspacePackage.directory, workspacePackageDependencies, absoluteRoot));
3812
+ }
3592
3813
  const sectionsModuleEntries = extractSectionsModuleEntries(absoluteRoot);
3593
3814
  const wranglerEntries = extractWranglerEntries(absoluteRoot);
3594
3815
  for (const workspacePackage of entryEligiblePackages) wranglerEntries.push(...extractWranglerEntries(workspacePackage.directory));
@@ -3599,7 +3820,7 @@ const resolveEntries = async (config) => {
3599
3820
  const testRunnerDiscovery = discoverTestRunnerEntryPoints(absoluteRoot, entryEligiblePackages);
3600
3821
  const toolingDiscovery = discoverToolingEntryPoints(absoluteRoot, entryEligiblePackages);
3601
3822
  const ciEntries = extractCiWorkflowEntries(absoluteRoot);
3602
- const testEntries = [...new Set([...testRunnerDiscovery.entryFiles, ...testSetupEntries])];
3823
+ const testEntries = [...new Set([...testRunnerDiscovery.entryFiles, ...testSetupEntries].map(toPosixPath))];
3603
3824
  const testEntryPathSet = new Set(testEntries);
3604
3825
  return {
3605
3826
  productionEntries: [...new Set([
@@ -3617,14 +3838,15 @@ const resolveEntries = async (config) => {
3617
3838
  ...webWorkerEntries,
3618
3839
  ...tsConfigIncludeEntries,
3619
3840
  ...configStringEntries,
3841
+ ...expoConfigPluginEntries,
3620
3842
  ...sectionsModuleEntries,
3621
3843
  ...wranglerEntries,
3622
3844
  ...pluginFileEntries,
3623
3845
  ...toolingDiscovery.entryFiles,
3624
3846
  ...ciEntries
3625
- ])].filter((entryPath) => !testEntryPathSet.has(entryPath)),
3847
+ ].map(toPosixPath))].filter((entryPath) => !testEntryPathSet.has(entryPath)),
3626
3848
  testEntries,
3627
- alwaysUsedFiles: [...new Set([...toolingDiscovery.alwaysUsedFiles, ...testRunnerDiscovery.alwaysUsedFiles])]
3849
+ alwaysUsedFiles: [...new Set([...toolingDiscovery.alwaysUsedFiles, ...testRunnerDiscovery.alwaysUsedFiles].map(toPosixPath))]
3628
3850
  };
3629
3851
  };
3630
3852
  const DEFAULT_INDEX_PATTERNS = [
@@ -5492,7 +5714,7 @@ const FRAMEWORK_PATTERNS = [
5492
5714
  "app/_layout.{ts,tsx,js,jsx}",
5493
5715
  "app/index.{ts,tsx,js,jsx}"
5494
5716
  ],
5495
- alwaysUsed: ["app.json", "app.config.{ts,js}"]
5717
+ alwaysUsed: ["app.json", "app.config.{ts,mts,cts,js,mjs,cjs}"]
5496
5718
  },
5497
5719
  {
5498
5720
  enablers: ["wrangler"],
@@ -6499,9 +6721,10 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
6499
6721
  try {
6500
6722
  const resolverResult = activeResolver.sync(fromDir, cleanedSpecifier);
6501
6723
  if (resolverResult.path) {
6502
- const isInsideNodeModules = resolverResult.path.includes("/node_modules/");
6724
+ const normalizedResolvedPath = toPosixPath(resolverResult.path);
6725
+ const isInsideNodeModules = normalizedResolvedPath.includes("/node_modules/");
6503
6726
  return {
6504
- resolvedPath: isInsideNodeModules ? void 0 : resolverResult.path,
6727
+ resolvedPath: isInsideNodeModules ? void 0 : normalizedResolvedPath,
6505
6728
  isExternal: isInsideNodeModules,
6506
6729
  packageName: isInsideNodeModules ? extractPackageNameFromSpecifier(cleanedSpecifier) : void 0
6507
6730
  };
@@ -6616,7 +6839,14 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
6616
6839
  resolveResultCache.set(cacheKey, unresolvedResult);
6617
6840
  return unresolvedResult;
6618
6841
  };
6619
- return { resolveModule };
6842
+ const resolveModuleWithPosixPath = (specifier, fromFile) => {
6843
+ const resolved = resolveModule(specifier, fromFile);
6844
+ return resolved.resolvedPath ? {
6845
+ ...resolved,
6846
+ resolvedPath: toPosixPath(resolved.resolvedPath)
6847
+ } : resolved;
6848
+ };
6849
+ return { resolveModule: resolveModuleWithPosixPath };
6620
6850
  };
6621
6851
  const stripJsonComments = (content) => {
6622
6852
  let result = "";
@@ -6682,7 +6912,7 @@ const isConfigFile = (filePath) => {
6682
6912
  //#region src/linker/build.ts
6683
6913
  const buildDependencyGraph = (inputs) => {
6684
6914
  const fileIdMap = /* @__PURE__ */ new Map();
6685
- for (const input of inputs) fileIdMap.set(input.fileId.path, input.fileId.index);
6915
+ for (const input of inputs) fileIdMap.set(toPosixPath(input.fileId.path), input.fileId.index);
6686
6916
  const modules = inputs.map((input) => ({
6687
6917
  fileId: input.fileId,
6688
6918
  imports: input.parsed.imports,
@@ -6728,7 +6958,7 @@ const buildDependencyGraph = (inputs) => {
6728
6958
  const sourceDir = node_path.default.dirname(input.fileId.path);
6729
6959
  const globPattern = importInfo.specifier;
6730
6960
  for (const [filePath] of fileIdMap) {
6731
- const relativePath = node_path.default.relative(sourceDir, filePath);
6961
+ const relativePath = toPosixPath(node_path.default.relative(sourceDir, filePath));
6732
6962
  if ((0, minimatch.minimatch)(relativePath.startsWith(".") ? relativePath : `./${relativePath}`, globPattern)) {
6733
6963
  const targetIndex = fileIdMap.get(filePath);
6734
6964
  if (targetIndex !== void 0) addEdge(sourceIndex, targetIndex, []);
@@ -6738,7 +6968,7 @@ const buildDependencyGraph = (inputs) => {
6738
6968
  }
6739
6969
  const resolved = input.resolvedImports.get(importInfo.specifier);
6740
6970
  if (!resolved?.resolvedPath) continue;
6741
- const targetIndex = fileIdMap.get(resolved.resolvedPath);
6971
+ const targetIndex = fileIdMap.get(toPosixPath(resolved.resolvedPath));
6742
6972
  if (targetIndex === void 0) continue;
6743
6973
  addEdge(sourceIndex, targetIndex, importInfo.importedNames.map((importedName) => ({
6744
6974
  importedName: importedName.name,
@@ -6753,7 +6983,7 @@ const buildDependencyGraph = (inputs) => {
6753
6983
  if (!exportInfo.isReExport || !exportInfo.reExportSource) continue;
6754
6984
  const resolved = input.resolvedImports.get(exportInfo.reExportSource);
6755
6985
  if (!resolved?.resolvedPath) continue;
6756
- const targetIndex = fileIdMap.get(resolved.resolvedPath);
6986
+ const targetIndex = fileIdMap.get(toPosixPath(resolved.resolvedPath));
6757
6987
  if (targetIndex === void 0) continue;
6758
6988
  const exportedName = exportInfo.isNamespaceReExport ? "*" : exportInfo.name;
6759
6989
  const originalName = exportInfo.isNamespaceReExport ? "*" : exportInfo.reExportOriginalName ?? exportInfo.name;
@@ -9882,12 +10112,19 @@ const visitFlagPatternsInExpression = (node, context) => {
9882
10112
  if (node.type === "CallExpression") {
9883
10113
  const callee = node.callee;
9884
10114
  let functionName;
10115
+ let calleeIsMemberExpression = false;
10116
+ let receiverIsItselfMemberExpression = false;
9885
10117
  if (isAstNode(callee)) {
9886
10118
  if (callee.type === "Identifier") functionName = getStaticName(callee);
9887
- else if (callee.type === "MemberExpression" || callee.type === "StaticMemberExpression") functionName = getStaticName(callee.property);
10119
+ else if (callee.type === "MemberExpression" || callee.type === "StaticMemberExpression") {
10120
+ calleeIsMemberExpression = true;
10121
+ functionName = getStaticName(callee.property);
10122
+ const receiver = callee.object;
10123
+ if (isAstNode(receiver) && (receiver.type === "MemberExpression" || receiver.type === "StaticMemberExpression")) receiverIsItselfMemberExpression = true;
10124
+ }
9888
10125
  }
9889
10126
  if (functionName !== void 0) {
9890
- if (context.vercelFlagsLocalNames.has(functionName) || VERCEL_FLAGS_FUNCTION_NAMES.has(functionName)) {
10127
+ if (!calleeIsMemberExpression && context.vercelFlagsLocalNames.has(functionName)) {
9891
10128
  const callArguments = node.arguments;
9892
10129
  const flagName = extractStringArgument(callArguments, 0);
9893
10130
  if (flagName !== void 0) {
@@ -9896,6 +10133,7 @@ const visitFlagPatternsInExpression = (node, context) => {
9896
10133
  }
9897
10134
  return;
9898
10135
  }
10136
+ if (calleeIsMemberExpression && receiverIsItselfMemberExpression) return;
9899
10137
  for (const sdkPattern of context.sdkPatterns) {
9900
10138
  if (sdkPattern.functionName !== functionName) continue;
9901
10139
  const callArguments = node.arguments;
@@ -10441,11 +10679,16 @@ const collectLocalTypeNames = (programNode) => {
10441
10679
  *
10442
10680
  * `Story` is intentionally a local alias — consumers don't import it; the
10443
10681
  * Storybook runtime reads the default export. Flagging this as a leak
10444
- * produces near-100% false positives on Storybook codebases, so skip
10445
- * `*.stories.{ts,tsx,js,jsx,mts,mjs,cts,cjs}` files entirely.
10682
+ * produces near-100% false positives on Storybook codebases, so skip story
10683
+ * files entirely. Storybook supports both common glob conventions — match
10684
+ * the `*.stories.{ext}` style as well as a bare `stories.{ext}` basename.
10446
10685
  */
10447
- const STORYBOOK_STORY_FILE_PATTERN = /\.stories\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/;
10448
- const isStorybookStoryFile = (filePath) => STORYBOOK_STORY_FILE_PATTERN.test(filePath);
10686
+ const STORYBOOK_STORY_BASENAME_PATTERN = /^(?:.*\.)?stories\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/i;
10687
+ const isStorybookStoryFile = (filePath) => {
10688
+ const lastSlash = filePath.lastIndexOf("/");
10689
+ const basename = lastSlash === -1 ? filePath : filePath.slice(lastSlash + 1);
10690
+ return STORYBOOK_STORY_BASENAME_PATTERN.test(basename);
10691
+ };
10449
10692
  /**
10450
10693
  * Detect TypeScript "private type leak": an exported declaration's signature
10451
10694
  * references a type that was declared locally in the same module but is not
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region src/errors.d.ts
2
- type DeslopErrorCode = "file-read-failed" | "file-too-large" | "file-empty" | "file-binary" | "file-minified" | "parse-failed" | "parse-recovered" | "ast-walk-failed" | "ast-walk-depth-exceeded" | "tsconfig-not-found" | "tsconfig-parse-failed" | "ts-program-creation-failed" | "ts-program-too-large" | "ts-not-loadable" | "package-json-not-found" | "package-json-parse-failed" | "workspace-discovery-failed" | "resolver-init-failed" | "monorepo-discovery-failed" | "detector-failed" | "config-invalid" | "system-out-of-memory" | "unknown";
2
+ type DeslopErrorCode = "file-read-failed" | "file-too-large" | "file-empty" | "file-binary" | "file-minified" | "parse-failed" | "parse-recovered" | "parse-recovered-partial" | "ast-walk-failed" | "ast-walk-depth-exceeded" | "tsconfig-not-found" | "tsconfig-parse-failed" | "ts-program-creation-failed" | "ts-program-too-large" | "ts-not-loadable" | "package-json-not-found" | "package-json-parse-failed" | "workspace-discovery-failed" | "resolver-init-failed" | "monorepo-discovery-failed" | "detector-failed" | "config-invalid" | "system-out-of-memory" | "unknown";
3
3
  type DeslopErrorModule = "collect" | "parse" | "linker" | "resolver" | "report" | "semantic" | "config";
4
4
  type DeslopErrorSeverity = "fatal" | "warning" | "info";
5
5
  interface DeslopErrorInput {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region src/errors.d.ts
2
- type DeslopErrorCode = "file-read-failed" | "file-too-large" | "file-empty" | "file-binary" | "file-minified" | "parse-failed" | "parse-recovered" | "ast-walk-failed" | "ast-walk-depth-exceeded" | "tsconfig-not-found" | "tsconfig-parse-failed" | "ts-program-creation-failed" | "ts-program-too-large" | "ts-not-loadable" | "package-json-not-found" | "package-json-parse-failed" | "workspace-discovery-failed" | "resolver-init-failed" | "monorepo-discovery-failed" | "detector-failed" | "config-invalid" | "system-out-of-memory" | "unknown";
2
+ type DeslopErrorCode = "file-read-failed" | "file-too-large" | "file-empty" | "file-binary" | "file-minified" | "parse-failed" | "parse-recovered" | "parse-recovered-partial" | "ast-walk-failed" | "ast-walk-depth-exceeded" | "tsconfig-not-found" | "tsconfig-parse-failed" | "ts-program-creation-failed" | "ts-program-too-large" | "ts-not-loadable" | "package-json-not-found" | "package-json-parse-failed" | "workspace-discovery-failed" | "resolver-init-failed" | "monorepo-discovery-failed" | "detector-failed" | "config-invalid" | "system-out-of-memory" | "unknown";
3
3
  type DeslopErrorModule = "collect" | "parse" | "linker" | "resolver" | "report" | "semantic" | "config";
4
4
  type DeslopErrorSeverity = "fatal" | "warning" | "info";
5
5
  interface DeslopErrorInput {
package/dist/index.mjs CHANGED
@@ -3,9 +3,9 @@ import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import fg from "fast-glob";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { parseSync } from "oxc-parser";
6
+ import ts from "typescript";
6
7
  import { ResolverFactory } from "oxc-resolver";
7
8
  import { minimatch } from "minimatch";
8
- import ts from "typescript";
9
9
 
10
10
  //#region src/constants.ts
11
11
  const DEFAULT_EXTENSIONS = [
@@ -1544,10 +1544,24 @@ const extractMdxImportsExports = (sourceText) => {
1544
1544
  return statements.join("\n");
1545
1545
  };
1546
1546
  const ASTRO_FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---/;
1547
- const extractAstroFrontmatter = (sourceText) => {
1547
+ const ASTRO_SCRIPT_TAG_PATTERN = /<script\b([^>]*?)\/>|<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
1548
+ const ASTRO_SCRIPT_SRC_ATTRIBUTE_PATTERN = /\bsrc\s*=\s*["']([^"']+)["']/i;
1549
+ const extractAstroSources = (sourceText) => {
1550
+ const sections = [];
1548
1551
  const frontmatterMatch = sourceText.match(ASTRO_FRONTMATTER_PATTERN);
1549
- if (!frontmatterMatch) return "";
1550
- return frontmatterMatch[1];
1552
+ if (frontmatterMatch) sections.push(frontmatterMatch[1]);
1553
+ ASTRO_SCRIPT_TAG_PATTERN.lastIndex = 0;
1554
+ let scriptMatch;
1555
+ while ((scriptMatch = ASTRO_SCRIPT_TAG_PATTERN.exec(sourceText)) !== null) {
1556
+ const selfClosingAttributes = scriptMatch[1];
1557
+ const pairedAttributes = scriptMatch[2];
1558
+ const attributes = selfClosingAttributes ?? pairedAttributes ?? "";
1559
+ const body = selfClosingAttributes === void 0 ? scriptMatch[3] ?? "" : "";
1560
+ const srcMatch = attributes.match(ASTRO_SCRIPT_SRC_ATTRIBUTE_PATTERN);
1561
+ if (srcMatch) sections.push(`import ${JSON.stringify(srcMatch[1])};`);
1562
+ if (body) sections.push(body);
1563
+ }
1564
+ return sections.join("\n");
1551
1565
  };
1552
1566
  const VUE_SCRIPT_PATTERN = /<script[^>]*(?:lang=["'](?:ts|tsx)["'][^>]*)?>([\s\S]*?)<\/script>/gi;
1553
1567
  const extractVueScriptContent = (sourceText) => {
@@ -1765,7 +1779,8 @@ const parseSourceFile = (filePath) => {
1765
1779
  const isAstro = filePath.endsWith(".astro");
1766
1780
  const isVue = filePath.endsWith(".vue");
1767
1781
  const isSvelte = filePath.endsWith(".svelte");
1768
- const textToParse = isMdx ? extractMdxImportsExports(sourceText) : isAstro ? extractAstroFrontmatter(sourceText) : isVue ? extractVueScriptContent(sourceText) : isSvelte ? extractSvelteScriptContent(sourceText) : sourceText;
1782
+ const isPreprocessed = isMdx || isAstro || isVue || isSvelte;
1783
+ const textToParse = isMdx ? extractMdxImportsExports(sourceText) : isAstro ? extractAstroSources(sourceText) : isVue ? extractVueScriptContent(sourceText) : isSvelte ? extractSvelteScriptContent(sourceText) : sourceText;
1769
1784
  const parseFileName = isMdx || isAstro || isVue || isSvelte ? filePath.replace(/\.(mdx|astro|vue|svelte)$/, ".tsx") : filePath;
1770
1785
  let result;
1771
1786
  try {
@@ -1789,7 +1804,7 @@ const parseSourceFile = (filePath) => {
1789
1804
  if (tsxResult.errors.length === 0) result = tsxResult;
1790
1805
  }
1791
1806
  } catch {}
1792
- if (result.errors.length > 0) return {
1807
+ if (result.errors.length > 0 && !isPreprocessed) return {
1793
1808
  ...createEmptyParsedSource(),
1794
1809
  imports,
1795
1810
  exports,
@@ -1801,6 +1816,12 @@ const parseSourceFile = (filePath) => {
1801
1816
  path: filePath
1802
1817
  })]
1803
1818
  };
1819
+ if (result.errors.length > 0) earlyErrors.push(new ParseError({
1820
+ code: "parse-recovered-partial",
1821
+ severity: "info",
1822
+ message: `oxc-parser reported ${result.errors.length} syntax issue(s) in extracted ${isAstro ? "Astro" : isVue ? "Vue" : isSvelte ? "Svelte" : "MDX"} sources; continuing with partial AST`,
1823
+ path: filePath
1824
+ }));
1804
1825
  const program = result.program;
1805
1826
  if (!program?.body) return {
1806
1827
  ...createEmptyParsedSource(),
@@ -3093,6 +3114,7 @@ const EXPO_ENTRY_PATTERNS = [
3093
3114
  ];
3094
3115
  const EXPO_ROUTER_ENTRY_PATTERNS = [
3095
3116
  "app/**/*.{ts,tsx,js,jsx}",
3117
+ "src/app/**/*.{ts,tsx,js,jsx}",
3096
3118
  "app.config.{ts,js,mjs,cjs}",
3097
3119
  "metro.config.{ts,js,mjs,cjs}",
3098
3120
  "babel.config.{ts,js,mjs,cjs}"
@@ -3135,6 +3157,196 @@ const discoverMobileEntryPoints = (directory) => {
3135
3157
  }
3136
3158
  };
3137
3159
 
3160
+ //#endregion
3161
+ //#region src/collect/expo-config-plugin-entries.ts
3162
+ const EXPO_CONFIG_FILE_GLOBS = ["app.config.{ts,mts,cts,js,mjs,cjs}", "app.json"];
3163
+ const NESTED_EXPO_CONFIG_FILE_GLOBS = [
3164
+ ...EXPO_CONFIG_FILE_GLOBS,
3165
+ "**/app.config.{ts,mts,cts,js,mjs,cjs}",
3166
+ "**/app.json"
3167
+ ];
3168
+ const EXPO_REACT_NATIVE_DEPENDENCIES = new Set(["expo", "react-native"]);
3169
+ const EXPO_PLUGIN_RESOLVABLE_EXTENSIONS = SOURCE_EXTENSIONS$3.map((sourceExtension) => `.${sourceExtension}`);
3170
+ const isRecord = (value) => typeof value === "object" && value !== null;
3171
+ const isExpoOrReactNativeWorkspace = (dependencies) => [...EXPO_REACT_NATIVE_DEPENDENCIES].some((dependencyName) => dependencyName in dependencies);
3172
+ const isLocalExpoPluginPath = (value) => (value.startsWith("./") || value.startsWith("../")) && !value.includes("*") && !value.includes("?");
3173
+ const isFile = (filePath) => {
3174
+ try {
3175
+ return statSync(filePath).isFile();
3176
+ } catch {
3177
+ return false;
3178
+ }
3179
+ };
3180
+ const resolveExpoPluginPath = (configDirectory, pluginPath) => {
3181
+ const candidatePath = resolve(configDirectory, pluginPath);
3182
+ if (isFile(candidatePath)) return candidatePath;
3183
+ for (const extension of EXPO_PLUGIN_RESOLVABLE_EXTENSIONS) {
3184
+ const candidatePathWithExtension = `${candidatePath}${extension}`;
3185
+ if (isFile(candidatePathWithExtension)) return candidatePathWithExtension;
3186
+ }
3187
+ for (const extension of EXPO_PLUGIN_RESOLVABLE_EXTENSIONS) {
3188
+ const indexCandidatePath = join(candidatePath, `index${extension}`);
3189
+ if (isFile(indexCandidatePath)) return indexCandidatePath;
3190
+ }
3191
+ };
3192
+ const addExpoPluginEntry = (entries, rootDirectory, configDirectory, pluginPath) => {
3193
+ if (!isLocalExpoPluginPath(pluginPath)) return;
3194
+ const resolvedPath = resolveExpoPluginPath(configDirectory, pluginPath);
3195
+ if (!resolvedPath) return;
3196
+ const relativePath = relative(rootDirectory, resolvedPath);
3197
+ if (relativePath !== "" && (relativePath.startsWith("..") || isAbsolute(relativePath))) return;
3198
+ entries.add(resolvedPath);
3199
+ };
3200
+ const getPropertyName = (name) => {
3201
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text;
3202
+ };
3203
+ const unwrapExpression = (expression) => {
3204
+ let currentExpression = expression;
3205
+ while (ts.isParenthesizedExpression(currentExpression)) currentExpression = currentExpression.expression;
3206
+ return currentExpression;
3207
+ };
3208
+ const collectExpoPluginPathsFromArray = (array, entries, rootDirectory, configDirectory) => {
3209
+ for (const element of array.elements) {
3210
+ if (ts.isStringLiteral(element) || ts.isNoSubstitutionTemplateLiteral(element)) {
3211
+ addExpoPluginEntry(entries, rootDirectory, configDirectory, element.text);
3212
+ continue;
3213
+ }
3214
+ if (ts.isArrayLiteralExpression(element)) {
3215
+ const [pluginName] = element.elements;
3216
+ if (pluginName && (ts.isStringLiteral(pluginName) || ts.isNoSubstitutionTemplateLiteral(pluginName))) addExpoPluginEntry(entries, rootDirectory, configDirectory, pluginName.text);
3217
+ }
3218
+ }
3219
+ };
3220
+ const collectExpoPluginPathsFromConfigObject = (objectLiteral, entries, rootDirectory, configDirectory) => {
3221
+ for (const property of objectLiteral.properties) if (ts.isPropertyAssignment(property) && getPropertyName(property.name) === "plugins" && ts.isArrayLiteralExpression(property.initializer)) collectExpoPluginPathsFromArray(property.initializer, entries, rootDirectory, configDirectory);
3222
+ };
3223
+ const collectReturnedExpoConfigPluginPaths = (body, entries, rootDirectory, configDirectory) => {
3224
+ if (!ts.isBlock(body)) {
3225
+ const expression = unwrapExpression(body);
3226
+ if (ts.isObjectLiteralExpression(expression)) collectExpoPluginPathsFromConfigObject(expression, entries, rootDirectory, configDirectory);
3227
+ return;
3228
+ }
3229
+ const visit = (node) => {
3230
+ if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) return;
3231
+ if (ts.isReturnStatement(node) && node.expression) {
3232
+ const expression = unwrapExpression(node.expression);
3233
+ if (ts.isObjectLiteralExpression(expression)) collectExpoPluginPathsFromConfigObject(expression, entries, rootDirectory, configDirectory);
3234
+ return;
3235
+ }
3236
+ ts.forEachChild(node, visit);
3237
+ };
3238
+ visit(body);
3239
+ };
3240
+ const collectExpoPluginPathsFromConfigExpression = (expression, entries, rootDirectory, configDirectory, bindings, seenIdentifiers = /* @__PURE__ */ new Set()) => {
3241
+ const configExpression = unwrapExpression(expression);
3242
+ if (ts.isObjectLiteralExpression(configExpression)) {
3243
+ collectExpoPluginPathsFromConfigObject(configExpression, entries, rootDirectory, configDirectory);
3244
+ return;
3245
+ }
3246
+ if (ts.isIdentifier(configExpression)) {
3247
+ if (seenIdentifiers.has(configExpression.text)) return;
3248
+ seenIdentifiers.add(configExpression.text);
3249
+ const boundExpression = bindings.expressions.get(configExpression.text);
3250
+ if (boundExpression) {
3251
+ collectExpoPluginPathsFromConfigExpression(boundExpression, entries, rootDirectory, configDirectory, bindings, seenIdentifiers);
3252
+ return;
3253
+ }
3254
+ const boundFunction = bindings.functions.get(configExpression.text);
3255
+ if (boundFunction?.body) collectReturnedExpoConfigPluginPaths(boundFunction.body, entries, rootDirectory, configDirectory);
3256
+ return;
3257
+ }
3258
+ if (ts.isArrowFunction(configExpression)) {
3259
+ collectReturnedExpoConfigPluginPaths(configExpression.body, entries, rootDirectory, configDirectory);
3260
+ return;
3261
+ }
3262
+ if (ts.isFunctionExpression(configExpression) && configExpression.body) collectReturnedExpoConfigPluginPaths(configExpression.body, entries, rootDirectory, configDirectory);
3263
+ };
3264
+ const hasDefaultExportModifier = (node) => Boolean(ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword));
3265
+ const isModuleExportsAssignmentTarget = (node) => ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "module" && node.name.text === "exports";
3266
+ const collectStaticConfigBindings = (sourceFile) => {
3267
+ const expressions = /* @__PURE__ */ new Map();
3268
+ const functions = /* @__PURE__ */ new Map();
3269
+ for (const statement of sourceFile.statements) {
3270
+ if (ts.isVariableStatement(statement)) {
3271
+ for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.initializer) expressions.set(declaration.name.text, declaration.initializer);
3272
+ continue;
3273
+ }
3274
+ if (ts.isFunctionDeclaration(statement) && statement.name) functions.set(statement.name.text, statement);
3275
+ }
3276
+ return {
3277
+ expressions,
3278
+ functions
3279
+ };
3280
+ };
3281
+ const collectExpoPluginPathsFromAppConfig = (configPath, entries, rootDirectory) => {
3282
+ const extension = extname(configPath);
3283
+ const sourceFile = ts.createSourceFile(configPath, readFileSync(configPath, "utf8"), ts.ScriptTarget.Latest, true, extension === ".ts" || extension === ".mts" || extension === ".cts" ? ts.ScriptKind.TS : ts.ScriptKind.JS);
3284
+ const configDirectory = dirname(configPath);
3285
+ const bindings = collectStaticConfigBindings(sourceFile);
3286
+ const visit = (node) => {
3287
+ if (ts.isExportAssignment(node)) {
3288
+ collectExpoPluginPathsFromConfigExpression(node.expression, entries, rootDirectory, configDirectory, bindings);
3289
+ return;
3290
+ }
3291
+ if (ts.isFunctionDeclaration(node) && hasDefaultExportModifier(node) && node.body) {
3292
+ collectReturnedExpoConfigPluginPaths(node.body, entries, rootDirectory, configDirectory);
3293
+ return;
3294
+ }
3295
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && isModuleExportsAssignmentTarget(node.left)) {
3296
+ collectExpoPluginPathsFromConfigExpression(node.right, entries, rootDirectory, configDirectory, bindings);
3297
+ return;
3298
+ }
3299
+ ts.forEachChild(node, visit);
3300
+ };
3301
+ visit(sourceFile);
3302
+ };
3303
+ const collectPluginPathsFromJsonValue = (value) => {
3304
+ if (!Array.isArray(value)) return [];
3305
+ const pluginPaths = [];
3306
+ for (const plugin of value) {
3307
+ if (typeof plugin === "string") {
3308
+ pluginPaths.push(plugin);
3309
+ continue;
3310
+ }
3311
+ if (Array.isArray(plugin) && typeof plugin[0] === "string") pluginPaths.push(plugin[0]);
3312
+ }
3313
+ return pluginPaths;
3314
+ };
3315
+ const collectExpoPluginPathsFromAppJson = (configPath, entries, rootDirectory) => {
3316
+ const parsedJson = JSON.parse(readFileSync(configPath, "utf8"));
3317
+ const configDirectory = dirname(configPath);
3318
+ if (!isRecord(parsedJson)) return;
3319
+ const expoConfig = parsedJson.expo;
3320
+ const expoPluginPaths = isRecord(expoConfig) ? collectPluginPathsFromJsonValue(expoConfig.plugins) : [];
3321
+ for (const pluginPath of [...expoPluginPaths, ...collectPluginPathsFromJsonValue(parsedJson.plugins)]) addExpoPluginEntry(entries, rootDirectory, configDirectory, pluginPath);
3322
+ };
3323
+ const collectExpoPluginPathsFromConfig = (configPath, entries, rootDirectory) => {
3324
+ try {
3325
+ if (basename(configPath) === "app.json") {
3326
+ collectExpoPluginPathsFromAppJson(configPath, entries, rootDirectory);
3327
+ return;
3328
+ }
3329
+ collectExpoPluginPathsFromAppConfig(configPath, entries, rootDirectory);
3330
+ } catch {}
3331
+ };
3332
+ const extractExpoConfigPluginEntries = (directory, dependencies, rootDirectory = directory, includeNestedConfigs = true) => {
3333
+ if (!isExpoOrReactNativeWorkspace(dependencies)) return [];
3334
+ const entries = /* @__PURE__ */ new Set();
3335
+ const configPaths = fg.sync(includeNestedConfigs ? NESTED_EXPO_CONFIG_FILE_GLOBS : EXPO_CONFIG_FILE_GLOBS, {
3336
+ cwd: directory,
3337
+ absolute: true,
3338
+ onlyFiles: true,
3339
+ ignore: [
3340
+ "**/node_modules/**",
3341
+ "**/dist/**",
3342
+ "**/build/**"
3343
+ ],
3344
+ deep: 6
3345
+ });
3346
+ for (const configPath of configPaths) collectExpoPluginPathsFromConfig(configPath, entries, rootDirectory);
3347
+ return [...entries];
3348
+ };
3349
+
3138
3350
  //#endregion
3139
3351
  //#region src/resolver/source-path.ts
3140
3352
  const SOURCE_EXTENSIONS$1 = [
@@ -3442,6 +3654,10 @@ const extractSectionsModuleEntries = (directory) => {
3442
3654
  return [...entries];
3443
3655
  };
3444
3656
 
3657
+ //#endregion
3658
+ //#region src/utils/to-posix-path.ts
3659
+ const toPosixPath = (filePath) => filePath.replace(/\\/g, "/");
3660
+
3445
3661
  //#endregion
3446
3662
  //#region src/collect/entries.ts
3447
3663
  const collectSourceFiles = async (config) => {
@@ -3558,6 +3774,11 @@ const resolveEntries = async (config) => {
3558
3774
  for (const workspacePackage of entryEligiblePackages) tsConfigIncludeEntries.push(...extractTsConfigIncludeFilesEntries(workspacePackage.directory));
3559
3775
  const configStringEntries = extractConfigStringReferencedEntries(absoluteRoot);
3560
3776
  for (const workspacePackage of entryEligiblePackages) configStringEntries.push(...extractConfigStringReferencedEntries(workspacePackage.directory));
3777
+ const expoConfigPluginEntries = extractExpoConfigPluginEntries(absoluteRoot, readPackageJsonDependencies(join(absoluteRoot, "package.json")), absoluteRoot, false);
3778
+ for (const workspacePackage of entryEligiblePackages) {
3779
+ const workspacePackageDependencies = readPackageJsonDependencies(join(workspacePackage.directory, "package.json"));
3780
+ expoConfigPluginEntries.push(...extractExpoConfigPluginEntries(workspacePackage.directory, workspacePackageDependencies, absoluteRoot));
3781
+ }
3561
3782
  const sectionsModuleEntries = extractSectionsModuleEntries(absoluteRoot);
3562
3783
  const wranglerEntries = extractWranglerEntries(absoluteRoot);
3563
3784
  for (const workspacePackage of entryEligiblePackages) wranglerEntries.push(...extractWranglerEntries(workspacePackage.directory));
@@ -3568,7 +3789,7 @@ const resolveEntries = async (config) => {
3568
3789
  const testRunnerDiscovery = discoverTestRunnerEntryPoints(absoluteRoot, entryEligiblePackages);
3569
3790
  const toolingDiscovery = discoverToolingEntryPoints(absoluteRoot, entryEligiblePackages);
3570
3791
  const ciEntries = extractCiWorkflowEntries(absoluteRoot);
3571
- const testEntries = [...new Set([...testRunnerDiscovery.entryFiles, ...testSetupEntries])];
3792
+ const testEntries = [...new Set([...testRunnerDiscovery.entryFiles, ...testSetupEntries].map(toPosixPath))];
3572
3793
  const testEntryPathSet = new Set(testEntries);
3573
3794
  return {
3574
3795
  productionEntries: [...new Set([
@@ -3586,14 +3807,15 @@ const resolveEntries = async (config) => {
3586
3807
  ...webWorkerEntries,
3587
3808
  ...tsConfigIncludeEntries,
3588
3809
  ...configStringEntries,
3810
+ ...expoConfigPluginEntries,
3589
3811
  ...sectionsModuleEntries,
3590
3812
  ...wranglerEntries,
3591
3813
  ...pluginFileEntries,
3592
3814
  ...toolingDiscovery.entryFiles,
3593
3815
  ...ciEntries
3594
- ])].filter((entryPath) => !testEntryPathSet.has(entryPath)),
3816
+ ].map(toPosixPath))].filter((entryPath) => !testEntryPathSet.has(entryPath)),
3595
3817
  testEntries,
3596
- alwaysUsedFiles: [...new Set([...toolingDiscovery.alwaysUsedFiles, ...testRunnerDiscovery.alwaysUsedFiles])]
3818
+ alwaysUsedFiles: [...new Set([...toolingDiscovery.alwaysUsedFiles, ...testRunnerDiscovery.alwaysUsedFiles].map(toPosixPath))]
3597
3819
  };
3598
3820
  };
3599
3821
  const DEFAULT_INDEX_PATTERNS = [
@@ -5461,7 +5683,7 @@ const FRAMEWORK_PATTERNS = [
5461
5683
  "app/_layout.{ts,tsx,js,jsx}",
5462
5684
  "app/index.{ts,tsx,js,jsx}"
5463
5685
  ],
5464
- alwaysUsed: ["app.json", "app.config.{ts,js}"]
5686
+ alwaysUsed: ["app.json", "app.config.{ts,mts,cts,js,mjs,cjs}"]
5465
5687
  },
5466
5688
  {
5467
5689
  enablers: ["wrangler"],
@@ -6468,9 +6690,10 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
6468
6690
  try {
6469
6691
  const resolverResult = activeResolver.sync(fromDir, cleanedSpecifier);
6470
6692
  if (resolverResult.path) {
6471
- const isInsideNodeModules = resolverResult.path.includes("/node_modules/");
6693
+ const normalizedResolvedPath = toPosixPath(resolverResult.path);
6694
+ const isInsideNodeModules = normalizedResolvedPath.includes("/node_modules/");
6472
6695
  return {
6473
- resolvedPath: isInsideNodeModules ? void 0 : resolverResult.path,
6696
+ resolvedPath: isInsideNodeModules ? void 0 : normalizedResolvedPath,
6474
6697
  isExternal: isInsideNodeModules,
6475
6698
  packageName: isInsideNodeModules ? extractPackageNameFromSpecifier(cleanedSpecifier) : void 0
6476
6699
  };
@@ -6585,7 +6808,14 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
6585
6808
  resolveResultCache.set(cacheKey, unresolvedResult);
6586
6809
  return unresolvedResult;
6587
6810
  };
6588
- return { resolveModule };
6811
+ const resolveModuleWithPosixPath = (specifier, fromFile) => {
6812
+ const resolved = resolveModule(specifier, fromFile);
6813
+ return resolved.resolvedPath ? {
6814
+ ...resolved,
6815
+ resolvedPath: toPosixPath(resolved.resolvedPath)
6816
+ } : resolved;
6817
+ };
6818
+ return { resolveModule: resolveModuleWithPosixPath };
6589
6819
  };
6590
6820
  const stripJsonComments = (content) => {
6591
6821
  let result = "";
@@ -6651,7 +6881,7 @@ const isConfigFile = (filePath) => {
6651
6881
  //#region src/linker/build.ts
6652
6882
  const buildDependencyGraph = (inputs) => {
6653
6883
  const fileIdMap = /* @__PURE__ */ new Map();
6654
- for (const input of inputs) fileIdMap.set(input.fileId.path, input.fileId.index);
6884
+ for (const input of inputs) fileIdMap.set(toPosixPath(input.fileId.path), input.fileId.index);
6655
6885
  const modules = inputs.map((input) => ({
6656
6886
  fileId: input.fileId,
6657
6887
  imports: input.parsed.imports,
@@ -6697,7 +6927,7 @@ const buildDependencyGraph = (inputs) => {
6697
6927
  const sourceDir = path.dirname(input.fileId.path);
6698
6928
  const globPattern = importInfo.specifier;
6699
6929
  for (const [filePath] of fileIdMap) {
6700
- const relativePath = path.relative(sourceDir, filePath);
6930
+ const relativePath = toPosixPath(path.relative(sourceDir, filePath));
6701
6931
  if (minimatch(relativePath.startsWith(".") ? relativePath : `./${relativePath}`, globPattern)) {
6702
6932
  const targetIndex = fileIdMap.get(filePath);
6703
6933
  if (targetIndex !== void 0) addEdge(sourceIndex, targetIndex, []);
@@ -6707,7 +6937,7 @@ const buildDependencyGraph = (inputs) => {
6707
6937
  }
6708
6938
  const resolved = input.resolvedImports.get(importInfo.specifier);
6709
6939
  if (!resolved?.resolvedPath) continue;
6710
- const targetIndex = fileIdMap.get(resolved.resolvedPath);
6940
+ const targetIndex = fileIdMap.get(toPosixPath(resolved.resolvedPath));
6711
6941
  if (targetIndex === void 0) continue;
6712
6942
  addEdge(sourceIndex, targetIndex, importInfo.importedNames.map((importedName) => ({
6713
6943
  importedName: importedName.name,
@@ -6722,7 +6952,7 @@ const buildDependencyGraph = (inputs) => {
6722
6952
  if (!exportInfo.isReExport || !exportInfo.reExportSource) continue;
6723
6953
  const resolved = input.resolvedImports.get(exportInfo.reExportSource);
6724
6954
  if (!resolved?.resolvedPath) continue;
6725
- const targetIndex = fileIdMap.get(resolved.resolvedPath);
6955
+ const targetIndex = fileIdMap.get(toPosixPath(resolved.resolvedPath));
6726
6956
  if (targetIndex === void 0) continue;
6727
6957
  const exportedName = exportInfo.isNamespaceReExport ? "*" : exportInfo.name;
6728
6958
  const originalName = exportInfo.isNamespaceReExport ? "*" : exportInfo.reExportOriginalName ?? exportInfo.name;
@@ -9851,12 +10081,19 @@ const visitFlagPatternsInExpression = (node, context) => {
9851
10081
  if (node.type === "CallExpression") {
9852
10082
  const callee = node.callee;
9853
10083
  let functionName;
10084
+ let calleeIsMemberExpression = false;
10085
+ let receiverIsItselfMemberExpression = false;
9854
10086
  if (isAstNode(callee)) {
9855
10087
  if (callee.type === "Identifier") functionName = getStaticName(callee);
9856
- else if (callee.type === "MemberExpression" || callee.type === "StaticMemberExpression") functionName = getStaticName(callee.property);
10088
+ else if (callee.type === "MemberExpression" || callee.type === "StaticMemberExpression") {
10089
+ calleeIsMemberExpression = true;
10090
+ functionName = getStaticName(callee.property);
10091
+ const receiver = callee.object;
10092
+ if (isAstNode(receiver) && (receiver.type === "MemberExpression" || receiver.type === "StaticMemberExpression")) receiverIsItselfMemberExpression = true;
10093
+ }
9857
10094
  }
9858
10095
  if (functionName !== void 0) {
9859
- if (context.vercelFlagsLocalNames.has(functionName) || VERCEL_FLAGS_FUNCTION_NAMES.has(functionName)) {
10096
+ if (!calleeIsMemberExpression && context.vercelFlagsLocalNames.has(functionName)) {
9860
10097
  const callArguments = node.arguments;
9861
10098
  const flagName = extractStringArgument(callArguments, 0);
9862
10099
  if (flagName !== void 0) {
@@ -9865,6 +10102,7 @@ const visitFlagPatternsInExpression = (node, context) => {
9865
10102
  }
9866
10103
  return;
9867
10104
  }
10105
+ if (calleeIsMemberExpression && receiverIsItselfMemberExpression) return;
9868
10106
  for (const sdkPattern of context.sdkPatterns) {
9869
10107
  if (sdkPattern.functionName !== functionName) continue;
9870
10108
  const callArguments = node.arguments;
@@ -10410,11 +10648,16 @@ const collectLocalTypeNames = (programNode) => {
10410
10648
  *
10411
10649
  * `Story` is intentionally a local alias — consumers don't import it; the
10412
10650
  * Storybook runtime reads the default export. Flagging this as a leak
10413
- * produces near-100% false positives on Storybook codebases, so skip
10414
- * `*.stories.{ts,tsx,js,jsx,mts,mjs,cts,cjs}` files entirely.
10651
+ * produces near-100% false positives on Storybook codebases, so skip story
10652
+ * files entirely. Storybook supports both common glob conventions — match
10653
+ * the `*.stories.{ext}` style as well as a bare `stories.{ext}` basename.
10415
10654
  */
10416
- const STORYBOOK_STORY_FILE_PATTERN = /\.stories\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/;
10417
- const isStorybookStoryFile = (filePath) => STORYBOOK_STORY_FILE_PATTERN.test(filePath);
10655
+ const STORYBOOK_STORY_BASENAME_PATTERN = /^(?:.*\.)?stories\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/i;
10656
+ const isStorybookStoryFile = (filePath) => {
10657
+ const lastSlash = filePath.lastIndexOf("/");
10658
+ const basename = lastSlash === -1 ? filePath : filePath.slice(lastSlash + 1);
10659
+ return STORYBOOK_STORY_BASENAME_PATTERN.test(basename);
10660
+ };
10418
10661
  /**
10419
10662
  * Detect TypeScript "private type leak": an exported declaration's signature
10420
10663
  * references a type that was declared locally in the same module but is not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deslop-js",
3
- "version": "0.0.13",
3
+ "version": "0.0.14-dev.ae0f67a",
4
4
  "description": "Deslop JavaScript code",
5
5
  "keywords": [
6
6
  "dead-code",
@@ -12,11 +12,20 @@
12
12
  "typescript",
13
13
  "unused"
14
14
  ],
15
+ "homepage": "https://github.com/millionco/deslop-js#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/millionco/deslop-js/issues"
18
+ },
15
19
  "license": "MIT",
16
20
  "author": {
17
21
  "name": "Aiden Bai",
18
22
  "email": "aiden@million.dev"
19
23
  },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/millionco/deslop-js.git",
27
+ "directory": "packages/deslop-js"
28
+ },
20
29
  "files": [
21
30
  "dist",
22
31
  "package.json",