hardcoded_strings_lint 2.0.0
hardcoded_strings_lint: ^2.0.0 copied to clipboard
A custom Flutter lint rule that identifies and prevents hardcoded strings in widget constructors, promoting better internationalization practices and code maintainability.
Hardcoded Strings Lint #
A Flutter analyzer plugin that detects hardcoded strings in widget constructors, encouraging better internationalization and code maintainability.
Overview #
hardcoded_strings_lint is built on top of Dart's official analysis_server_plugin system, so its diagnostics surface natively in dart analyze and inside the Dart Analysis Server (VS Code, IntelliJ, etc.) without any extra commands.
Features #
Smart Detection #
- Flags hardcoded strings passed directly to Flutter widget constructor arguments.
- Skips strings inside callback bodies (e.g.
onTap: () { logger.info('...'); }). - Walks the inheritance chain so custom
Widgetsubclasses are detected.
Intelligent Filtering #
- Technical patterns skipped: URLs, emails, hex colors, snake_case, CONSTANT_CASE, dotted notation, file paths.
- Short strings skipped: empty and ≤ 2 characters.
- Map keys skipped:
map['k']and{'k': value}. - Acceptable widget properties skipped:
semanticsLabel,restorationId,heroTag,key,debugLabel,tooltip,fontFamily,package,asset,textDirection,textAlign, and others.
Quick Fixes #
- Add ignore comment — inserts the prefixed
// ignore: hardcoded_strings_lint/avoid_hardcoded_strings_in_widgetsabove the offending line. - Extract to variable — extracts the literal to a
constlocal (inside a method) or astatic constfield (inside a class), naming the variable from the string contents (e.g.'Hello, World!'→helloWorldText).
Installation #
1. Configure analysis_options.yaml #
Add the plugin under the top-level plugins: block (not under analyzer:):
plugins:
hardcoded_strings_lint: ^2.0.0
The plugin's diagnostic is enabled by default. To explicitly disable or re-enable it use the nested diagnostics: map:
plugins:
hardcoded_strings_lint:
version: ^2.0.0
diagnostics:
avoid_hardcoded_strings_in_widgets: true
2. Resolve and analyze #
dart pub get
dart analyze
That's it — no separate dart run … command, no dev_dependencies entry, no analyzer.plugins block.
Restart the analyzer in your IDE after first installing the plugin or changing the
plugins:section.
Usage #
class WelcomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Welcome to Our App'), // ⚠️ flagged
),
body: Column(
children: [
Text('Hello, World!'), // ⚠️ flagged
ElevatedButton(
onPressed: () {},
child: Text('Get Started'), // ⚠️ flagged
),
],
),
);
}
}
class WelcomeScreen extends StatelessWidget {
static const _welcomeTitle = 'Welcome to Our App';
static const _helloText = 'Hello, World!';
static const _getStartedText = 'Get Started';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_welcomeTitle), // ✅
backgroundColor: Colors.blue, // ✅ technical value
),
body: Column(
children: [
Text(_helloText), // ✅
ElevatedButton(
onPressed: () {},
child: Text(_getStartedText), // ✅
),
Image.asset('assets/logo.png'), // ✅ asset path
],
),
);
}
}
Ignoring warnings #
The plugin uses the analyzer's native ignore-comment system. The hardcoded_strings_lint/ prefix is required — bespoke shorthands like // hardcoded.ok are no longer recognized.
Per line #
// ignore: hardcoded_strings_lint/avoid_hardcoded_strings_in_widgets
Text('This is acceptable hardcoded text'),
Per file #
// ignore_for_file: hardcoded_strings_lint/avoid_hardcoded_strings_in_widgets
class DebugScreen extends StatelessWidget { /* ... */ }
Smart filtering rules #
Technical patterns automatically skipped #
- URLs:
https://example.com,file://path - Email addresses:
user@example.com - Hex colors:
#FF5722,#ffffff - File paths:
/assets/images/logo.png - snake_case / CONSTANT_CASE identifiers
- Dotted notation:
package.asset
Acceptable widget properties #
Text('hi', semanticsLabel: 'A long accessibility label'), // ✅
Scaffold(restorationId: 'home_scaffold'), // ✅
Full allowlist: semanticsLabel, excludeSemantics, restorationId, heroTag, key, debugLabel, fontFamily, package, name, asset, tooltip, textDirection, locale, materialType, clipBehavior, crossAxisAlignment, mainAxisAlignment, textAlign, textBaseline, overflow, softWrap, textScaleFactor.
Quick fixes #
Add ignore comment #
// Before:
Text('Hello World')
// After:
// ignore: hardcoded_strings_lint/avoid_hardcoded_strings_in_widgets
Text('Hello World')
Extract to variable #
// Before (inside a build method):
Text('Welcome to our application')
// After:
@override
Widget build(BuildContext context) {
const welcomeToOurText = 'Welcome to our application';
return Text(welcomeToOurText);
}
// Before (inside a class, outside a method):
class MyWidget extends StatelessWidget {
final Widget header = Text('Welcome to our application');
}
// After:
class MyWidget extends StatelessWidget {
static const welcomeToOurText = 'Welcome to our application';
final Widget header = Text(welcomeToOurText);
}
Migrating from 1.x #
Version 2.0.0 is a clean break. The package is now built on Dart's official analysis_server_plugin system instead of the deprecated custom_lint_builder ecosystem.
Why the break: custom_lint is no longer actively developed. The Dart team's analysis_server_plugin is the official replacement, ships with Dart 3.10 (Flutter 3.38) and later, and removes the need for a separate dart run custom_lint step.
pubspec.yaml #
environment:
- sdk: ^3.6.0
+ sdk: ^3.11.0 # Flutter 3.41+
dev_dependencies:
- custom_lint: ^0.8.1
- analyzer: ^8.1.1
- hardcoded_strings_lint: ^1.0.4
The plugin is no longer a dev_dependency — it's loaded entirely through analysis_options.yaml.
analysis_options.yaml #
-analyzer:
- plugins:
- - custom_lint
+plugins:
+ hardcoded_strings_lint: ^2.0.0
Note the plugins: block is now a top-level key, not nested under analyzer:.
Ignore comments #
The ignore syntax now requires the hardcoded_strings_lint/ prefix:
-// ignore: avoid_hardcoded_strings_in_widgets
-// ignore: hardcoded.string
-// hardcoded.ok
+// ignore: hardcoded_strings_lint/avoid_hardcoded_strings_in_widgets
The bespoke // hardcoded.ok and // ignore: hardcoded.string shorthands are removed.
No more dart run custom_lint #
dart analyze and the Dart Analysis Server pick up the plugin automatically. There's nothing else to run.
Troubleshooting #
Rule not running #
- Confirm
hardcoded_strings_lintis listed underplugins:inanalysis_options.yaml. - Restart the Dart Analysis Server / your IDE — plugin changes only take effect after a restart.
- Run
dart pub get. - Run
dart analyzeto surface diagnostics from the command line.
Ignore comments not taking effect #
This can happen in workspaces with multiple analysis_options.yaml files (see dart-lang/sdk#62173). The fix is tracked upstream; for now, hoist the plugins: block to a single root analysis options file.
False positives #
- Verify the string really matches a technical pattern listed above. If not, add an ignore comment or rename the value to fit a pattern (e.g.
snake_case). - Consider whether the widget argument should be added to the acceptable allowlist; PRs welcome.
Best practices #
// Recommended: localization
Text(AppLocalizations.of(context).welcomeMessage)
// Or grouped constants
class AppStrings {
static const appTitle = 'My App';
static const welcomeMessage = 'Welcome';
static const loginButton = 'Login';
}
// Use ignore comments sparingly — debug/dev only
// ignore: hardcoded_strings_lint/avoid_hardcoded_strings_in_widgets
Text('DEBUG: state=$state')
License #
This package is released under the MIT License. See LICENSE for details.
Changelog #
See CHANGELOG.md for version history.