Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add linter rule to disallow string literals in Widget classes #261

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/leancode_lint/lib/leancode_lint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:leancode_lint/assists/convert_record_into_nominal_type.dart';
import 'package:leancode_lint/lints/add_cubit_suffix_for_cubits.dart';
import 'package:leancode_lint/lints/avoid_conditional_hooks.dart';
import 'package:leancode_lint/lints/avoid_single_child_in_multi_child_widget.dart';
import 'package:leancode_lint/lints/avoid_string_literals_in_widgets.dart';
import 'package:leancode_lint/lints/catch_parameter_names.dart';
import 'package:leancode_lint/lints/constructor_parameters_and_fields_should_have_the_same_order.dart';
import 'package:leancode_lint/lints/hook_widget_does_not_use_hooks.dart';
Expand All @@ -26,6 +27,7 @@ class _Linter extends PluginBase {
HookWidgetDoesNotUseHooks(),
ConstructorParametersAndFieldsShouldHaveTheSameOrder(),
AvoidSingleChildInMultiChildWidgets(),
AvoidStringLiteralsInWidgets(),
];

@override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/error/listener.dart';
import 'package:custom_lint_builder/custom_lint_builder.dart';
import 'package:leancode_lint/helpers.dart';

/// Displays warning for string literals used inside Widget classes.
class AvoidStringLiteralsInWidgets extends DartLintRule {
AvoidStringLiteralsInWidgets() : super(code: _getLintCode());

static const ruleName = 'avoid_string_literals_in_widgets';

@override
void run(
CustomLintResolver resolver,
ErrorReporter reporter,
CustomLintContext context,
) {
context.registry.addClassDeclaration(
(node) {
final isThisWidgetClass = isWidgetClass(node);
if (!isThisWidgetClass) {
return;
}

final stringLiterals = _getDescendantStringLiterals(node);

for (final stringLiteral in stringLiterals) {
reporter.reportErrorForNode(_getLintCode(), stringLiteral);
}
},
);
}

static List<AstNode> _getDescendantStringLiterals(
AstNode node,
) {
final stringLiterals = <StringLiteral>[];
node.visitChildren(_StringLiteralVisitor(stringLiterals));
return stringLiterals;
}

static LintCode _getLintCode() {
return const LintCode(
name: ruleName,
problemMessage: 'Do not use string literals in widgets.',
correctionMessage: 'Prefer a localized message.',
errorSeverity: ErrorSeverity.WARNING,
);
}
}

class _StringLiteralVisitor extends GeneralizingAstVisitor<void> {
_StringLiteralVisitor(this.acc);

final List<StringLiteral> acc;

@override
void visitStringLiteral(StringLiteral node) {
final insideCatchClause =
node.thisOrAncestorMatching((n) => n is CatchClause) != null;
final insideThrowExpression =
node.thisOrAncestorMatching((n) => n is ThrowExpression) != null;
if (!insideCatchClause && !insideThrowExpression) {
acc.add(node);
}

super.visitStringLiteral(node);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: avoid_string_literals_in_widgets

import 'dart:math';

import 'package:flutter/material.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// ignore_for_file: avoid_print

import 'package:flutter/material.dart';

class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({super.key});

// expect_lint: avoid_string_literals_in_widgets
final aStringVariable = 'This is an hardcoded text';

// expect_lint: avoid_string_literals_in_widgets
final anInterpolatedStringVariable = 'This is a number: ${1}';

@override
Widget build(BuildContext context) {
try {
// expect_lint: avoid_string_literals_in_widgets
print('Executing something...');
_doSomethingBad();
} catch (err) {
print('Something bad happened.');
}

return Column(
children: [
// expect_lint: avoid_string_literals_in_widgets
const WidgetWithText('This is an hardcoded text'),
WidgetWithText(aStringVariable),
],
);
}

void _doSomethingBad() {
throw Exception('Bad stuff happening');
}
}

class WidgetWithText extends StatelessWidget {
const WidgetWithText(this.text, {super.key});

final String text;

@override
Widget build(BuildContext context) {
return Container();
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: avoid_string_literals_in_widgets

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// ignore_for_file: unused_local_variable
// ignore_for_file: avoid_string_literals_in_widgets

import 'package:flutter/material.dart';

Expand Down
Loading