run method

  1. @override
void run(
  1. CustomLintResolver resolver,
  2. DiagnosticReporter reporter,
  3. CustomLintContext context
)

Emits lints for a given file.

run will only be invoked with files respecting filesToAnalyze

Implementation

@override
void run(
  CustomLintResolver resolver,
  DiagnosticReporter reporter,
  CustomLintContext context,
) {
  if (isGeneratedFile(resolver.path)) return;
  final config = RigidConfig.forFile(resolver.path);
  if (!config.isEnabled(code.name)) return;
  if (isTestFile(resolver.path)) return;

  final maxLines = config.maxWidgetLines ?? _defaultMax;
  if (maxLines <= 0) return; // Disabled by user.

  context.registry.addClassDeclaration((node) {
    final extendsClause = node.extendsClause;
    if (extendsClause == null) return;

    final superName = extendsClause.superclass.name.lexeme;
    // Match common widget base classes by name. For type resolution we'd
    // need the full type hierarchy which is expensive; name-matching covers
    // the practical cases agents produce.
    const widgetBases = {
      'StatelessWidget',
      'StatefulWidget',
      'State',
      'ConsumerWidget',
      'ConsumerStatefulWidget',
      'HookWidget',
      'HookConsumerWidget',
    };
    if (!widgetBases.contains(superName)) return;

    final startLine = node.leftBracket.offset;
    final endLine = node.rightBracket.offset;
    final source = resolver.source.contents.data;
    // Count newlines in the class body.
    var lineCount = 0;
    for (var i = startLine; i < endLine && i < source.length; i++) {
      if (source[i] == '\n') lineCount++;
    }

    if (lineCount > maxLines) {
      reporter.atNode(
        node,
        LintCode(
          name: 'rigid_max_widget_lines',
          problemMessage:
              'Widget class has $lineCount lines (max $maxLines). '
              'Decompose into smaller, focused widgets.',
          errorSeverity: DiagnosticSeverity.WARNING,
        ),
      );
    }
  });
}