Skip to content

Commit 51840c5

Browse files
srawlinscommit-bot@chromium.org
authored andcommitted
Analyzer: add two checks for the @internal annotation.
The meta package does not yet have an `internal` constant; this CL just adds one to the mock packages for testing. Two checks are implemented: * Hint if an @internal annotation is found on an element which is already part of a package's public API (based on file path). * Hint if an element annotated with @internal is exported from a package's public API. The notion of "public API" is also implemented for each type of Package: BasicPackage, BazelPackage, GnPackage, PackageBuildPackage, and PubPackage. Bug: #28066 Change-Id: Ifc2709028afcd241f59e802f5952539f717704c3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/163126 Commit-Queue: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]>
1 parent f3acf0c commit 51840c5

File tree

16 files changed

+1124
-3
lines changed

16 files changed

+1124
-3
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,9 @@ abstract class Element implements AnalysisTarget {
534534
/// Return `true` if this element has an annotation of the form `@factory`.
535535
bool get hasFactory;
536536

537+
/// Return `true` if this element has an annotation of the form `@internal`.
538+
bool get hasInternal;
539+
537540
/// Return `true` if this element has an annotation of the form `@isTest`.
538541
bool get hasIsTest;
539542

@@ -717,6 +720,10 @@ abstract class ElementAnnotation implements ConstantEvaluationTarget {
717720
/// subclasses as being immutable.
718721
bool get isImmutable;
719722

723+
/// Return `true` if this annotation marks the associated element as being
724+
/// internal to its package.
725+
bool get isInternal;
726+
720727
/// Return `true` if this annotation marks the associated member as running
721728
/// a single test.
722729
bool get isIsTest;

pkg/analyzer/lib/error/error.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,10 +492,12 @@ const List<ErrorCode> errorCodeValues = [
492492
HintCode.INFERENCE_FAILURE_ON_UNINITIALIZED_VARIABLE,
493493
HintCode.INFERENCE_FAILURE_ON_UNTYPED_PARAMETER,
494494
HintCode.INVALID_ANNOTATION_TARGET,
495+
HintCode.INVALID_EXPORT_OF_INTERNAL_ELEMENT,
495496
HintCode.INVALID_FACTORY_ANNOTATION,
496497
HintCode.INVALID_FACTORY_METHOD_DECL,
497498
HintCode.INVALID_FACTORY_METHOD_IMPL,
498499
HintCode.INVALID_IMMUTABLE_ANNOTATION,
500+
HintCode.INVALID_INTERNAL_ANNOTATION,
499501
HintCode.INVALID_LANGUAGE_VERSION_OVERRIDE_AT_SIGN,
500502
HintCode.INVALID_LANGUAGE_VERSION_OVERRIDE_EQUALS,
501503
HintCode.INVALID_LANGUAGE_VERSION_OVERRIDE_GREATER,

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2313,6 +2313,10 @@ class ElementAnnotationImpl implements ElementAnnotation {
23132313
/// as being immutable.
23142314
static const String _IMMUTABLE_VARIABLE_NAME = "immutable";
23152315

2316+
/// The name of the top-level variable used to mark an element as being
2317+
/// internal to its package.
2318+
static const String _INTERNAL_VARIABLE_NAME = "internal";
2319+
23162320
/// The name of the top-level variable used to mark a constructor as being
23172321
/// literal.
23182322
static const String _LITERAL_VARIABLE_NAME = "literal";
@@ -2459,6 +2463,12 @@ class ElementAnnotationImpl implements ElementAnnotation {
24592463
element.name == _IMMUTABLE_VARIABLE_NAME &&
24602464
element.library?.name == _META_LIB_NAME;
24612465

2466+
@override
2467+
bool get isInternal =>
2468+
element is PropertyAccessorElement &&
2469+
element.name == _INTERNAL_VARIABLE_NAME &&
2470+
element.library?.name == _META_LIB_NAME;
2471+
24622472
@override
24632473
bool get isIsTest =>
24642474
element is PropertyAccessorElement &&
@@ -2747,6 +2757,18 @@ abstract class ElementImpl implements Element {
27472757
return _cachedHashCode ??= location.hashCode;
27482758
}
27492759

2760+
@override
2761+
bool get hasInternal {
2762+
var metadata = this.metadata;
2763+
for (var i = 0; i < metadata.length; i++) {
2764+
var annotation = metadata[i];
2765+
if (annotation.isInternal) {
2766+
return true;
2767+
}
2768+
}
2769+
return false;
2770+
}
2771+
27502772
@override
27512773
bool get hasIsTest {
27522774
var metadata = this.metadata;
@@ -5995,6 +6017,9 @@ class MultiplyDefinedElementImpl implements MultiplyDefinedElement {
59956017
@override
59966018
bool get hasFactory => false;
59976019

6020+
@override
6021+
bool get hasInternal => false;
6022+
59986023
@override
59996024
bool get hasIsTest => false;
60006025

pkg/analyzer/lib/src/dart/element/member.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,9 @@ abstract class Member implements Element {
459459
@override
460460
bool get hasFactory => _declaration.hasFactory;
461461

462+
@override
463+
bool get hasInternal => _declaration.hasInternal;
464+
462465
@override
463466
bool get hasIsTest => _declaration.hasIsTest;
464467

pkg/analyzer/lib/src/dart/error/hint_codes.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,19 @@ class HintCode extends AnalyzerErrorCode {
576576
'INVALID_ANNOTATION_TARGET',
577577
"The annotation '{0}' can only be used on {1}");
578578

579+
/**
580+
* This hint is generated anywhere where an element annotated with `@internal`
581+
* is exported as a part of a package's public API.
582+
*
583+
* Parameters:
584+
* 0: the name of the element
585+
*/
586+
static const HintCode INVALID_EXPORT_OF_INTERNAL_ELEMENT = HintCode(
587+
'INVALID_EXPORT_OF_INTERNAL_ELEMENT',
588+
"The member '{0}' can't be exported as a part of a package's public "
589+
"API.",
590+
correction: "Try using a hide clause to hide '{0}'.");
591+
579592
/**
580593
* This hint is generated anywhere a @factory annotation is associated with
581594
* anything other than a method.
@@ -612,6 +625,15 @@ class HintCode extends AnalyzerErrorCode {
612625
'INVALID_IMMUTABLE_ANNOTATION',
613626
"Only classes can be annotated as being immutable.");
614627

628+
/**
629+
* This hint is generated anywhere a @internal annotation is associated with
630+
* an element found in a package's public API.
631+
*/
632+
static const HintCode INVALID_INTERNAL_ANNOTATION = HintCode(
633+
'INVALID_INTERNAL_ANNOTATION',
634+
"Only public elements in a package's private API can be annotated as "
635+
"being internal.");
636+
615637
/// Invalid Dart language version comments don't follow the specification [1].
616638
/// If a comment begins with "@dart" or "dart" (letters in any case),
617639
/// followed by optional whitespace, followed by optional non-alphanumeric,
@@ -922,7 +944,7 @@ class HintCode extends AnalyzerErrorCode {
922944

923945
/**
924946
* This hint is generated anywhere where a member annotated with `@protected`
925-
* is used outside an instance member of a subclass.
947+
* is used outside of an instance member of a subclass.
926948
*
927949
* Parameters:
928950
* 0: the name of the member

pkg/analyzer/lib/src/error/best_practices_verifier.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'package:analyzer/src/dart/element/type.dart';
2121
import 'package:analyzer/src/dart/element/type_system.dart';
2222
import 'package:analyzer/src/dart/resolver/body_inference_context.dart';
2323
import 'package:analyzer/src/dart/resolver/exit_detector.dart';
24+
import 'package:analyzer/src/dart/resolver/scope.dart';
2425
import 'package:analyzer/src/error/codes.dart';
2526
import 'package:analyzer/src/generated/constant.dart';
2627
import 'package:analyzer/src/generated/engine.dart';
@@ -113,6 +114,11 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
113114
);
114115
}
115116

117+
bool get _inPublicPackageApi {
118+
return _workspacePackage != null &&
119+
_workspacePackage.sourceIsInPublicApi(_currentLibrary.source);
120+
}
121+
116122
@override
117123
void visitAnnotation(Annotation node) {
118124
ElementAnnotation element = node.elementAnnotation;
@@ -132,6 +138,35 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
132138
_errorReporter.reportErrorForNode(
133139
HintCode.INVALID_IMMUTABLE_ANNOTATION, node, []);
134140
}
141+
} else if (element.isInternal) {
142+
var parentElement = parent is Declaration ? parent.declaredElement : null;
143+
if (parent is TopLevelVariableDeclaration) {
144+
for (VariableDeclaration variable in parent.variables.variables) {
145+
if (Identifier.isPrivateName(variable.declaredElement.name)) {
146+
_errorReporter.reportErrorForNode(
147+
HintCode.INVALID_INTERNAL_ANNOTATION, variable, []);
148+
}
149+
}
150+
} else if (parent is FieldDeclaration) {
151+
for (VariableDeclaration variable in parent.fields.variables) {
152+
if (Identifier.isPrivateName(variable.declaredElement.name)) {
153+
_errorReporter.reportErrorForNode(
154+
HintCode.INVALID_INTERNAL_ANNOTATION, variable, []);
155+
}
156+
}
157+
} else if (parent is ConstructorDeclaration) {
158+
var class_ = parent.declaredElement.enclosingElement;
159+
if (class_.isPrivate || (parentElement?.isPrivate ?? false)) {
160+
_errorReporter.reportErrorForNode(
161+
HintCode.INVALID_INTERNAL_ANNOTATION, node, []);
162+
}
163+
} else if (parentElement?.isPrivate ?? false) {
164+
_errorReporter
165+
.reportErrorForNode(HintCode.INVALID_INTERNAL_ANNOTATION, node, []);
166+
} else if (_inPublicPackageApi) {
167+
_errorReporter
168+
.reportErrorForNode(HintCode.INVALID_INTERNAL_ANNOTATION, node, []);
169+
}
135170
} else if (element.isLiteral == true) {
136171
if (parent is! ConstructorDeclaration ||
137172
(parent as ConstructorDeclaration).constKeyword == null) {
@@ -319,6 +354,7 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
319354
@override
320355
void visitExportDirective(ExportDirective node) {
321356
_checkForDeprecatedMemberUse(node.uriElement, node);
357+
_checkForInternalExport(node);
322358
super.visitExportDirective(node);
323359
}
324360

@@ -994,6 +1030,31 @@ class BestPracticesVerifier extends RecursiveAstVisitor<void> {
9941030
}
9951031
}
9961032

1033+
/// Check that the namespace exported by [node] does not include any elements
1034+
/// annotated with `@internal`.
1035+
void _checkForInternalExport(ExportDirective node) {
1036+
if (!_inPublicPackageApi) return;
1037+
1038+
var libraryElement = node.uriElement;
1039+
if (libraryElement == null) return;
1040+
if (libraryElement.hasInternal) {
1041+
_errorReporter.reportErrorForNode(
1042+
HintCode.INVALID_EXPORT_OF_INTERNAL_ELEMENT,
1043+
node,
1044+
[libraryElement.displayName]);
1045+
}
1046+
var exportNamespace =
1047+
NamespaceBuilder().createExportNamespaceForDirective(node.element);
1048+
exportNamespace.definedNames.forEach((String name, Element element) {
1049+
if (element.hasInternal) {
1050+
_errorReporter.reportErrorForNode(
1051+
HintCode.INVALID_EXPORT_OF_INTERNAL_ELEMENT,
1052+
node,
1053+
[element.displayName]);
1054+
}
1055+
});
1056+
}
1057+
9971058
void _checkForInvalidFactory(MethodDeclaration decl) {
9981059
// Check declaration.
9991060
// Note that null return types are expected to be flagged by other analyses.

pkg/analyzer/lib/src/test_utilities/mock_packages.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const _AlwaysThrows alwaysThrows = const _AlwaysThrows();
2828
const _DoNotStore doNotStore = _DoNotStore();
2929
const _Factory factory = const _Factory();
3030
const Immutable immutable = const Immutable();
31+
const _Internal internal = const Internal();
3132
const _Literal literal = const _Literal();
3233
const _MustCallSuper mustCallSuper = const _MustCallSuper();
3334
const _NonVirtual nonVirtual = const _NonVirtual();
@@ -50,6 +51,9 @@ class Immutable {
5051
final String reason;
5152
const Immutable([this.reason]);
5253
}
54+
class _Internal {
55+
const Internal();
56+
}
5357
class _Literal {
5458
const _Literal();
5559
}

pkg/analyzer/lib/src/workspace/basic.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,12 @@ class BasicWorkspacePackage extends WorkspacePackage {
8080
@override
8181
Map<String, List<Folder>> packagesAvailableTo(String libraryPath) =>
8282
workspace.packageMap;
83+
84+
@override
85+
bool sourceIsInPublicApi(Source source) {
86+
// Since every source file in a BasicPackage is in the same directory, they
87+
// are all in the public API of the package. A file in a subdirectory
88+
// is in a separate package.
89+
return true;
90+
}
8391
}

pkg/analyzer/lib/src/workspace/bazel.dart

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,19 @@ class BazelWorkspace extends Workspace
260260
WorkspacePackage findPackageFor(String filePath) {
261261
path.Context context = provider.pathContext;
262262
Folder folder = provider.getFolder(context.dirname(filePath));
263+
if (!context.isWithin(root, folder.path)) {
264+
return null;
265+
}
266+
267+
// Handle files which are given with their location in "bazel-bin", etc.
268+
// This does not typically happen during usual analysis, but it still could,
269+
// and it can come up in tests.
270+
if ([genfiles, ...binPaths]
271+
.any((binPath) => context.isWithin(binPath, folder.path))) {
272+
var relative = context.relative(filePath, from: root);
273+
return findPackageFor(
274+
context.joinAll([root, ...context.split(relative).skip(1)]));
275+
}
263276

264277
while (true) {
265278
Folder parent = folder.parent;
@@ -300,7 +313,8 @@ class BazelWorkspace extends Workspace
300313
// [folder]'s path, relative to [root]. For example, "foo/bar".
301314
String relative = context.relative(folder.path, from: root);
302315
for (String bin in binPaths) {
303-
Folder binChild = provider.getFolder(context.join(bin, relative));
316+
Folder binChild =
317+
provider.getFolder(context.normalize(context.join(bin, relative)));
304318
if (binChild.exists &&
305319
binChild.getChildren().any((c) => c.path.endsWith('.packages'))) {
306320
// [folder]'s sister folder within [bin] contains a ".packages" file.
@@ -548,4 +562,41 @@ class BazelWorkspacePackage extends WorkspacePackage {
548562
// lists.
549563
Map<String, List<Folder>> packagesAvailableTo(String libraryPath) =>
550564
<String, List<Folder>>{};
565+
566+
@override
567+
bool sourceIsInPublicApi(Source source) {
568+
var filePath = filePathFromSource(source);
569+
if (filePath == null) return false;
570+
571+
var libFolder = workspace.provider.pathContext.join(root, 'lib');
572+
if (workspace.provider.pathContext.isWithin(libFolder, filePath)) {
573+
// A file in "$root/lib" is public iff it is not in "$root/lib/src".
574+
var libSrcFolder = workspace.provider.pathContext.join(libFolder, 'src');
575+
return !workspace.provider.pathContext.isWithin(libSrcFolder, filePath);
576+
}
577+
578+
var relativeRoot =
579+
workspace.provider.pathContext.relative(root, from: workspace.root);
580+
for (var binPath in workspace.binPaths) {
581+
libFolder =
582+
workspace.provider.pathContext.join(binPath, relativeRoot, 'lib');
583+
if (workspace.provider.pathContext.isWithin(libFolder, filePath)) {
584+
// A file in "$bin/lib" is public iff it is not in "$bin/lib/src".
585+
var libSrcFolder =
586+
workspace.provider.pathContext.join(libFolder, 'src');
587+
return !workspace.provider.pathContext.isWithin(libSrcFolder, filePath);
588+
}
589+
}
590+
591+
libFolder = workspace.provider.pathContext
592+
.join(workspace.genfiles, relativeRoot, 'lib');
593+
if (workspace.provider.pathContext.isWithin(libFolder, filePath)) {
594+
// A file in "$genfiles/lib" is public iff it is not in
595+
// "$genfiles/lib/src".
596+
var libSrcFolder = workspace.provider.pathContext.join(libFolder, 'src');
597+
return !workspace.provider.pathContext.isWithin(libSrcFolder, filePath);
598+
}
599+
600+
return false;
601+
}
551602
}

pkg/analyzer/lib/src/workspace/gn.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,17 @@ class GnWorkspacePackage extends WorkspacePackage {
237237
@override
238238
Map<String, List<Folder>> packagesAvailableTo(String libraryPath) =>
239239
workspace.packageMap;
240+
241+
@override
242+
bool sourceIsInPublicApi(Source source) {
243+
var filePath = filePathFromSource(source);
244+
if (filePath == null) return false;
245+
var libFolder = workspace.provider.pathContext.join(root, 'lib');
246+
if (workspace.provider.pathContext.isWithin(libFolder, filePath)) {
247+
var libSrcFolder =
248+
workspace.provider.pathContext.join(root, 'lib', 'src');
249+
return !workspace.provider.pathContext.isWithin(libSrcFolder, filePath);
250+
}
251+
return false;
252+
}
240253
}

0 commit comments

Comments
 (0)