Linter Demo Errors: 4Warnings: 18File: /home/fstrocco/Dart/dart/benchmark/linter/lib/src/linter.dart // 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. library linter.src.linter; import 'dart:io'; import 'package:analyzer/analyzer.dart'; import 'package:analyzer/src/generated/engine.dart'; import 'package:analyzer/src/generated/java_engine.dart'; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/services/lint.dart'; import 'package:glob/glob.dart'; import 'package:linter/src/analysis.dart'; import 'package:linter/src/config.dart'; import 'package:linter/src/io.dart'; import 'package:linter/src/project.dart'; import 'package:linter/src/pub.dart'; import 'package:linter/src/rules.dart'; import 'package:path/path.dart' as p; void _registerLinters(Iterable linters) { if (linters != null) { LintGenerator.LINTERS.clear(); LintGenerator.LINTERS.addAll(linters); } } 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); 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. abstract class DartLinter { /// Creates a new linter. factory DartLinter([LinterOptions options]) => new SourceLinter(options); factory DartLinter.forRules(Iterable ruleSet) => new DartLinter(new LinterOptions(ruleSet)); /// The total number of sources that were analyzed. Only valid after /// [lintFiles] has been called. int get numSourcesAnalyzed; LinterOptions get options; Iterable lintFiles(List files); Iterable lintPubspecSource({String contents}); } class FileGlobFilter extends LintFilter { Iterable includes; Iterable excludes; FileGlobFilter([Iterable includeGlobs, Iterable 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 { /// 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 Pubspec Format', '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 Style Guide', 'https://www.dartlang.org/articles/style-guide/')); /// List of builtin groups in presentation order. static const Iterable 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 => '${_emph(label)}'; String _emph(msg) => bold ? '$msg' : 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]); String toString() => message == null ? "LinterException" : "LinterException: $message"; } /// Linter options. class LinterOptions extends DriverOptions { Iterable enabledLints; final bool enableLints = true; LintFilter filter; LinterOptions([this.enabledLints]) { if (enabledLints == null) { enabledLints = ruleRegistry; } } void configure(LintConfig config) { // TODO(pquitslund): revisit these default-to-on semantics. enabledLints = 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 { /// 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. 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 _locationInfo = []; LintRule( {this.name, this.group, this.description, this.details, this.maturity: Maturity.stable}); @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) { reporter.reportErrorForNode(new _LintCode(name, description), node, []); } 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 var error = new AnalysisError(source, node.span.start.column + 1, node.span.length, new _LintCode(name, description)); _locationInfo.add(new AnalysisErrorInfoImpl([error], new _LineInfo(node))); // Then do the reporting if (reporter != null) { reporter.reportError(error); } } } class Maturity implements Comparable { 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 logError2(String message, Object exception) { _print('ERROR: $message'); } @override void logInformation(String message, [CaughtException exception]) { _print('INFO: $message'); } @override void logInformation2(String message, Object 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 { final errors = []; final LinterOptions options; final Reporter reporter; @override int numSourcesAnalyzed; SourceLinter(this.options, {this.reporter: const PrintingReporter()}); @override Iterable lintFiles(List files) { List errors = []; _registerLinters(options.enabledLints); 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 lintPubspecSource( {String contents, String sourcePath}) { var results = []; 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.isEmpty) { results.addAll(rule._locationInfo); rule._locationInfo.clear(); } } } } return results; } @override onError(AnalysisError error) => errors.add(error); Iterable _lintPubspecFile(File sourceFile) => lintPubspecSource( contents: sourceFile.readAsStringSync(), sourcePath: sourceFile.path); } class _LineInfo implements LineInfo { PSNode node; _LineInfo(this.node); @override LineInfo_Location getLocation(int offset) => new LineInfo_Location( node.span.start.line + 1, node.span.start.column + 1); } class _LintCode extends LintCode { static final registry = {}; factory _LintCode(String name, String message) => registry.putIfAbsent( name + message, () => new _LintCode._(name, message)); _LintCode._(String name, String message) : super(name, message); }