@@ -32,6 +32,7 @@ class AnalyzerPublicApi extends MultiAnalysisRule {
3232 List <DiagnosticCode > get diagnosticCodes => [
3333 LinterLintCode .analyzerPublicApiBadPartDirective,
3434 LinterLintCode .analyzerPublicApiBadType,
35+ LinterLintCode .analyzerPublicApiExperimentalInconsistency,
3536 LinterLintCode .analyzerPublicApiExportsNonPublicName,
3637 LinterLintCode .analyzerPublicApiImplInPublicApi,
3738 ];
@@ -48,6 +49,16 @@ class AnalyzerPublicApi extends MultiAnalysisRule {
4849 }
4950}
5051
52+ /// Types of problematic uses of types that can be detected by this lint.
53+ enum _ProblematicTypeUseKind {
54+ /// Use of a non-public type by a part of the analyzer public API.
55+ useOfNonPublicType,
56+
57+ /// Use of an experimental type by a non-experimental part of the analyzer
58+ /// public API.
59+ useOfExperimentalType,
60+ }
61+
5162/// Lightweight object representing a public import.
5263class _PublicImport {
5364 final Element ? Function (String name) lookup;
@@ -130,7 +141,7 @@ class _Visitor extends SimpleAstVisitor<void> {
130141 }
131142 }
132143
133- void _checkMember (Fragment fragment) {
144+ void _checkMember (Fragment fragment, { required bool isParentExperimental} ) {
134145 if (fragment is ConstructorFragment &&
135146 fragment.element.enclosingElement is EnumElement ) {
136147 // Enum constructors aren't callable from outside of the enum, so they
@@ -143,7 +154,12 @@ class _Visitor extends SimpleAstVisitor<void> {
143154 return ;
144155 }
145156 if (fragment is ExecutableFragment ) {
146- _checkType (fragment.element.type, fragment: fragment);
157+ _checkType (
158+ fragment.element.type,
159+ fragment: fragment,
160+ isUsageSiteExperimental:
161+ isParentExperimental || fragment.element.metadata.hasExperimental,
162+ );
147163 }
148164 }
149165
@@ -158,95 +174,136 @@ class _Visitor extends SimpleAstVisitor<void> {
158174 diagnosticCode: LinterLintCode .analyzerPublicApiImplInPublicApi,
159175 );
160176 }
177+ var isUsageSiteExperimental = fragment.element.metadata.hasExperimental;
161178 switch (fragment) {
162179 case ExtensionFragment (name: null ):
163180 // Unnamed extensions are not public, so ignore.
164181 break ;
165182 case InstanceFragment (: var typeParameters, : var children):
166183 for (var typeParameter in typeParameters) {
167- _checkTypeParameter (typeParameter, fragment: fragment);
184+ _checkTypeParameter (
185+ typeParameter,
186+ fragment: fragment,
187+ isUsageSiteExperimental: isUsageSiteExperimental,
188+ );
168189 }
169190 if (fragment is InterfaceFragment ) {
170191 var element = fragment.element;
171- _checkType (element.supertype, fragment: fragment);
192+ _checkType (
193+ element.supertype,
194+ fragment: fragment,
195+ isUsageSiteExperimental: isUsageSiteExperimental,
196+ );
172197 for (var t in element.interfaces) {
173- _checkType (t, fragment: fragment);
198+ _checkType (
199+ t,
200+ fragment: fragment,
201+ isUsageSiteExperimental: isUsageSiteExperimental,
202+ );
174203 }
175204 for (var t in element.mixins) {
176- _checkType (t, fragment: fragment);
205+ _checkType (
206+ t,
207+ fragment: fragment,
208+ isUsageSiteExperimental: isUsageSiteExperimental,
209+ );
177210 }
178211 }
179212 if (fragment case ExtensionFragment (: var element)) {
180- _checkType (element.extendedType, fragment: fragment);
213+ _checkType (
214+ element.extendedType,
215+ fragment: fragment,
216+ isUsageSiteExperimental: isUsageSiteExperimental,
217+ );
181218 }
182219 if (fragment case MixinFragment (: var superclassConstraints)) {
183220 for (var t in superclassConstraints) {
184- _checkType (t, fragment: fragment);
221+ _checkType (
222+ t,
223+ fragment: fragment,
224+ isUsageSiteExperimental: isUsageSiteExperimental,
225+ );
185226 }
186227 }
187- children.forEach (_checkMember);
228+ for (var child in children) {
229+ _checkMember (child, isParentExperimental: isUsageSiteExperimental);
230+ }
188231 case ExecutableFragment ():
189- _checkType (fragment.element.type, fragment: fragment);
232+ _checkType (
233+ fragment.element.type,
234+ fragment: fragment,
235+ isUsageSiteExperimental: isUsageSiteExperimental,
236+ );
190237 case TypeAliasFragment (: var element, : var typeParameters):
191238 var aliasedType = element.aliasedType;
192- _checkType (aliasedType, fragment: fragment);
239+ _checkType (
240+ aliasedType,
241+ fragment: fragment,
242+ isUsageSiteExperimental: isUsageSiteExperimental,
243+ );
193244 if (typeParameters.isNotEmpty &&
194245 aliasedType is FunctionType &&
195246 aliasedType.typeParameters.isEmpty) {
196247 // Sometimes `aliasedType` doesn't have the type parameters. Not sure
197248 // why.
198249 // TODO(paulberry): consider fixing this in the analyzer.
199250 for (var typeParameter in typeParameters) {
200- _checkTypeParameter (typeParameter, fragment: fragment);
251+ _checkTypeParameter (
252+ typeParameter,
253+ fragment: fragment,
254+ isUsageSiteExperimental: isUsageSiteExperimental,
255+ );
201256 }
202257 }
203258 }
204259 }
205260
206- void _checkType (DartType ? type, {required Fragment fragment}) {
261+ void _checkType (
262+ DartType ? type, {
263+ required Fragment fragment,
264+ required bool isUsageSiteExperimental,
265+ }) {
207266 if (type == null ) return ;
208- var problems = _problemsForAnalyzerPublicApi (type);
209- if (problems.isEmpty) return ;
210- int offset;
211- int length;
212- while (true ) {
213- if (fragment.nameOffset != null ) {
214- offset = fragment.nameOffset! ;
215- length = fragment.name! .length;
216- break ;
217- } else if (fragment case PropertyAccessorFragment ()
218- when fragment.element.variable.firstFragment.nameOffset != null ) {
219- offset = fragment.element.variable.firstFragment.nameOffset! ;
220- length = fragment.element.variable.name! .length;
221- break ;
222- } else if (fragment is ConstructorFragment &&
223- fragment.typeNameOffset != null ) {
224- offset = fragment.typeNameOffset! ;
225- length = fragment.enclosingFragment! .name! .length;
226- break ;
227- } else if (fragment.enclosingFragment case var enclosingFragment? ) {
228- fragment = enclosingFragment;
229- } else {
230- // This should never happen. But if it does, make sure we generate a
231- // lint anyway.
232- offset = 0 ;
233- length = 1 ;
234- break ;
235- }
236- }
237- rule.reportAtOffset (
238- offset,
239- length,
240- diagnosticCode: LinterLintCode .analyzerPublicApiBadType,
241- arguments: [problems.join (', ' )],
267+ var nonPublicProblems = _problemsForAnalyzerPublicApi (
268+ type,
269+ kind: _ProblematicTypeUseKind .useOfNonPublicType,
242270 );
271+ var experimentalProblems = isUsageSiteExperimental
272+ ? const < String > {}
273+ : _problemsForAnalyzerPublicApi (
274+ type,
275+ kind: _ProblematicTypeUseKind .useOfExperimentalType,
276+ );
277+ late var offsetAndLength = _offsetAndLengthForFragment (fragment);
278+ if (nonPublicProblems.isNotEmpty) {
279+ rule.reportAtOffset (
280+ offsetAndLength.offset,
281+ offsetAndLength.length,
282+ diagnosticCode: LinterLintCode .analyzerPublicApiBadType,
283+ arguments: [nonPublicProblems.join (', ' )],
284+ );
285+ }
286+ if (experimentalProblems.isNotEmpty) {
287+ rule.reportAtOffset (
288+ offsetAndLength.offset,
289+ offsetAndLength.length,
290+ diagnosticCode:
291+ LinterLintCode .analyzerPublicApiExperimentalInconsistency,
292+ arguments: [nonPublicProblems.join (', ' )],
293+ );
294+ }
243295 }
244296
245297 void _checkTypeParameter (
246298 TypeParameterFragment typeParameter, {
247299 required Fragment fragment,
300+ required bool isUsageSiteExperimental,
248301 }) {
249- _checkType (typeParameter.element.bound, fragment: fragment);
302+ _checkType (
303+ typeParameter.element.bound,
304+ fragment: fragment,
305+ isUsageSiteExperimental: isUsageSiteExperimental,
306+ );
250307 }
251308
252309 /// Whether [element] is visible through at least one public import.
@@ -270,20 +327,56 @@ class _Visitor extends SimpleAstVisitor<void> {
270327 return false ;
271328 }
272329
273- Set <String > _problemsForAnalyzerPublicApi (DartType type) {
330+ ({int length, int offset}) _offsetAndLengthForFragment (Fragment fragment) {
331+ while (true ) {
332+ if (fragment.nameOffset != null ) {
333+ return (offset: fragment.nameOffset! , length: fragment.name! .length);
334+ } else if (fragment case PropertyAccessorFragment ()
335+ when fragment.element.variable.firstFragment.nameOffset != null ) {
336+ return (
337+ offset: fragment.element.variable.firstFragment.nameOffset! ,
338+ length: fragment.element.variable.name! .length,
339+ );
340+ } else if (fragment is ConstructorFragment &&
341+ fragment.typeNameOffset != null ) {
342+ return (
343+ offset: fragment.typeNameOffset! ,
344+ length: fragment.enclosingFragment! .name! .length,
345+ );
346+ } else if (fragment.enclosingFragment case var enclosingFragment? ) {
347+ // ignore: parameter_assignments
348+ fragment = enclosingFragment;
349+ } else {
350+ // This should never happen. But if it does, make sure we generate a
351+ // lint anyway.
352+ return (offset: 0 , length: 1 );
353+ }
354+ }
355+ }
356+
357+ Set <String > _problemsForAnalyzerPublicApi (
358+ DartType type, {
359+ required _ProblematicTypeUseKind kind,
360+ }) {
274361 switch (type) {
275362 case RecordType (: var positionalFields, : var namedFields):
276363 return {
277364 for (var f in positionalFields)
278- ..._problemsForAnalyzerPublicApi (f.type),
279- for (var f in namedFields) ..._problemsForAnalyzerPublicApi (f.type),
365+ ..._problemsForAnalyzerPublicApi (f.type, kind: kind),
366+ for (var f in namedFields)
367+ ..._problemsForAnalyzerPublicApi (f.type, kind: kind),
280368 };
281369 case InterfaceType (: var element, : var typeArguments):
370+ var isProblematicElement = switch (kind) {
371+ _ProblematicTypeUseKind .useOfNonPublicType =>
372+ ! _isPubliclyImported (element) && ! element.isOkForAnalyzerPublicApi,
373+ _ProblematicTypeUseKind .useOfExperimentalType =>
374+ element.metadata.hasExperimental,
375+ };
282376 return {
283- if (! _isPubliclyImported (element) &&
284- ! element.isOkForAnalyzerPublicApi)
285- element.name! ,
286- for (var t in typeArguments) ..._problemsForAnalyzerPublicApi (t),
377+ if (isProblematicElement) element.name! ,
378+ for (var t in typeArguments)
379+ ..._problemsForAnalyzerPublicApi (t, kind: kind),
287380 };
288381 case NeverType ():
289382 case DynamicType ():
@@ -297,11 +390,12 @@ class _Visitor extends SimpleAstVisitor<void> {
297390 : var formalParameters,
298391 ):
299392 return {
300- ..._problemsForAnalyzerPublicApi (returnType),
393+ ..._problemsForAnalyzerPublicApi (returnType, kind : kind ),
301394 for (var p in typeParameters)
302- if (p.bound != null ) ..._problemsForAnalyzerPublicApi (p.bound! ),
395+ if (p.bound != null )
396+ ..._problemsForAnalyzerPublicApi (p.bound! , kind: kind),
303397 for (var p in formalParameters)
304- ..._problemsForAnalyzerPublicApi (p.type),
398+ ..._problemsForAnalyzerPublicApi (p.type, kind : kind ),
305399 };
306400 default :
307401 throw StateError ('Unexpected type $runtimeType ' );
0 commit comments