MarkupAnalyzer Lint Rule

Pub Package Pub Likes Pub Score Pub Monthly Downloads Star on Github Forks on Github Contributors Issues Build Status Code size License Platform

MarkupAnalyzer logo

Description

Markup Analyzer is a native Dart analyzer plugin that enforces localization in Flutter widgets. It flags raw string expressions passed to widget constructors, encouraging the use of localized strings instead.

The plugin uses the built-in analysis_server_plugin — no additional tools required.

Installation

Add the plugin to your analysis_options.yaml. No changes to pubspec.yaml required — but the entry must carry a source, otherwise the analyzer parses the block, finds nothing to resolve, and silently loads no plugin at all (no error, just zero diagnostics):

plugins:
  markup_analyzer:
    version: ^4.1.0
    diagnostics:
      simple_string: error
      string_interpolation: error
      adjacent_strings: error
      binary_expression: false
      binary_string_literal: error
      prefixed_identifier: error
      method_invocation: error
      simple_identifier: false
      function_invocation: false

Any of pub's source formats works in place of version:

plugins:
  # From pub.flutter-io.cn.
  markup_analyzer: ^4.1.0

  # From another host.
  markup_analyzer:
    version: ^4.1.0
    hosted: https://my-pub-host.dev

  # From git.
  markup_analyzer:
    git:
      url: https://github.com/AlexHCJP/markup_analyzer.git
      ref: main

  # From a local checkout.
  markup_analyzer:
    path: ../markup_analyzer

The short markup_analyzer: ^4.1.0 form takes no diagnostics: block — use the nested form whenever you want to configure severities.

Verify the plugin is live by running dart analyze from the package root, with no target. Passing a subdirectory (dart analyze lib) makes that directory the analysis context root; with no pubspec.yaml there the plugin cannot be resolved, and the run reports "No issues found" even where the plugin would fire.

Configuration

Each rule is configured independently under plugins: markup_analyzer: diagnostics:.

Set severity to error, warning, or info to enable. Set to false to disable entirely.

Code Description Suggested severity
simple_string Simple string literal error
string_interpolation String interpolation error
adjacent_strings Adjacent string literals error
binary_expression Binary string expression false
binary_string_literal Raw string inside a binary expression error
prefixed_identifier Prefixed String (e.g. widget.title) warning
method_invocation String-returning method call warning
simple_identifier String variable false
function_invocation String-returning function expression false

Diagnostics

Code Description
simple_string Simple string literal passed to a widget
string_interpolation String interpolation passed to a widget
adjacent_strings Adjacent string literals passed to a widget
binary_expression Binary string expression (e.g. 'a' + 'b') passed to a widget, whatever its operands are
binary_string_literal Raw string reached through a binary expression (e.g. 'a' + b, a ?? 'b') passed to a widget
prefixed_identifier Prefixed identifier of type String (e.g. widget.title) passed to a widget
method_invocation Method call returning String (e.g. 'x'.tr()) passed to a widget
simple_identifier Variable of type String passed to a widget
function_invocation Function expression returning String passed to a widget

All checks are widget-scoped: only constructor calls of classes that extend Widget are analyzed.

Examples

Simple string literal

// BAD
Text('Hello, world!');

// GOOD
Text(AppLocalizations.of(context).greeting);

String interpolation

// BAD
Text('Hello, $name!');

// GOOD
Text(AppLocalizations.of(context).helloWithName(name));

Adjacent strings

// BAD
Text(
  'Hello, '
  'world!',
);

Binary expression

binary_expression flags the expression itself, operands unread.

// BAD
Text('Hello, ' + 'world!');
Text(label ?? l10n.fallback);

Binary string literal

binary_string_literal flags the literal instead, and only when there is text in it. Every other rule looks at the argument itself, so a literal one operator deep passes all of them — this is how they reach it.

Enable it and disable binary_expression to require localization without calling ?? a mistake; enable both to forbid the operator outright.

// BAD
Text('Hello, ' + name);
Text(title ?? 'Untitled');
Text(title ?? name ?? 'Untitled');

// GOOD
Text(label ?? l10n.fallback);
Text(name ?? '');

Prefixed identifier

// BAD
Text(widget.title);

Method invocation

// BAD
Text('hello'.tr());

// GOOD
Text(AppLocalizations.of(context).hello);

Simple identifier

// BAD
final String title = 'Hello';
Text(title);

Function invocation

// BAD
Text((() => 'Hello')());

Libraries

main