buildFromEnvironment static method

IntrospectionResult buildFromEnvironment(
  1. Environment environment, {
  2. bool includeBuiltins = false,
  3. SCompilationUnit? compilationUnit,
})

Builds an IntrospectionResult from the given environment. Optionally takes a SCompilationUnit to extract additional metadata.

Implementation

static IntrospectionResult buildFromEnvironment(
  Environment environment, {
  bool includeBuiltins = false,
  SCompilationUnit? compilationUnit,
}) {
  final functions = <FunctionInfo>[];
  final classes = <ClassInfo>[];
  final variables = <VariableInfo>[];
  final enums = <EnumInfo>[];
  final extensions = <ExtensionInfo>[];

  // Build a map of variable type metadata from the AST
  final variableTypeMap = <String, String>{};
  final processedExtensions = <String>{};

  // Cluster INTROSPECT (I-FILE-36/38): when a compilation unit is provided,
  // collect the names actually declared in the user's source. The
  // environment also contains pre-registered stdlib natives (e.g.
  // `identityHashCode`, `Object`, `print`); the legacy `_isBuiltinName`
  // blocklist covered the common type names but missed many others
  // (`identityHashCode` in particular). Driving the filter from the AST
  // is exhaustive — anything not declared by the user is a built-in.
  final Set<String>? userDeclaredNames =
      compilationUnit == null ? null : <String>{};

  if (compilationUnit != null) {
    // Extract variable type annotations from AST
    for (final declaration in compilationUnit.declarations) {
      if (declaration is STopLevelVariableDeclaration) {
        final typeAnnotation = declaration.variables?.type;
        final typeName = _typeNodeToString(typeAnnotation);
        for (final variable in declaration.variables?.variables ?? []) {
          final varName = variable.name?.name ?? '';
          if (typeName != null) {
            variableTypeMap[varName] = typeName;
          }
          if (varName.isNotEmpty) {
            userDeclaredNames!.add(varName);
          }
        }
      } else if (declaration is SExtensionDeclaration) {
        // Handle extensions (including unnamed ones) from AST
        final extName = declaration.name?.name ?? '';
        final onTypeNode = declaration.extendedType;
        final onType = _typeNodeToString(onTypeNode) ?? 'unknown';
        final methodNames = <String>[];

        for (final member in declaration.members) {
          if (member is SMethodDeclaration) {
            methodNames.add(member.name?.name ?? '');
          }
        }

        if (extName.isEmpty) {
          // Unnamed extension - create with generated name
          extensions.add(ExtensionInfo(
            name: '<unnamed extension on $onType>',
            onType: onType,
            methods: methodNames,
          ));
        } else {
          userDeclaredNames!.add(extName);
        }
        processedExtensions.add(extName);
      } else if (declaration is SNamedDeclaration) {
        // SClassDeclaration, SEnumDeclaration, SFunctionDeclaration,
        // SMixinDeclaration, SExtensionTypeDeclaration, STypedefDeclaration
        // — all mix in SNamedDeclaration with a `name` accessor.
        final declName = (declaration as SNamedDeclaration).name?.name;
        if (declName != null && declName.isNotEmpty) {
          userDeclaredNames!.add(declName);
        }
      }
    }
  }

  // Process the environment values
  for (final entry in environment.values.entries) {
    final name = entry.key;
    final value = entry.value;

    // Filter out anything not declared in the user's source. Two modes:
    //   1. With a compilation unit (typical for `analyze()`), use the AST
    //      whitelist — exhaustive and exact.
    //   2. Without a compilation unit, fall back to the legacy
    //      `_isBuiltinName` blocklist for backward compatibility.
    if (!includeBuiltins) {
      if (userDeclaredNames != null) {
        if (!userDeclaredNames.contains(name)) continue;
      } else if (_isBuiltinName(name)) {
        continue;
      }
    }

    if (value is InterpretedFunction) {
      functions.add(_buildFunctionInfo(name, value));
    } else if (value is InterpretedClass) {
      classes.add(_buildClassInfo(name, value));
    } else if (value is InterpretedEnum) {
      enums.add(_buildEnumInfo(name, value));
    } else if (value is InterpretedExtension) {
      extensions.add(_buildExtensionInfo(name, value));
    } else {
      // It's a variable - use type metadata if available
      final declaredType = variableTypeMap[name];
      variables
          .add(_buildVariableInfo(name, value, declaredType: declaredType));
    }
  }

  return IntrospectionResult(
    functions: functions,
    classes: classes,
    variables: variables,
    enums: enums,
    extensions: extensions,
  );
}