Skip to content

Commit d3de420

Browse files
stereotype441Commit Queue
authored and
Commit Queue
committed
Add a custom lint to sanity check the analyzer public API.
The new lint, called `analyzer_public_api`, verifies that the analyzer public API satisfies the following properties: - No method, function, getter, setter, or supertype in the public API refers to a non-public type. - No `export` declaration in the public API shows a non-public name. - No declaration in the public API has a name ending in `Impl`. - No file in the public API has a `part` declaration that points to a file that's not in the public API. (If it did, then the other checks could be circumvented.) A new annotation is added, `@AnalyzerPublicApi()`, allowing declarations in `package:analyzer/src` or `package:_fe_analyzer_shared/src` to be marked as part of the analyzer public API. This is necessary because some parts of the analyzer public API need to be declared elsewhere and then exported by the analyzer. A few lint violations have been ignored using `ignore:` comments. I will try to clean these up in follow-up CLs. Fixes #60058. Bug: #60058 Change-Id: I0047a73dec8a29e2ffe03dd3a90f7e41ca2e27b6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/409763 Reviewed-by: Brian Wilkerson <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 4c63263 commit d3de420

28 files changed

+3109
-1
lines changed

pkg/_fe_analyzer_shared/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ include: analysis_options_no_lints.yaml
66

77
linter:
88
rules:
9+
- analyzer_public_api
910
- annotate_overrides
1011
- collection_methods_unrelated_type
1112
- curly_braces_in_flow_control_structures
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// Annotation for top level elements within a library that are part of the
6+
/// analyzer's public API, even though they might not appear to be (e.g., due to
7+
/// being implemented inside a `src` directory but re-exported in `lib`, or due
8+
/// to being a supertype of a public type).
9+
///
10+
/// This annotation is intended to be used inside the `src` subdirectories of
11+
/// the `_fe_analyzer_shared` and `analyzer` packages.
12+
///
13+
/// Applying this annotation to an element lets developers know that
14+
/// modifications to the element should be carefully reviewed for backward
15+
/// compatibility, and to make sure they don't unduly expose private
16+
/// implementation details.
17+
///
18+
/// The `analyzer_public_api` lint rules use this annotation to tell:
19+
/// - Which elements are safe to export in the analyzer's `lib` directory
20+
/// - Which classes and mixins are safe to use as supertypes of a public type
21+
class AnalyzerPublicApi {
22+
/// Explanation of why this element is part of the public API, in spite of not
23+
/// being in the analyzer's `lib` folder.
24+
final String message;
25+
26+
const AnalyzerPublicApi({required this.message});
27+
}

pkg/_fe_analyzer_shared/lib/src/base/errors.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44

55
import 'dart:math';
66

7+
import 'package:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
8+
79
import 'customized_codes.dart';
810

911
/// An error code associated with an `AnalysisError`.
1012
///
1113
/// Generally, messages should follow the [Guide for Writing
1214
/// Diagnostics](https://github.com/dart-lang/sdk/blob/main/pkg/front_end/lib/src/base/diagnostics.md).
15+
@AnalyzerPublicApi(message: 'exported by package:analyzer/error/error.dart')
1316
abstract class ErrorCode {
1417
/// Regular expression for identifying positional arguments in error messages.
1518
static final RegExp _positionalArgumentRegExp = new RegExp(r'{(\d+)\}');
@@ -117,6 +120,7 @@ abstract class ErrorCode {
117120
/**
118121
* The severity of an [ErrorCode].
119122
*/
123+
@AnalyzerPublicApi(message: 'exported by package:analyzer/error/error.dart')
120124
class ErrorSeverity implements Comparable<ErrorSeverity> {
121125
/**
122126
* The severity representing a non-error. This is never used for any error
@@ -189,6 +193,7 @@ class ErrorSeverity implements Comparable<ErrorSeverity> {
189193
/**
190194
* The type of an [ErrorCode].
191195
*/
196+
@AnalyzerPublicApi(message: 'exported by package:analyzer/error/error.dart')
192197
class ErrorType implements Comparable<ErrorType> {
193198
/**
194199
* Task (todo) comments in user code.

pkg/_fe_analyzer_shared/lib/src/base/syntactic_entity.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
6+
57
/**
68
* Interface representing a syntactic entity (either a token or an AST node)
79
* which has a location and extent in the source file.
810
*/
11+
@AnalyzerPublicApi(
12+
message: 'exported by package:analyzer/dart/ast/syntactic_entity.dart')
913
abstract class SyntacticEntity {
1014
/**
1115
* Return the offset from the beginning of the file to the character after the

pkg/_fe_analyzer_shared/lib/src/scanner/token.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
6+
57
/**
68
* Defines the tokens that are produced by the scanner, used by the parser, and
79
* referenced from the [AST structure](ast.dart).
@@ -68,6 +70,7 @@ class BeginToken extends SimpleToken {
6870
/**
6971
* A token representing a comment.
7072
*/
73+
@AnalyzerPublicApi(message: 'exported by package:analyzer/dart/ast/token.dart')
7174
class CommentToken extends StringToken {
7275
/**
7376
* The token that contains this comment.
@@ -92,6 +95,7 @@ class DocumentationCommentToken extends CommentToken {
9295
DocumentationCommentToken(super.type, super.value, super.offset);
9396
}
9497

98+
@AnalyzerPublicApi(message: 'exposed by Keyword.keywordStyle')
9599
enum KeywordStyle {
96100
reserved,
97101
builtIn,
@@ -103,6 +107,7 @@ enum KeywordStyle {
103107
*
104108
* Clients may not extend, implement or mix-in this class.
105109
*/
110+
@AnalyzerPublicApi(message: 'exported by package:analyzer/dart/ast/token.dart')
106111
class Keyword extends TokenType {
107112
static const Keyword ABSTRACT = const Keyword(
108113
/* index = */ 82, "abstract", "ABSTRACT", KeywordStyle.builtIn,
@@ -501,6 +506,7 @@ class KeywordToken extends SimpleToken {
501506
* A specialized comment token representing a language version
502507
* (e.g. '// @dart = 2.1').
503508
*/
509+
@AnalyzerPublicApi(message: 'exported by package:analyzer/dart/ast/token.dart')
504510
class LanguageVersionToken extends CommentToken {
505511
/**
506512
* The major language version.
@@ -520,6 +526,8 @@ class LanguageVersionToken extends CommentToken {
520526
* A token that was scanned from the input. Each token knows which tokens
521527
* precede and follow it, acting as a link in a doubly linked list of tokens.
522528
*/
529+
@AnalyzerPublicApi(
530+
message: 'exposed by CommentToken.parent and StringToken (superclass)')
523531
class SimpleToken implements Token {
524532
/**
525533
* The type of the token.
@@ -708,6 +716,7 @@ class SimpleToken implements Token {
708716
/**
709717
* A token whose value is independent of it's type.
710718
*/
719+
@AnalyzerPublicApi(message: 'exposed by CommentToken (superclass)')
711720
class StringToken extends SimpleToken {
712721
/**
713722
* The lexeme represented by this token.
@@ -836,6 +845,7 @@ class ReplacementToken extends SyntheticToken {
836845
*
837846
* Clients may not extend, implement or mix-in this class.
838847
*/
848+
@AnalyzerPublicApi(message: 'exported by package:analyzer/dart/ast/token.dart')
839849
abstract class Token implements SyntacticEntity {
840850
/**
841851
* Initialize a newly created token to have the given [type] and [offset].
@@ -1226,6 +1236,7 @@ class TokenClass {
12261236
*
12271237
* Clients may not extend, implement or mix-in this class.
12281238
*/
1239+
@AnalyzerPublicApi(message: 'exported by package:analyzer/dart/ast/token.dart')
12291240
class TokenType {
12301241
static const TokenType UNUSED = const TokenType(
12311242
/* index = */ 255, '', 'UNUSED', NO_PRECEDENCE, EOF_TOKEN);

pkg/_fe_analyzer_shared/lib/src/type_inference/nullability_suffix.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
6+
57
/// Suffix indicating the nullability of a type.
68
///
79
/// This enum describes whether a `?` or `*` would be used at the end of the
@@ -12,6 +14,9 @@
1214
///
1315
/// This enum is exposed through the analyzer API, so it should not be modified
1416
/// without considering the impact on analyzer clients.
17+
@AnalyzerPublicApi(
18+
message:
19+
'exported by package:analyzer/dart/element/nullability_suffix.dart')
1520
enum NullabilitySuffix {
1621
/// An indication that the canonical representation of the type under
1722
/// consideration ends with `?`. Types having this nullability suffix should

pkg/analysis_server/lib/src/services/correction/error_fix_status.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,6 +1853,14 @@ LintCode.always_specify_types_split_to_types:
18531853
status: hasFix
18541854
LintCode.always_use_package_imports:
18551855
status: hasFix
1856+
LintCode.analyzer_public_api_bad_part_directive:
1857+
status: noFix
1858+
LintCode.analyzer_public_api_bad_type:
1859+
status: noFix
1860+
LintCode.analyzer_public_api_exports_non_public_name:
1861+
status: noFix
1862+
LintCode.analyzer_public_api_impl_in_public_api:
1863+
status: noFix
18561864
LintCode.analyzer_use_new_elements:
18571865
status: noFix
18581866
LintCode.annotate_overrides:

pkg/analyzer/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ analyzer:
4646
linter:
4747
rules:
4848
- always_use_package_imports
49+
- analyzer_public_api
4950
- analyzer_use_new_elements
5051
- avoid_dynamic_calls
5152
- avoid_redundant_argument_values

pkg/analyzer/lib/dart/analysis/analysis_options.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ abstract class AnalysisOptions {
4444

4545
/// A list of the lint rules that are to be run in an analysis context if
4646
/// [lint] is `true`.
47+
// ignore: analyzer_public_api_bad_type
4748
List<LintRule> get lintRules;
4849

4950
/// The plugin configurations for each plugin which is configured in analysis
@@ -123,11 +124,13 @@ final class PluginConfiguration {
123124
final PluginSource source;
124125

125126
/// The list of specified [DiagnosticConfig]s.
127+
// ignore: analyzer_public_api_bad_type
126128
final Map<String, DiagnosticConfig> diagnosticConfigs;
127129

128130
/// Whether the plugin is enabled.
129131
final bool isEnabled;
130132

133+
// ignore: analyzer_public_api_bad_type
131134
PluginConfiguration({
132135
required this.name,
133136
required this.source,

pkg/analyzer/lib/dart/analysis/context_root.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ abstract class ContextRoot {
4545
Folder get root;
4646

4747
/// Return the workspace that contains this context root.
48+
// ignore: analyzer_public_api_bad_type
4849
Workspace get workspace;
4950

5051
/// Return the absolute, normalized paths of all of the files that are

pkg/analyzer/lib/dart/element/element.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
/// represented by an element.
3939
library;
4040

41+
import 'package:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
4142
import 'package:analyzer/dart/analysis/features.dart';
4243
import 'package:analyzer/dart/analysis/session.dart';
4344
import 'package:analyzer/dart/constant/value.dart';
@@ -2637,6 +2638,8 @@ abstract class VariableElement implements Element, ConstantEvaluationTarget {
26372638

26382639
/// This class exists to provide non-nullable overrides for existing elements,
26392640
/// as opposite to artificial "multiply defined" element.
2641+
@AnalyzerPublicApi(
2642+
message: 'Exposed because it is implemented by various elements')
26402643
abstract class _ExistingElement implements Element {
26412644
@override
26422645
Element get declaration;

pkg/analyzer/lib/src/dart/analysis/experiments_impl.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'package:_fe_analyzer_shared/src/base/analyzer_public_api.dart';
56
import 'package:analyzer/dart/analysis/features.dart';
67
import 'package:analyzer/src/dart/analysis/experiments.dart';
78
import 'package:meta/meta.dart';
@@ -287,6 +288,7 @@ class EnabledDisabledFlags {
287288

288289
/// Information about a single experimental flag that the user might use to
289290
/// request that a feature be enabled (or disabled).
291+
@AnalyzerPublicApi(message: 'Exposed by static fields in the Feature class.')
290292
class ExperimentalFeature implements Feature {
291293
/// Index of the flag in the private data structure maintained by
292294
/// [ExperimentStatus].

0 commit comments

Comments
 (0)