Skip to content

Commit d974c79

Browse files
authored
[Mono.Android] Enumify API-36.1, API-36.1 is "Stable" (#10515)
Context: http://github.com/jpobst/BindingStudio Context: #10005 Use jpobst/BindingStudio to begin enumifying API-36.1. Note: current dotnet/java-interop emits an "extra" `,` on `map.csv` output, which would make for a "noisy" diff (every line changed!). The diff size is reduced by removing trailing commas: sed 's/,$//' < src/Mono.Android/new-map.csv > src/Mono.Android/map.csv This keeps the diff to a reasonable size. TODO: update `map.csv` to current BindingStudio output after this is merged. Update `build-tools/manifest-attribute-codegen` so that: 1. It can be executed from topdir. 2. Warning message are actually emitted when using `dotnet build`. Previously, only the fact that the executed command failed would be printed, but none of the tool output, which was needed! ~~ Stable API-36.1 ~~ Normally when we declare a new API level as "stable", it is the *only* API that is built. For API-36.1, this is not the case: we want to continue building API-36, *and also* build API-36.1, *and also* using the the API-36.1 build artifacts doesn't require `$(EnablePreviewFeatures)`=true. Mirror #10005: * build both API levels by "repurposing" the `$(AndroidLatestUnstable*)` MSBuild properties as an "alternate set" of stable API levels. * What "really" indicates a `Mono.Android.dll` is unstable is the presence of `ANDROID_UNSTABLE` when building it. This is conditional on `$(IsUnstableVersion)`. Update `$(IsUnstableVersion)` so that it uses the [MSBuild version-comparison functions][0] to compare `$(AndroidApiLevel)` against `$(AndroidLatestStableApiLevel)` (previous behavior) *and also* against `$(AndroidLatestStableApiLevel2)` (new property). This allows API-36.1 to be built via the `*Unstable*` properties while not defining `ANDROID_UNSTABLE.` ~~ Fix assertion failure in <CheckApiCompatibility/> ~~ When attempting to declare API-36.1 as the latest stable API, my local (Debug) build promptly started *crashing*. Which was rather unexpected. The most useful lesson in this context: *do not* call `Debug.Assert()` or throw exceptions from code which is called from the Thread Pool. `Debug.Assert()` -- in Debug config builds -- will immediately exit the process, which in turn means that MSBuild `binlog` output is *incomplete*, as MSBuild never gets a chance to flush everything (courtesy of the process aborting!). Throwing an exception is very similar: unhandled exceptions on the thread pool cause the process to abort. MSBuild never has a chance to write complete binlog data. Plumb logging through to `CodeGenDiff` so that the assertion failure can instead become an "Internal Error" message. This fixes the crash. Then there's the matter of the former assertion failure now error message: why was `currentObject.InternalCounter` negative? `currentObject.InternalCounter` was negative because of this line (newlines added for readability, but it's all one line): public static void AddEventHandler<TInterface, TImplementor>( [System.Runtime.CompilerServices.NullableAttribute((byte)2)] ref System.WeakReference? implementor, System.Func<TImplementor> creator, System.Action<TInterface> setListener, System.Action<TImplementor> add) where TInterface : class where TImplementor : Java.Lang.Object, TInterface { } The `where TInterface : class ` was matching this check: content.IndexOf (" class ", StringComparison.Ordinal) and thus the method was being interpreted as the introduction of a new type declaration instead of something to add to the existing `currentObject` scope. This meant that the new `currentObject.InternalCounter` was 0, then we hit `}` to decrement `.InternalCounter`, resulting in a negative value. Fix this by checking for ` partial class `, as was done previously with ` partial struct `, as `Microsoft.DotNet.GenAPI.dll` always emits the `partial` modifier on everything except `enum`s. This fixes the now internal error message former assertion. [0]: https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions?iew=vs-2022#msbuild-version-comparison-functions
1 parent ae89f16 commit d974c79

File tree

17 files changed

+536
-80
lines changed

17 files changed

+536
-80
lines changed

Configuration.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828
<AndroidLatestStableApiLevel Condition="'$(AndroidLatestStableApiLevel)' == ''">36</AndroidLatestStableApiLevel>
2929
<AndroidLatestStablePlatformId Condition="'$(AndroidLatestStablePlatformId)' == ''">$(AndroidLatestStableApiLevel)</AndroidLatestStablePlatformId>
3030
<AndroidLatestStableFrameworkVersion Condition="'$(AndroidLatestStableFrameworkVersion)'==''">v16.0</AndroidLatestStableFrameworkVersion>
31+
<AndroidLatestStableApiLevel2 Condition="'$(AndroidLatestStableApiLevel2)' == ''">36.1</AndroidLatestStableApiLevel2>
3132
<!-- *Latest* *unstable* API level binding that we support; this can be the same as *stable* -->
3233
<AndroidLatestUnstableApiLevel Condition="'$(AndroidLatestUnstableApiLevel)' == ''">36.1</AndroidLatestUnstableApiLevel>
33-
<AndroidLatestUnstablePlatformId Condition="'$(AndroidLatestUnstablePlatformId)' == ''">36.1</AndroidLatestUnstablePlatformId>
34+
<AndroidLatestUnstablePlatformId Condition="'$(AndroidLatestUnstablePlatformId)' == ''">$(AndroidLatestUnstableApiLevel)</AndroidLatestUnstablePlatformId>
3435
<AndroidLatestUnstableFrameworkVersion Condition="'$(AndroidLatestUnstableFrameworkVersion)'==''">v16.1</AndroidLatestUnstableFrameworkVersion>
3536
<!-- The default API level used for $(TargetPlatformVersion) -->
3637
<AndroidDefaultTargetDotnetApiLevel Condition=" '$(AndroidDefaultTargetDotnetApiLevel)' == '' ">$(AndroidLatestStableApiLevel)</AndroidDefaultTargetDotnetApiLevel>

Documentation/workflow/HowToAddNewApiLevel.md

Lines changed: 197 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The first unstable preview generally ships in late February or early March. At
66
stage for the APIs, we simply add literal bindings for them. We do not spend resources on
77
the more manual parts like enumification that will likely change as the APIs mature.
88

9+
<a name="repository-xml"></a>
10+
911
### Review `repository2-3.xml`
1012

1113
<https://dl.google.com/android/repository/repository2-3.xml> is an XML description of the Android SDK,
@@ -256,6 +258,104 @@ It's a Winforms app, so it only runs on Windows. It's ugly as sin, and has very
256258
it prompts you with the exact decisions you need to make, and handles as much dirty work as possible,
257259
allowing enumification to be done in a few days.
258260

261+
### Source of Inspiration for enum grouping
262+
263+
*If the Android sources package has been published* -- which is not always a given, and thus why
264+
historically this could not be relied upon for automated enumification -- then the Android Source
265+
package can be used as a "source of inspiration". The Android Source package is listed in the
266+
<a href="#repository-xml">repository XML</a> in a "source" remote package:
267+
268+
```xml
269+
<sdk:sdk-repository …>
270+
<remotePackage path="sources;android-36.1">
271+
<type-details xsi:type="sdk:sourceDetailsType">
272+
<api-level>36.1</api-level>
273+
<extension-level>20</extension-level>
274+
<base-extension>true</base-extension>
275+
</type-details>
276+
<revision>
277+
<major>1</major>
278+
</revision>
279+
<display-name>Sources for Android 36.1</display-name>
280+
<uses-license ref="android-sdk-license"/>
281+
<channelRef ref="channel-0"/>
282+
<archives>
283+
<archive>
284+
<complete>
285+
<size>51810808</size>
286+
<checksum type="sha1">54cea0371ec284404e06051cd389646d34470da4</checksum>
287+
<url>source-36.1_r01.zip</url>
288+
</complete>
289+
</archive>
290+
</archives>
291+
</remotePackage>
292+
</sdk:sdk-repository>
293+
```
294+
295+
As with other Repository packages, append the `//archive/complete/url` value to
296+
`https://dl.google.com/android/repository/`, creating e.g.
297+
<https://dl.google.com/android/repository/source-36.1_r01.zip>. The contents of
298+
this archive is *Java source code* for the public Android API.
299+
300+
Within the Java source code is usage of *source-only annotations* which Java IDEs
301+
can use to associate Java constants with method parameters and return types.
302+
For example, from `src/android/view/View.java`:
303+
304+
```java
305+
public /* partial */ class View {
306+
307+
@FlaggedApi(FLAG_REQUEST_RECTANGLE_WITH_SOURCE)
308+
public boolean requestRectangleOnScreen(@NonNull Rect rectangle, boolean immediate,
309+
@RectangleOnScreenRequestSource int source) {
310+
311+
}
312+
313+
/**
314+
* @hide
315+
*/
316+
@IntDef(prefix = { "RECTANGLE_ON_SCREEN_REQUEST_SOURCE_" }, value = {
317+
RECTANGLE_ON_SCREEN_REQUEST_SOURCE_UNDEFINED,
318+
RECTANGLE_ON_SCREEN_REQUEST_SOURCE_SCROLL_ONLY,
319+
RECTANGLE_ON_SCREEN_REQUEST_SOURCE_TEXT_CURSOR,
320+
RECTANGLE_ON_SCREEN_REQUEST_SOURCE_INPUT_FOCUS,
321+
})
322+
@Retention(RetentionPolicy.SOURCE)
323+
public @interface RectangleOnScreenRequestSource {}
324+
325+
@FlaggedApi(FLAG_REQUEST_RECTANGLE_WITH_SOURCE)
326+
public static final int RECTANGLE_ON_SCREEN_REQUEST_SOURCE_UNDEFINED = 0x00000000;
327+
328+
@FlaggedApi(FLAG_REQUEST_RECTANGLE_WITH_SOURCE)
329+
public static final int RECTANGLE_ON_SCREEN_REQUEST_SOURCE_SCROLL_ONLY = 0x00000001;
330+
331+
@FlaggedApi(FLAG_REQUEST_RECTANGLE_WITH_SOURCE)
332+
public static final int RECTANGLE_ON_SCREEN_REQUEST_SOURCE_TEXT_CURSOR = 0x00000002;
333+
334+
@FlaggedApi(FLAG_REQUEST_RECTANGLE_WITH_SOURCE)
335+
public static final int RECTANGLE_ON_SCREEN_REQUEST_SOURCE_INPUT_FOCUS = 0x00000003;
336+
}
337+
```
338+
339+
The `public @interface RectangleOnScreenRequestSource` introduces a *Java annotation*.
340+
The `@RectangleOnScreenRequestSource` annotation itself has
341+
[`@Retention(RetentionPolicy.SOURCE)`](https://developer.android.com/reference/kotlin/java/lang/annotation/Retention.html),
342+
which indicates that the annotation will only be present in *source code*, not `.class` files.
343+
The `@RectangleOnScreenRequestSource` annotation also has an
344+
[`@IntDef` annotation](https://developer.android.com/reference/androidx/annotation/IntDef), which is
345+
used to specify which constants are grouped together.
346+
347+
* `IntDef.prefix` is an optional string listing a common prefix for all the grouped constants.
348+
* `IntDef.flag` is an optional boolean value specifying whether the grouped constants
349+
are usable in a bitwise context, akin to `[System.Flags]`.
350+
* `IntDef.value` is an array of the grouped constants.
351+
352+
The `@RectangleOnScreenRequestSource` annotation can then be used on Java method parameters
353+
and return types to indicate which parameters and return types should be enumified.
354+
355+
Something to consider for the future: *if* Google reliably provides source packages,
356+
these annotations could be used to auto-generate enumified APIs. For now, this information
357+
can be used as a source of inspiration for enum names and what should be in the enums.
358+
259359
### Extract constants from API
260360
261361
Using BindingStudio:
@@ -327,12 +427,15 @@ The left tree view can be updated by saving and reopening the `map.csv` file.
327427
328428
Using BindingStudio:
329429
330-
- Update the file paths in `MainForm.FindAPILevelMethodsToolStripMenuItem_Click`
430+
- Update the file paths in `MainForm.FindAPILevelMethodsToolStripMenuItem_Click`.
431+
In particular, `api` is the path to the `api-*.xml` file to parse, and
432+
`csv` is the name of a CSV file containing the contents described below.
331433
- Run BindingStudio and choose `Tools` -> `Find API Level Methods`
332434
333-
This will create a file of every method in the new API level that takes an `int` as a parameter
334-
or returns an `int` as a return value. Each method will be marked with a `?` in the file
335-
to indicate a decision needs to be made to ignore it or map it to an enum.
435+
The `csv` variable within `MainForm.FindAPILevelMethodsToolStripMenuItem_Click()` is the name
436+
of the created file which contains every method in the new API level that takes an `int` as a
437+
parameter or returns an `int` as a return value. Each method will be marked with a `?` in the
438+
file to indicate a decision needs to be made to ignore it or map it to an enum.
336439
337440
Example:
338441
```
@@ -347,7 +450,7 @@ Using BindingStudio:
347450
- Choose `File` -> `Open Constant Map`
348451
- Choose existing `map.csv`: `xamarin-android/src/Mono.Android/map.csv`
349452
- Choose `File` -> `Open Method Map`
350-
- Choose the new `.csv` created in the previous step
453+
- Choose the new `.csv` created in the previous step.
351454
352455
The left tree will populate with every method that possibly should be enumified and
353456
needs a decision to be made. Clicking a method shows the Android documentation for
@@ -358,45 +461,87 @@ Note a method may show up multiple times, once for each parameter or return type
358461
359462
There are 3 possible options for a method parameter/return type:
360463
361-
1) Unknown
464+
1. Unknown
362465
363-
You don't how to handle this method currently, so leaving it in the initial state
364-
of "Unknown" will leave it alone until a decision can be made.
466+
You don't how to handle this method currently, so leaving it in the initial
467+
state of "Unknown" will leave it alone until a decision can be made.
365468
366-
2) Ignore
469+
2. Ignore
367470
368-
The method parameter/return type should remain an `int` and not be converted to an enum.
471+
The method parameter/return type should remain an `int` and not be converted to an enum.
369472
370-
Ex:
371-
```
372-
int Add (int value1, int value2) { ... }
373-
```
473+
For example:
474+
475+
```java
476+
int Add (int value1, int value2) { ... }
477+
```
478+
479+
Click the **Ignore** radio button and then the **Save** button.
480+
481+
3. Enumify
374482
375-
Click the "Ignore" radio button and then the "Save" button.
483+
The method parameter/return type should be changed to an enum.
484+
485+
For example:
486+
487+
```java
488+
void AudioAttributesBuilder.SetSpatializationBehavior (int sb) {… }
489+
```
490+
491+
- Choose the **Enumify** radio option
492+
- Use the DropDown in the bottom-left to select the enum to use
493+
- When selected, the members of that enum will be shown in the box below the enum
494+
- Alternatively, search for a enum by enum member name using the Search box in the right
495+
- If desired enum is found, clicking it will populate dropdown
496+
- Click **Save**
497+
498+
Use `File` -> `Save` to save your work often!
376499
377-
3) Enumify
500+
### Turning `int` into `Color`
378501
379-
The method parameter/return type should be changed to an enum.
502+
As part of the above **Mapping methods** process, some methods may appear which should
503+
be an `Android.Graphics.Color`, e.g. methods named `getTextColor()`.
380504
381-
Ex:
505+
[`src/Mono.Android/metadata`](../../src/Mono.Android/metadata) is used to transform `int`
506+
parameter and return types into `Android.Graphics.Color`. This is a manual process.
507+
508+
To map the return type:
509+
510+
```xml
511+
<attr
512+
api-since="36.1"
513+
path="/api/package[@name='…package name…']/class[@name='…class name…]/method[@name='…method name…']"
514+
name="return"
515+
>Android.Graphics.Color</attr>
382516
```
383-
void AudioAttributesBuilder.SetSpatializationBehavior (int sb) { ... }
517+
518+
Specifically:
519+
520+
* `//attr/@path` is the XPath expression to the method name to update
521+
* `//attr/@name` the XML attribute to update. For return types, this is `return`.
522+
* The value of the `<attr/>` is `Android.Graphics.Color`.
523+
524+
To map parameter types:
525+
526+
```xml
527+
<attr
528+
api-since="36.1"
529+
path="/api/package[@name='…package name…']/class[@name='…class name…]/method[@name='…method name…']/parameter[@type='int']"
530+
name="type"
531+
>Android.Graphics.Color</attr>
384532
```
385533
386-
- Choose the "Enumify" radio option
387-
- Use the DropDown in the middle to select the enum to use
388-
- When selected, the members of that enum will be shown in the box below the enum
389-
- Alternatively, search for a enum by enum member name using the Search box in the right
390-
- If desired enum is found, clicking it will populate dropdown
391-
- Click "Save"
534+
* `//attr/@path` is the XPath expression to the parameter to update
535+
* `//attr/@name` the XML attribute to update. For return types, this is `type`.
536+
* The value of the `<attr/>` is `Android.Graphics.Color`.
392537
393-
Use `File` -> `Save` to save your work often!
394538
395539
### Finishing the method map
396540
397541
The official `methodmap.csv` uses a slightly different format than the one used for enumification.
398542
399543
Using BindingStudio:
544+
400545
- Ensure the "new api level method map" CSV file is loaded.
401546
- Choose `Tools` -> `Export Final Method Map`
402547
- Choose a temporary file name
@@ -405,26 +550,41 @@ Using BindingStudio:
405550
406551
Congrats! Enumification is complete!
407552
408-
---- Somewhat outdated docs below, update when we do this year's stabilization ----
553+
But wait, there's more!
554+
555+
### New `AndroidManifest.xml` elements and attributes
409556
410-
6) new AndroidManifest.xml elements and attributes
557+
`build-tools/manifest-attribute-codegen/manifest-attribute-codegen.cs` can be
558+
compiled to a tool that collects all Manifest elements and attributes with the
559+
API level since when each of them became available. New members are supposed
560+
to be added to the existing `(FooBar)Attribute.cs` and
561+
`(FooBar)Attribute.Partial.cs` in `src/Mono.Android` and
562+
`src/Xamarin.Android.Build.Tasks`, respectively.
411563
412-
`build-tools/manifest-attribute-codegen/manifest-attribute-codegen.cs` can be compiled to a tool that collects all Manifest elements and attributes with the API level since when each of them became available. New members are supposed to be added to the existing `(FooBar)Attribute.cs` and `(FooBar)Attribute.Partial.cs` in `src/Mono.Android` and `src/Xamarin.Android.Build.Tasks` respectively.
564+
See [`build-tools/manifest-attribute-codegen/README.md`](../../build-tools/manifest-attribute-codegen/README.md)
565+
for details.
413566
414-
Note that there are documented and undocumented XML nodes, and we don't have to deal with undocumented ones.
567+
Note that there are documented and undocumented XML nodes, and we don't have to
568+
deal with undocumented ones.
415569
416570
Android P introduced no documented XML artifact.
417571
418-
7) Update Android Tooling Versions
572+
### Update Android Tooling Versions
573+
574+
[`Xamarin.Android.Common.props.in`](../../src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in)
575+
contains multiple MSBuild properties which provide default versions for various Android SDK packages.
576+
These properties in turn come from properties defined within
577+
[`Configuration.props`](../../Configuration.props).
419578
420-
These sre located in [Xamarin.Android.Common.props.in](../../src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in). The following MSBuild properties need to be updated to ensure
421-
the latest tool versions are being used.
579+
* `$(AndroidSdkBuildToolsVersion)`: Android SDK `build-tools` version.
580+
Defaults to `$(XABuildToolsFolder)` within `Configuration.props`.
581+
* `$(AndroidSdkPlatformToolsVersion)`: Android SDK `platform-tools` version
582+
Defaults to `$(XAPlatformToolsVersion)` within `Configuration.props`.
422583
423-
`AndroidSdkBuildToolsVersion`
424-
`AndroidSdkPlatformToolsVersion`
425-
`AndroidSdkToolsVersion`
584+
The major version should generally match the new API level. For Android P this will be 28.x.x . If a version which exactly matches the API Level is not available then the latest version should be used.
426585
427-
The major version should match the new API level. For Android P this will be 28.x.x . If a version which exactly matches the API Level is not available then the latest version should be used.
586+
A separate PR should be created which bumps the values within `Configuration.props`
587+
to ensure that all unit tests pass.
428588
429589
## Bindings Finalization
430590

build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CheckApiCompatibility.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ void dataReceived (object sender, DataReceivedEventArgs args)
266266
LogError ($"CheckApiCompatibility found nonacceptable Api breakages for ApiLevel: {ApiLevel}.{Environment.NewLine}{string.Join (Environment.NewLine, lines)}");
267267
ReportMissingLines (acceptableIssuesFile.FullName, lines);
268268

269-
var missingItems = CodeGenDiff.GenerateMissingItems (CodeGenPath, contractAssembly.FullName, implementationAssembly.FullName);
269+
var missingItems = CodeGenDiff.GenerateMissingItems (CodeGenPath, contractAssembly.FullName, implementationAssembly.FullName, JdkInfo.CreateTaskLogger (this));
270270
if (missingItems.Any ()) {
271271
Log.LogMessage (MessageImportance.High, $"{Environment.NewLine}*** CodeGen missing items***{Environment.NewLine}");
272272
var indent = 0;

build-tools/Xamarin.Android.Tools.BootstrapTasks/Xamarin.Android.Tools.BootstrapTasks/CodeGenDiff.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ namespace Xamarin.Android.Tools.BootstrapTasks
88
{
99
public sealed class CodeGenDiff
1010
{
11-
public static List<string> GenerateMissingItems (string codeGenPath, string contractAssembly, string implementationAssembly)
11+
public static List<string> GenerateMissingItems (string codeGenPath, string contractAssembly, string implementationAssembly, Action<TraceLevel, string> logger)
1212
{
13-
var contract = GenerateObjectDescription (codeGenPath, contractAssembly);
14-
var implementation = GenerateObjectDescription (codeGenPath, implementationAssembly);
13+
var contract = GenerateObjectDescription (codeGenPath, contractAssembly, logger);
14+
var implementation = GenerateObjectDescription (codeGenPath, implementationAssembly, logger);
1515

1616
return Diff (contract, implementation);
1717
}
@@ -55,7 +55,7 @@ static List<string> Diff (ObjectDescription contract, ObjectDescription implemen
5555
return missingItems;
5656
}
5757

58-
static ObjectDescription GenerateObjectDescription (string codeGenPath, string assembly)
58+
static ObjectDescription GenerateObjectDescription (string codeGenPath, string assembly, Action<TraceLevel, string> logger)
5959
{
6060
ObjectDescription currentObject = new ObjectDescription () { Item = "root" };
6161
var objectStack = new Stack<ObjectDescription> ();
@@ -74,6 +74,8 @@ static ObjectDescription GenerateObjectDescription (string codeGenPath, string a
7474

7575
genApiProcess.StartInfo.Arguments += $"\"{assembly}\"";
7676

77+
logger (TraceLevel.Verbose, $"Executing: `\"{genApiProcess.StartInfo.FileName}\" {genApiProcess.StartInfo.Arguments}`");
78+
7779
genApiProcess.StartInfo.UseShellExecute = false;
7880
genApiProcess.StartInfo.CreateNoWindow = true;
7981
genApiProcess.StartInfo.RedirectStandardOutput = true;
@@ -110,7 +112,12 @@ void dataReceived (object sender, DataReceivedEventArgs args)
110112

111113
if (content.StartsWith ("}", StringComparison.OrdinalIgnoreCase)) {
112114
currentObject.InternalCounter--;
113-
System.Diagnostics.Debug.Assert (currentObject.InternalCounter >= 0);
115+
if (currentObject.InternalCounter < 0) {
116+
logger (TraceLevel.Error, $"Internal Error! currentObject.InternalCounter is {currentObject.InternalCounter}; must be >= 0! " +
117+
$"currentObject.Item=`{currentObject.Item}`");
118+
currentObject.InternalCounter = 0;
119+
}
120+
114121
if (currentObject.InternalCounter == 0) {
115122
objectStack.Pop ();
116123
if (objectStack.Count > 0) {
@@ -121,7 +128,7 @@ void dataReceived (object sender, DataReceivedEventArgs args)
121128
return;
122129
}
123130

124-
if (content.StartsWith ("namespace ", StringComparison.Ordinal) || content.IndexOf (" interface ", StringComparison.Ordinal) != -1 || content.IndexOf (" class ", StringComparison.Ordinal) != -1 || content.IndexOf (" partial struct ", StringComparison.Ordinal) != -1 || content.IndexOf (" enum ", StringComparison.Ordinal) != -1) {
131+
if (content.StartsWith ("namespace ", StringComparison.Ordinal) || LineBeginsType (content)) {
125132
if (string.IsNullOrWhiteSpace (currentObject.Item)) {
126133
currentObject.Item = content;
127134
} else {
@@ -163,6 +170,17 @@ void dataReceived (object sender, DataReceivedEventArgs args)
163170
return currentObject;
164171
}
165172

173+
static bool LineBeginsType (string content)
174+
{
175+
if (content.IndexOf (" partial interface ", StringComparison.Ordinal) != -1
176+
|| content.IndexOf (" partial class ", StringComparison.Ordinal) != -1
177+
|| content.IndexOf (" partial struct ", StringComparison.Ordinal) != -1
178+
|| content.IndexOf (" enum ", StringComparison.Ordinal) != -1) {
179+
return true;
180+
}
181+
return false;
182+
}
183+
166184
class ObjectDescription
167185
{
168186
public string Item;

0 commit comments

Comments
 (0)