Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(309)

Unified Diff: pkg/analyzer/lib/src/lint/linter.dart

Issue 2559773002: Reapply "Move the linter core to the analyzer package" (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/analyzer/lib/src/lint/io.dart ('k') | pkg/analyzer/lib/src/lint/project.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/analyzer/lib/src/lint/linter.dart
diff --git a/pkg/analyzer/lib/src/lint/linter.dart b/pkg/analyzer/lib/src/lint/linter.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5706f35ea8d26f8d5198b2c6bea474b33e6a6250
--- /dev/null
+++ b/pkg/analyzer/lib/src/lint/linter.dart
@@ -0,0 +1,429 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:analyzer/analyzer.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/src/generated/engine.dart'
+ show AnalysisErrorInfo, AnalysisErrorInfoImpl, Logger;
+import 'package:analyzer/src/generated/java_engine.dart' show CaughtException;
+import 'package:analyzer/src/generated/source.dart' show LineInfo;
+import 'package:analyzer/src/generated/source_io.dart';
+import 'package:analyzer/src/lint/analysis.dart';
+import 'package:analyzer/src/lint/config.dart';
+import 'package:analyzer/src/lint/io.dart';
+import 'package:analyzer/src/lint/project.dart';
+import 'package:analyzer/src/lint/pub.dart';
+import 'package:analyzer/src/lint/registry.dart';
+import 'package:analyzer/src/services/lint.dart' show Linter;
+import 'package:glob/glob.dart';
+import 'package:path/path.dart' as p;
+
+typedef Printer(String msg);
+
+/// Describes a String in valid camel case format.
+class CamelCaseString {
+ static final _camelCaseMatcher = new RegExp(r'[A-Z][a-z]*');
+ static final _camelCaseTester = new RegExp(r'^([_$]*)([A-Z?$]+[a-z0-9]*)+$');
+
+ final String value;
+ CamelCaseString(this.value) {
+ if (!isCamelCase(value)) {
+ throw new ArgumentError('$value is not CamelCase');
+ }
+ }
+
+ String get humanized => _humanize(value);
+
+ @override
+ String toString() => value;
+
+ static bool isCamelCase(String name) => _camelCaseTester.hasMatch(name);
+
+ static String _humanize(String camelCase) =>
+ _camelCaseMatcher.allMatches(camelCase).map((m) => m.group(0)).join(' ');
+}
+
+/// Dart source linter.
+class DartLinter implements AnalysisErrorListener {
+ final errors = <AnalysisError>[];
+
+ final LinterOptions options;
+ final Reporter reporter;
+
+ /// The total number of sources that were analyzed. Only valid after
+ /// [lintFiles] has been called.
+ int numSourcesAnalyzed;
+
+ /// Creates a new linter.
+ DartLinter(this.options, {this.reporter: const PrintingReporter()});
+
+ Iterable<AnalysisErrorInfo> lintFiles(List<File> files) {
+ List<AnalysisErrorInfo> errors = [];
+ var analysisDriver = new AnalysisDriver(options);
+ errors.addAll(analysisDriver.analyze(files.where((f) => isDartFile(f))));
+ numSourcesAnalyzed = analysisDriver.numSourcesAnalyzed;
+ files.where((f) => isPubspecFile(f)).forEach((p) {
+ numSourcesAnalyzed++;
+ return errors.addAll(_lintPubspecFile(p));
+ });
+ return errors;
+ }
+
+ Iterable<AnalysisErrorInfo> lintPubspecSource(
+ {String contents, String sourcePath}) {
+ var results = <AnalysisErrorInfo>[];
+
+ Uri sourceUrl = sourcePath == null ? null : p.toUri(sourcePath);
+
+ var spec = new Pubspec.parse(contents, sourceUrl: sourceUrl);
+
+ for (Linter lint in options.enabledLints) {
+ if (lint is LintRule) {
+ LintRule rule = lint;
+ var visitor = rule.getPubspecVisitor();
+ if (visitor != null) {
+ // Analyzer sets reporters; if this file is not being analyzed,
+ // we need to set one ourselves. (Needless to say, when pubspec
+ // processing gets pushed down, this hack can go away.)
+ if (rule.reporter == null && sourceUrl != null) {
+ var source = createSource(sourceUrl);
+ rule.reporter = new ErrorReporter(this, source);
+ }
+ try {
+ spec.accept(visitor);
+ } on Exception catch (e) {
+ reporter.exception(new LinterException(e.toString()));
+ }
+ if (rule._locationInfo != null && rule._locationInfo.isNotEmpty) {
+ results.addAll(rule._locationInfo);
+ rule._locationInfo.clear();
+ }
+ }
+ }
+ }
+
+ return results;
+ }
+
+ @override
+ onError(AnalysisError error) => errors.add(error);
+
+ Iterable<AnalysisErrorInfo> _lintPubspecFile(File sourceFile) =>
+ lintPubspecSource(
+ contents: sourceFile.readAsStringSync(), sourcePath: sourceFile.path);
+}
+
+class FileGlobFilter extends LintFilter {
+ Iterable<Glob> includes;
+ Iterable<Glob> excludes;
+
+ FileGlobFilter([Iterable<String> includeGlobs, Iterable<String> excludeGlobs])
+ : includes = includeGlobs.map((glob) => new Glob(glob)),
+ excludes = excludeGlobs.map((glob) => new Glob(glob));
+
+ @override
+ bool filter(AnalysisError lint) {
+ // TODO specify order
+ return excludes.any((glob) => glob.matches(lint.source.fullName)) &&
+ !includes.any((glob) => glob.matches(lint.source.fullName));
+ }
+}
+
+class Group implements Comparable<Group> {
+ /// Defined rule groups.
+ static const Group errors =
+ const Group._('errors', description: 'Possible coding errors.');
+ static const Group pub = const Group._('pub',
+ description: 'Pub-related rules.',
+ link: const Hyperlink('See the <strong>Pubspec Format</strong>',
+ 'https://www.dartlang.org/tools/pub/pubspec.html'));
+ static const Group style = const Group._('style',
+ description:
+ 'Matters of style, largely derived from the official Dart Style Guide.',
+ link: const Hyperlink('See the <strong>Style Guide</strong>',
+ 'https://www.dartlang.org/articles/style-guide/'));
+
+ /// List of builtin groups in presentation order.
+ static const Iterable<Group> builtin = const [errors, style, pub];
+
+ final String name;
+ final bool custom;
+ final String description;
+ final Hyperlink link;
+
+ factory Group(String name, {String description: '', Hyperlink link}) {
+ var n = name.toLowerCase();
+ return builtin.firstWhere((g) => g.name == n,
+ orElse: () => new Group._(name,
+ custom: true, description: description, link: link));
+ }
+
+ const Group._(this.name, {this.custom: false, this.description, this.link});
+
+ @override
+ int compareTo(Group other) => name.compareTo(other.name);
+}
+
+class Hyperlink {
+ final String label;
+ final String href;
+ final bool bold;
+ const Hyperlink(this.label, this.href, {this.bold: false});
+ String get html => '<a href="$href">${_emph(label)}</a>';
+ String _emph(msg) => bold ? '<strong>$msg</strong>' : msg;
+}
+
+/// Thrown when an error occurs in linting.
+class LinterException implements Exception {
+ /// A message describing the error.
+ final String message;
+
+ /// Creates a new LinterException with an optional error [message].
+ const LinterException([this.message]);
+
+ @override
+ String toString() =>
+ message == null ? "LinterException" : "LinterException: $message";
+}
+
+/// Linter options.
+class LinterOptions extends DriverOptions {
+ Iterable<LintRule> enabledLints;
+ LintFilter filter;
+ LinterOptions([this.enabledLints]) {
+ enabledLints ??= Registry.ruleRegistry;
+ }
+ void configure(LintConfig config) {
+ // TODO(pquitslund): revisit these default-to-on semantics.
+ enabledLints = Registry.ruleRegistry.where((LintRule rule) =>
+ !config.ruleConfigs.any((rc) => rc.disables(rule.name)));
+ filter = new FileGlobFilter(config.fileIncludes, config.fileExcludes);
+ }
+}
+
+/// Filtered lints are ommitted from linter output.
+abstract class LintFilter {
+ bool filter(AnalysisError lint);
+}
+
+/// Describes a lint rule.
+abstract class LintRule extends Linter implements Comparable<LintRule> {
+ /// Description (in markdown format) suitable for display in a detailed lint
+ /// description.
+ final String details;
+
+ /// Short description suitable for display in console output.
+ final String description;
+
+ /// Lint group (for example, 'style').
+ final Group group;
+
+ /// Lint maturity (stable|experimental).
+ final Maturity maturity;
+
+ /// Lint name.
+ @override
+ final String name;
+
+ /// Until pubspec analysis is pushed into the analyzer proper, we need to
+ /// do some extra book-keeping to keep track of details that will help us
+ /// constitute AnalysisErrorInfos.
+ final List<AnalysisErrorInfo> _locationInfo = <AnalysisErrorInfo>[];
+
+ LintRule(
+ {this.name,
+ this.group,
+ this.description,
+ this.details,
+ this.maturity: Maturity.stable});
+
+ LintCode get lintCode => new _LintCode(name, description);
+
+ @override
+ int compareTo(LintRule other) {
+ var g = group.compareTo(other.group);
+ if (g != 0) {
+ return g;
+ }
+ return name.compareTo(other.name);
+ }
+
+ /// Return a visitor to be passed to provide access to Dart project context
+ /// and to perform project-level analyses.
+ ProjectVisitor getProjectVisitor() => null;
+
+ /// Return a visitor to be passed to pubspecs to perform lint
+ /// analysis.
+ /// Lint errors are reported via this [Linter]'s error [reporter].
+ PubspecVisitor getPubspecVisitor() => null;
+
+ @override
+ AstVisitor getVisitor() => null;
+
+ void reportLint(AstNode node, {bool ignoreSyntheticNodes: true}) {
+ if (node != null && (!node.isSynthetic || !ignoreSyntheticNodes)) {
+ reporter.reportErrorForNode(lintCode, node, []);
+ }
+ }
+
+ void reportLintForToken(Token token, {bool ignoreSyntheticTokens: true}) {
+ if (token != null && (!token.isSynthetic || !ignoreSyntheticTokens)) {
+ reporter.reportErrorForToken(lintCode, token, []);
+ }
+ }
+
+ void reportPubLint(PSNode node) {
+ Source source = createSource(node.span.sourceUrl);
+
+ // Cache error and location info for creating AnalysisErrorInfos
+ // Note that error columns are 1-based
+ AnalysisError error = new AnalysisError(
+ source, node.span.start.column + 1, node.span.length, lintCode);
+ LineInfo lineInfo = new LineInfo.fromContent(source.contents.data);
+
+ _locationInfo.add(new AnalysisErrorInfoImpl([error], lineInfo));
+
+ // Then do the reporting
+ reporter?.reportError(error);
+ }
+}
+
+class Maturity implements Comparable<Maturity> {
+ static const Maturity stable = const Maturity._('stable', ordinal: 0);
+ static const Maturity experimental = const Maturity._('stable', ordinal: 1);
+
+ final String name;
+ final int ordinal;
+
+ factory Maturity(String name, {int ordinal}) {
+ switch (name.toLowerCase()) {
+ case 'stable':
+ return stable;
+ case 'experimental':
+ return experimental;
+ default:
+ return new Maturity._(name, ordinal: ordinal);
+ }
+ }
+
+ const Maturity._(this.name, {this.ordinal});
+
+ @override
+ int compareTo(Maturity other) => this.ordinal - other.ordinal;
+}
+
+class PrintingReporter implements Reporter, Logger {
+ final Printer _print;
+
+ const PrintingReporter([this._print = print]);
+
+ @override
+ void exception(LinterException exception) {
+ _print('EXCEPTION: $exception');
+ }
+
+ @override
+ void logError(String message, [CaughtException exception]) {
+ _print('ERROR: $message');
+ }
+
+ @override
+ void logInformation(String message, [CaughtException exception]) {
+ _print('INFO: $message');
+ }
+
+ @override
+ void warn(String message) {
+ _print('WARN: $message');
+ }
+}
+
+abstract class Reporter {
+ void exception(LinterException exception);
+ void warn(String message);
+}
+
+/// Linter implementation.
+class SourceLinter implements DartLinter, AnalysisErrorListener {
+ @override
+ final errors = <AnalysisError>[];
+ @override
+ final LinterOptions options;
+ @override
+ final Reporter reporter;
+
+ @override
+ int numSourcesAnalyzed;
+
+ SourceLinter(this.options, {this.reporter: const PrintingReporter()});
+
+ @override
+ Iterable<AnalysisErrorInfo> lintFiles(List<File> files) {
+ List<AnalysisErrorInfo> errors = [];
+ var analysisDriver = new AnalysisDriver(options);
+ errors.addAll(analysisDriver.analyze(files.where((f) => isDartFile(f))));
+ numSourcesAnalyzed = analysisDriver.numSourcesAnalyzed;
+ files.where((f) => isPubspecFile(f)).forEach((p) {
+ numSourcesAnalyzed++;
+ return errors.addAll(_lintPubspecFile(p));
+ });
+ return errors;
+ }
+
+ @override
+ Iterable<AnalysisErrorInfo> lintPubspecSource(
+ {String contents, String sourcePath}) {
+ var results = <AnalysisErrorInfo>[];
+
+ Uri sourceUrl = sourcePath == null ? null : p.toUri(sourcePath);
+
+ var spec = new Pubspec.parse(contents, sourceUrl: sourceUrl);
+
+ for (Linter lint in options.enabledLints) {
+ if (lint is LintRule) {
+ LintRule rule = lint;
+ var visitor = rule.getPubspecVisitor();
+ if (visitor != null) {
+ // Analyzer sets reporters; if this file is not being analyzed,
+ // we need to set one ourselves. (Needless to say, when pubspec
+ // processing gets pushed down, this hack can go away.)
+ if (rule.reporter == null && sourceUrl != null) {
+ var source = createSource(sourceUrl);
+ rule.reporter = new ErrorReporter(this, source);
+ }
+ try {
+ spec.accept(visitor);
+ } on Exception catch (e) {
+ reporter.exception(new LinterException(e.toString()));
+ }
+ if (rule._locationInfo != null && rule._locationInfo.isNotEmpty) {
+ results.addAll(rule._locationInfo);
+ rule._locationInfo.clear();
+ }
+ }
+ }
+ }
+
+ return results;
+ }
+
+ @override
+ onError(AnalysisError error) => errors.add(error);
+
+ @override
+ Iterable<AnalysisErrorInfo> _lintPubspecFile(File sourceFile) =>
+ lintPubspecSource(
+ contents: sourceFile.readAsStringSync(), sourcePath: sourceFile.path);
+}
+
+class _LintCode extends LintCode {
+ static final registry = <String, LintCode>{};
+
+ factory _LintCode(String name, String message) => registry.putIfAbsent(
+ name + message, () => new _LintCode._(name, message));
+
+ _LintCode._(String name, String message) : super(name, message);
+}
« no previous file with comments | « pkg/analyzer/lib/src/lint/io.dart ('k') | pkg/analyzer/lib/src/lint/project.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698