prepareStaticTypes method

Future<void> prepareStaticTypes()

Resolves every in-package .dart file with the real Dart front end so a subsequent encode sees a type-resolved AST instead of the syntax-only one parseString produces.

This is what lets DartEncoder dispatch collection methods by RECEIVER TYPE rather than by method name alone (issue #488): on a resolved unit expression.staticType is non-null, so Set<String>.add(x) can be told apart from List<String>.add(x).

Opt-in and fail-soft by design:

  • Resolution is asynchronous (the analyzer exposes no synchronous resolved-unit API) while encode is synchronous, so this is a separate step callers await before encoding rather than a flag.
  • Every failure mode — no .dart_tool/package_config.json (the target package was never pub get-ed), an unreadable file, an analyzer crash — degrades to a warnings entry and an unresolved encode. The tool has always worked on any directory containing a pubspec.yaml regardless of dependency-resolution state, and it still does.
  • Files that fail individually simply stay syntax-only; the rest of the package still benefits.

Cost: the analyzer has a multi-second cold start, so callers that do not need receiver types (Tier A structural studies, plain re-encoding) should keep calling encode directly.

Implementation

Future<void> prepareStaticTypes() async {
  _resolvedUnits.clear();
  final provider = PhysicalResourceProvider.INSTANCE;
  final ctx = provider.pathContext;
  final rootPath = ctx.normalize(packageDir.absolute.path);
  AnalysisContextCollection collection;
  try {
    collection = AnalysisContextCollection(
      includedPaths: <String>[rootPath],
      resourceProvider: provider,
    );
  } on Object catch (e) {
    warnings.add(
      'Static type resolution unavailable for "$packageName" '
      '($rootPath): $e. Encoding without receiver types.',
    );
    return;
  }

  try {
    for (final relPath in _fileToModule.keys) {
      final filePath = ctx.normalize(
        ctx.join(rootPath, ctx.joinAll(relPath.split('/'))),
      );
      if (!File(filePath).existsSync()) continue;
      try {
        final session = collection.contextFor(filePath).currentSession;
        final result = await session.getResolvedUnit(filePath);
        if (result is ResolvedUnitResult) {
          _resolvedUnits[relPath] = result.unit;
        } else {
          warnings.add(
            'Could not resolve "$relPath" (${result.runtimeType}); '
            'encoding it without receiver types.',
          );
        }
      } on Object catch (e) {
        warnings.add(
          'Could not resolve "$relPath": $e; '
          'encoding it without receiver types.',
        );
      }
    }
  } finally {
    await collection.dispose();
  }
}