| // Copyright (c) 2019, 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' show File; | 
 | import 'dart:typed_data' show Uint8List; | 
 |  | 
 | import 'package:_fe_analyzer_shared/src/parser/experimental_features.dart' | 
 |     show DefaultExperimentalFeatures; | 
 | import 'package:_fe_analyzer_shared/src/parser/listener.dart' show Listener; | 
 | import 'package:_fe_analyzer_shared/src/parser/parser.dart' | 
 |     show FormalParameterKind, MemberKind, Parser; | 
 | import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart' | 
 |     show ScannerConfiguration; | 
 | import 'package:_fe_analyzer_shared/src/scanner/token.dart' show Token; | 
 | import 'package:_fe_analyzer_shared/src/scanner/utf8_bytes_scanner.dart' | 
 |     show Utf8BytesScanner; | 
 | import 'package:front_end/src/base/command_line_reporting.dart' | 
 |     as command_line_reporting; | 
 | import 'package:front_end/src/source/diet_parser.dart' | 
 |     show useImplicitCreationExpressionInCfe; | 
 | import 'package:kernel/kernel.dart'; | 
 | import 'package:package_config/package_config.dart'; | 
 | import 'package:testing/testing.dart' | 
 |     show Chain, ChainContext, Result, Step, TestDescription; | 
 |  | 
 | import 'utils/suite_utils.dart'; | 
 | import 'testing_utils.dart' show checkEnvironment, filterList; | 
 |  | 
 | void main([List<String> arguments = const []]) => internalMain( | 
 |   createContext, | 
 |   arguments: arguments, | 
 |   displayName: "lint suite", | 
 |   configurationPath: "../testing.json", | 
 | ); | 
 |  | 
 | Future<Context> createContext(Chain suite, Map<String, String> environment) { | 
 |   const Set<String> knownEnvironmentKeys = {"onlyInGit"}; | 
 |   checkEnvironment(environment, knownEnvironmentKeys); | 
 |  | 
 |   bool onlyInGit = environment["onlyInGit"] != "false"; | 
 |   return new Future.value(new Context(onlyInGit: onlyInGit)); | 
 | } | 
 |  | 
 | class LintTestDescription extends TestDescription { | 
 |   @override | 
 |   final String shortName; | 
 |   @override | 
 |   final Uri uri; | 
 |   final LintTestCache cache; | 
 |   final LintListener listener; | 
 |  | 
 |   LintTestDescription(this.shortName, this.uri, this.cache, this.listener) { | 
 |     this.listener.description = this; | 
 |     this.listener.uri = uri; | 
 |   } | 
 |  | 
 |   String getErrorMessage(int offset, int squigglyLength, String message) { | 
 |     cache.source ??= new Source(cache.lineStarts, cache.rawBytes!, uri, uri); | 
 |     Location location = cache.source!.getLocation(uri, offset); | 
 |     return command_line_reporting.formatErrorMessage( | 
 |       cache.source!.getTextLine(location.line), | 
 |       location, | 
 |       squigglyLength, | 
 |       uri.toString(), | 
 |       message, | 
 |     ); | 
 |   } | 
 | } | 
 |  | 
 | class LintTestCache { | 
 |   Uint8List? rawBytes; | 
 |   late List<int> lineStarts; | 
 |   Source? source; | 
 |   Token? firstToken; | 
 |   PackageConfig? packages; | 
 | } | 
 |  | 
 | class Context extends ChainContext { | 
 |   final bool onlyInGit; | 
 |   Context({required this.onlyInGit}); | 
 |  | 
 |   @override | 
 |   final List<Step> steps = const <Step>[const LintStep()]; | 
 |  | 
 |   @override | 
 |   Future<List<LintTestDescription>> list(Chain suite) async { | 
 |     String rootString = "${suite.root}"; | 
 |     Uri apiUnstableUri = Uri.base.resolve( | 
 |       "pkg/front_end/lib/src/api_unstable/", | 
 |     ); | 
 |     String apiUnstableString = apiUnstableUri.toString(); | 
 |  | 
 |     List<LintTestDescription> result = []; | 
 |     for (TestDescription description in await filterList( | 
 |       suite, | 
 |       onlyInGit, | 
 |       await super.list(suite), | 
 |     )) { | 
 |       String baseName = "${description.uri}".substring(rootString.length); | 
 |       baseName = baseName.substring(0, baseName.length - ".dart".length); | 
 |       LintTestCache cache = new LintTestCache(); | 
 |  | 
 |       result.add( | 
 |         new LintTestDescription( | 
 |           "$baseName/ExplicitType", | 
 |           description.uri, | 
 |           cache, | 
 |           new ExplicitTypeLintListener(), | 
 |         ), | 
 |       ); | 
 |  | 
 |       result.add( | 
 |         new LintTestDescription( | 
 |           "$baseName/ImportsTwice", | 
 |           description.uri, | 
 |           cache, | 
 |           new ImportsTwiceLintListener(), | 
 |         ), | 
 |       ); | 
 |  | 
 |       if (!description.uri.toString().startsWith(apiUnstableString)) { | 
 |         result.add( | 
 |           new LintTestDescription( | 
 |             "$baseName/Exports", | 
 |             description.uri, | 
 |             cache, | 
 |             new ExportsLintListener(), | 
 |           ), | 
 |         ); | 
 |       } | 
 |     } | 
 |     return result; | 
 |   } | 
 | } | 
 |  | 
 | class LintStep extends Step<LintTestDescription, LintTestDescription, Context> { | 
 |   const LintStep(); | 
 |  | 
 |   @override | 
 |   String get name => "lint"; | 
 |  | 
 |   @override | 
 |   Future<Result<LintTestDescription>> run( | 
 |     LintTestDescription description, | 
 |     Context context, | 
 |   ) async { | 
 |     if (description.cache.rawBytes == null) { | 
 |       File f = new File.fromUri(description.uri); | 
 |       Uint8List bytes = description.cache.rawBytes = f.readAsBytesSync(); | 
 |       Utf8BytesScanner scanner = new Utf8BytesScanner( | 
 |         bytes, | 
 |         configuration: const ScannerConfiguration(enableTripleShift: true), | 
 |         includeComments: true, | 
 |         languageVersionChanged: (scanner, languageVersion) { | 
 |           // Nothing - but don't overwrite the previous settings. | 
 |         }, | 
 |       ); | 
 |       description.cache.firstToken = scanner.tokenize(); | 
 |       description.cache.lineStarts = scanner.lineStarts; | 
 |  | 
 |       Uri packageConfig = description.uri.resolve( | 
 |         ".dart_tool/package_config.json", | 
 |       ); | 
 |       while (true) { | 
 |         if (new File.fromUri(packageConfig).existsSync()) { | 
 |           break; | 
 |         } | 
 |         // Stupid bailout. | 
 |         if (packageConfig.pathSegments.length < Uri.base.pathSegments.length) { | 
 |           break; | 
 |         } | 
 |         packageConfig = packageConfig.resolve( | 
 |           "../../.dart_tool/package_config.json", | 
 |         ); | 
 |       } | 
 |  | 
 |       File packageConfigUri = new File.fromUri(packageConfig); | 
 |       if (packageConfigUri.existsSync()) { | 
 |         description.cache.packages = await loadPackageConfigUri(packageConfig); | 
 |       } | 
 |     } | 
 |  | 
 |     if (description.cache.firstToken == null) { | 
 |       return crash(description, StackTrace.current); | 
 |     } | 
 |  | 
 |     Parser parser = new Parser( | 
 |       description.listener, | 
 |       useImplicitCreationExpression: useImplicitCreationExpressionInCfe, | 
 |       experimentalFeatures: const DefaultExperimentalFeatures(), | 
 |     ); | 
 |     parser.parseUnit(description.cache.firstToken!); | 
 |  | 
 |     if (description.listener.problems.isEmpty) { | 
 |       return pass(description); | 
 |     } | 
 |     return fail(description, description.listener.problems.join("\n\n")); | 
 |   } | 
 | } | 
 |  | 
 | class LintListener extends Listener { | 
 |   List<String> problems = <String>[]; | 
 |   late final LintTestDescription description; | 
 |   late final Uri uri; | 
 |  | 
 |   void onProblem(int offset, int squigglyLength, String message) { | 
 |     problems.add(description.getErrorMessage(offset, squigglyLength, message)); | 
 |   } | 
 | } | 
 |  | 
 | class ExplicitTypeLintListener extends LintListener { | 
 |   List<LatestType> _latestTypes = <LatestType>[]; | 
 |  | 
 |   @override | 
 |   void beginVariablesDeclaration( | 
 |     Token token, | 
 |     Token? lateToken, | 
 |     Token? varFinalOrConst, | 
 |   ) { | 
 |     if (!_latestTypes.last.type) { | 
 |       onProblem( | 
 |         varFinalOrConst!.offset, | 
 |         varFinalOrConst.length, | 
 |         "No explicit type.", | 
 |       ); | 
 |     } | 
 |   } | 
 |  | 
 |   @override | 
 |   void handleType(Token beginToken, Token? questionMark) { | 
 |     _latestTypes.add(new LatestType(beginToken, true)); | 
 |   } | 
 |  | 
 |   @override | 
 |   void handleNoType(Token lastConsumed) { | 
 |     _latestTypes.add(new LatestType(lastConsumed, false)); | 
 |   } | 
 |  | 
 |   @override | 
 |   void endFunctionType(Token functionToken, Token? questionMark) { | 
 |     _latestTypes.add(new LatestType(functionToken, true)); | 
 |   } | 
 |  | 
 |   @override | 
 |   void endTopLevelFields( | 
 |     Token? augmentToken, | 
 |     Token? externalToken, | 
 |     Token? staticToken, | 
 |     Token? covariantToken, | 
 |     Token? lateToken, | 
 |     Token? varFinalOrConst, | 
 |     int count, | 
 |     Token beginToken, | 
 |     Token endToken, | 
 |   ) { | 
 |     if (!_latestTypes.last.type) { | 
 |       onProblem(beginToken.offset, beginToken.length, "No explicit type."); | 
 |     } | 
 |     _latestTypes.removeLast(); | 
 |   } | 
 |  | 
 |   @override | 
 |   void endClassFields( | 
 |     Token? abstractToken, | 
 |     Token? augmentToken, | 
 |     Token? externalToken, | 
 |     Token? staticToken, | 
 |     Token? covariantToken, | 
 |     Token? lateToken, | 
 |     Token? varFinalOrConst, | 
 |     int count, | 
 |     Token beginToken, | 
 |     Token endToken, | 
 |   ) { | 
 |     if (!_latestTypes.last.type) { | 
 |       onProblem( | 
 |         varFinalOrConst!.offset, | 
 |         varFinalOrConst.length, | 
 |         "No explicit type.", | 
 |       ); | 
 |     } | 
 |     _latestTypes.removeLast(); | 
 |   } | 
 |  | 
 |   @override | 
 |   void endFormalParameter( | 
 |     Token? varOrFinal, | 
 |     Token? thisKeyword, | 
 |     Token? superKeyword, | 
 |     Token? periodAfterThisOrSuper, | 
 |     Token nameToken, | 
 |     Token? initializerStart, | 
 |     Token? initializerEnd, | 
 |     FormalParameterKind kind, | 
 |     MemberKind memberKind, | 
 |   ) { | 
 |     _latestTypes.removeLast(); | 
 |   } | 
 | } | 
 |  | 
 | class LatestType { | 
 |   final Token token; | 
 |   bool type; | 
 |  | 
 |   LatestType(this.token, this.type); | 
 | } | 
 |  | 
 | class ImportsTwiceLintListener extends LintListener { | 
 |   Map<Uri, Set<String?>> seenImports = {}; | 
 |  | 
 |   Token? seenAsKeyword; | 
 |  | 
 |   @override | 
 |   void handleImportPrefix(Token? deferredKeyword, Token? asKeyword) { | 
 |     seenAsKeyword = asKeyword; | 
 |   } | 
 |  | 
 |   @override | 
 |   void endImport(Token importKeyword, Token? augmentToken, Token? semicolon) { | 
 |     Token importUriToken = importKeyword.next!; | 
 |     String importUri = importUriToken.lexeme; | 
 |     if (importUri.startsWith("r")) { | 
 |       importUri = importUri.substring(2, importUri.length - 1); | 
 |     } else { | 
 |       importUri = importUri.substring(1, importUri.length - 1); | 
 |     } | 
 |     Uri resolved = uri.resolve(importUri); | 
 |     if (resolved.isScheme("package")) { | 
 |       if (description.cache.packages != null) { | 
 |         resolved = description.cache.packages!.resolve(resolved)!; | 
 |       } | 
 |     } | 
 |     String? asName = seenAsKeyword?.lexeme; | 
 |     Set<String?> asNames = seenImports[resolved] ??= {}; | 
 |     if (!asNames.add(asName)) { | 
 |       if (asName != null) { | 
 |         onProblem( | 
 |           importUriToken.offset, | 
 |           importUriToken.lexeme.length, | 
 |           "Uri '$resolved' already imported once as '${asName}'.", | 
 |         ); | 
 |       } else { | 
 |         onProblem( | 
 |           importUriToken.offset, | 
 |           importUriToken.lexeme.length, | 
 |           "Uri '$resolved' already imported once.", | 
 |         ); | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | class ExportsLintListener extends LintListener { | 
 |   @override | 
 |   void endExport(Token exportKeyword, Token semicolon) { | 
 |     Token exportUriToken = exportKeyword.next!; | 
 |     String exportUri = exportUriToken.lexeme; | 
 |     if (exportUri.startsWith("r")) { | 
 |       exportUri = exportUri.substring(2, exportUri.length - 1); | 
 |     } else { | 
 |       exportUri = exportUri.substring(1, exportUri.length - 1); | 
 |     } | 
 |     Uri resolved = uri.resolve(exportUri); | 
 |  | 
 |     if (resolved.isScheme("package") && resolved.path.startsWith('kernel/')) { | 
 |       // Exporting from `package:kernel` is allowed. | 
 |       return; | 
 |     } | 
 |  | 
 |     // Allowlist specific exports. | 
 |     if (resolved.isScheme('package') && | 
 |         ['_fe_analyzer_shared/src/macros/uri.dart'].contains(resolved.path)) { | 
 |       return; | 
 |     } | 
 |  | 
 |     if (resolved.isScheme("package")) { | 
 |       if (description.cache.packages != null) { | 
 |         resolved = description.cache.packages!.resolve(resolved)!; | 
 |       } | 
 |     } | 
 |     onProblem( | 
 |       exportUriToken.offset, | 
 |       exportUriToken.lexeme.length, | 
 |       "Exports disallowed internally.", | 
 |     ); | 
 |   } | 
 | } |