visitImportDirective method

  1. @override
Object? visitImportDirective(
  1. SImportDirective node
)
override

Visit a SImportDirective.

Implementation

@override
Object? visitImportDirective(SImportDirective node) {
  final importUriString = (node.uri as SSimpleStringLiteral?)?.value;
  if (importUriString == null) {
    Logger.warn(
      "[visitImportDirective] Import directive with null URI string.",
    );
    return null; // Return null if the URI is null
  }

  Logger.debug(
    "[InterpreterVisitor.visitImportDirective] START processing import: $importUriString",
  );

  Uri resolvedUri;
  final importUri = Uri.parse(importUriString);

  if (importUri.hasScheme) {
    // DFUB2 — any already-absolute URI (dart:, package:, file:, …) is
    // self-resolving and needs no base. Resolving an absolute URI against a
    // base returns the absolute URI unchanged, so this only widens the set of
    // imports that succeed WITHOUT a base (notably absolute `file:` imports),
    // matching upstream. The downstream module loader decides whether such a
    // URI is actually loadable.
    resolvedUri = importUri;
    Logger.debug(
      "[visitImportDirective] Using absolute/unresolvable URI: $resolvedUri",
    );
  } else {
    final baseUri = moduleContext.currentLibrary;

    if (baseUri != null) {
      Logger.debug(
        "[visitImportDirective] Attempting to resolve relative URI '$importUriString' relative to '$baseUri'",
      );
      resolvedUri = baseUri.resolveUri(importUri);
      Logger.debug(
        "[visitImportDirective] Resolved relative URI: $resolvedUri",
      );
    } else {
      throw RuntimeD4rtException(
        "Unable to resolve relative import '$importUriString': Base URI not defined in ModuleLoader. "
        "Either provide a basePath parameter or use absolute URIs.",
      );
    }
  }

  final prefixIdentifier = node.prefix; // Get the prefix identifier
  final prefixName = prefixIdentifier?.name;

  // Extract the show/hide combinators from the import directive BEFORE loading
  Set<String>? showNames;
  Set<String>? hideNames;

  for (final combinator in node.combinators) {
    if (combinator is SShowCombinator) {
      showNames ??= {};
      showNames.addAll(combinator.shownNames.map((id) => id.name));
      Logger.debug(
        "[visitImportDirective] Combinator: show ${combinator.shownNames.map((id) => id.name).join(', ')}",
      );
    } else if (combinator is SHideCombinator) {
      hideNames ??= {};
      hideNames.addAll(combinator.hiddenNames.map((id) => id.name));
      Logger.debug(
        "[visitImportDirective] Combinator: hide ${combinator.hiddenNames.map((id) => id.name).join(', ')}",
      );
    }
  }

  Logger.debug(
    "[visitImportDirective] Loading module for resolved URI: $resolvedUri (prefix: $prefixName, show: $showNames, hide: $hideNames)",
  );

  // DFUB13 — this is the TOP-LEVEL directive: the script the user ran, which
  // has no enclosing module to supply owner context, so it is attached here.
  // The owner MUST be read before the call: loadModule advances
  // `currentLibrary` to the module it is loading, so reading it in the catch
  // would report owner == target.
  //
  // It is null for a bare `source:` string, and there the wrap is SKIPPED
  // rather than filled with a placeholder. Owner context earns its keep by
  // distinguishing the file holding the directive from the file that could
  // not be found; with a single anonymous script those coincide, so naming a
  // synthetic owner would restate the target and leak an internal URI.
  final ownerUri = moduleContext.currentLibrary;
  // Pass show/hide to loadModule so bridged content respects the filters
  LoadedModule loadedModule;
  try {
    loadedModule = moduleContext.loadModule(
      resolvedUri,
      showNames: showNames,
      hideNames: hideNames,
    );
  } on D4rtException catch (e) {
    if (ownerUri == null) rethrow;
    throw wrapDirectiveError('import', ownerUri, resolvedUri, e);
  }

  if (prefixName != null) {
    Logger.debug(
      "[visitImportDirective] Importation du module '${resolvedUri.toString()}' avec le préfixe '$prefixName'. Show: $showNames, Hide: $hideNames",
    );

    Environment envForPrefix;
    if (showNames != null || hideNames != null) {
      // Apply show/hide to the exported environment of the loaded module BEFORE defining it for the prefix
      envForPrefix = loadedModule.exportedEnvironment.shallowCopyFiltered(
        showNames: showNames,
        hideNames: hideNames,
      );
    } else {
      envForPrefix = loadedModule.exportedEnvironment;
    }
    environment.definePrefixedImport(prefixName, envForPrefix);
  } else {
    Logger.debug(
      "[visitImportDirective] Direct import of module '${resolvedUri.toString()}' into the current environment. Show: $showNames, Hide: $hideNames",
    );
    // Apply show/hide directly during import into the current environment
    environment.importEnvironment(
      loadedModule.exportedEnvironment,
      show: showNames,
      hide: hideNames,
    );
  }
  return null; // Import directives do not produce a value.
}