Skip to content

Commit

Permalink
BindableReadOnlyProp (#75)
Browse files Browse the repository at this point in the history
* Refactor to reduce duplicate

* BindableReadOnlyProp and a simple test case

* Abtract test class

* Unit test

* Config version

* Clean up code

* Update documentation

* Update cicd

* Filter test

* Upgrade cpdeql version
  • Loading branch information
KafkaWannaFly authored May 4, 2024
1 parent 9e39a50 commit e786a4d
Show file tree
Hide file tree
Showing 42 changed files with 2,463 additions and 2,086 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/auto-update.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ jobs:
id: waitforstatuschecks
uses: WyriHaximus/github-action-wait-for-status@v1.8
with:
ignoreActions: worker,WIP
checkInterval: 300
ignoreActions: worker,WIP,Automerge PRs
checkInterval: 37
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: 'Automerge'
uses: pascalgn/automerge-action@v0.15.6
uses: pascalgn/automerge-action@v0.16.3
if: steps.waitforstatuschecks.outputs.status == 'success'
env:
MERGE_LABELS: "dependencies"
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -56,7 +56,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand All @@ -69,10 +69,10 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

- name: Generate coverage file
run: dotnet test ./UnitTest --collect:"XPlat Code Coverage"
run: dotnet test ./UnitTest --collect:"XPlat Code Coverage" --filter Displayname!~BaseTest

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
Expand Down
15 changes: 9 additions & 6 deletions .github/workflows/publish-to-nuget.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@ jobs:
unit-test:
name: Unit Test
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- name: Current directory
run: pwd

- name: Run unit tests
uses: zyborg/dotnet-tests-report@v1
with:
project_path: UnitTest/
project_path: ./UnitTest/
report_name: unit-test-report
report_title: Unit Test Report
github_token: ${{ secrets.GITHUB_TOKEN }}
fail_build_on_failed_tests: true
fail_build_on_failed_tests: false
msbuild_configuration: Release
msbuild_verbosity: minimal
gist_name: bindable_props_tests.md
gist_badge_label: 'Bindable Props: %Counters_passed%/%Counters_total%'
gist_token: ${{ secrets.GIST_TOKEN }}

extra_test_parameters: --filter Displayname!~BaseTest
publish:
name: Build, Pack & Publish
runs-on: ubuntu-latest
Expand Down
15 changes: 1 addition & 14 deletions BindableProps/AttachedProp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,4 @@
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
// ReSharper disable once UnusedType.Global
public sealed class AttachedProp : Attribute
{
public int DefaultBindingMode { get; set; }

public string? ValidateValueDelegate { get; set; }

public string? PropertyChangedDelegate { get; set; }

public string? PropertyChangingDelegate { get; set; }

public string? CoerceValueDelegate { get; set; }

public string? CreateDefaultValueDelegate { get; set; }
}
public class AttachedProp : BindableProp;
4 changes: 2 additions & 2 deletions BindableProps/BindableProp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
// ReSharper disable once UnusedType.Global
public sealed class BindableProp : Attribute
public class BindableProp : Attribute
{
public int DefaultBindingMode { get; set; }

Expand All @@ -19,4 +19,4 @@ public sealed class BindableProp : Attribute

public string? CreateDefaultValueDelegate { get; set; }
}
}
}
3 changes: 2 additions & 1 deletion BindableProps/BindableProps.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageTags>source-generator;maui;net-standard;net-6;net-7;net-8;utility;helper</PackageTags>
<PackageProjectUrl>https://github.com/KafkaWannaFly/BindableProps</PackageProjectUrl>
<Version>1.3.10</Version>
<Version>1.4.0</Version>
<RepositoryUrl>https://github.com/KafkaWannaFly/BindableProps</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>lion.png</PackageIcon>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions BindableProps/BindableReadOnlyProp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace BindableProps;

[AttributeUsage(AttributeTargets.Field)]
public class BindableReadOnlyProp : BindableProp;
1 change: 1 addition & 0 deletions BindablePropsSG/BindablePropsSG.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Keep this version to be compatible-->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />

<None Include="..\LICENSE.txt">
Expand Down
76 changes: 28 additions & 48 deletions BindablePropsSG/Generators/AllBindablePropsSG.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ namespace BindablePropsSG.Generators
[SuppressMessage("ReSharper", "HeapView.BoxingAllocation")]
public class AllBindablePropsSG : BaseGenerator
{
private readonly List<string> ignoredAttributes = new()
private readonly HashSet<string> ignoredAttributes = new()
{
"IgnoredProp",
"BindableProp",
"IgnoredPropAttribute",
"BindablePropAttribute",
"AttachedProp",
"AttachedPropAttribute"
"AttachedPropAttribute",
"BindableReadOnlyProp",
"BindableReadOnlyPropAttribute"
};

protected override IEnumerable<string> TargetAttributes => new[]
Expand Down Expand Up @@ -62,10 +64,7 @@ protected override void Execute(SourceProductionContext context,
continue;

var fieldTuples = classSymbol.GetMembers()
.Where(symbol => FieldNotIncludeAttributes(
symbol,
ignoredAttributes)
)
.Where(FieldNotIncludeAttributes)
.Where(item =>
{
var fieldSymbol = item as IFieldSymbol;
Expand Down Expand Up @@ -115,9 +114,9 @@ public partial class {classSyntax.Identifier}
ProcessField(source, classSyntax, fieldDeclarationSyntax, symbol);
}

source.Append(@$"
}}
}}
source.Append(@"
}
}
");

return source.ToString();
Expand All @@ -126,66 +125,47 @@ public partial class {classSyntax.Identifier}
protected override void ProcessField(StringBuilder source, ClassDeclarationSyntax classDeclarationSyntax,
SyntaxNode syntaxNode, ISymbol fieldSymbol)
{
var fieldSyntax = (FieldDeclarationSyntax)syntaxNode;

var fieldName = fieldSymbol.Name;
var propName = StringUtil.PascalCaseOf(fieldName);
var dataType = fieldSyntax.Declaration.Type;

// typeof operation doesn't accept nullable data type
var unNullableDataType = dataType.ToString();
if (unNullableDataType[unNullableDataType.Length - 1] == '?')
{
unNullableDataType = unNullableDataType.Substring(0, unNullableDataType.Length - 1);
}

var newKeyword = fieldSyntax.Modifiers
.FirstOrDefault(keyword => keyword.Text.Equals("new"));

var className = classDeclarationSyntax.Identifier.ToString();

var variableDeclaratorSyntax = fieldSyntax.ChildNodes().OfType<VariableDeclarationSyntax>().FirstOrDefault()
?.Variables
.FirstOrDefault();
var defaultValue = variableDeclaratorSyntax?.Initializer?
.Value
.ToString() ?? "default";
var bindablePropParam = SyntaxUtil.ExtractCreateBindablePropertyParam(
classDeclarationSyntax,
syntaxNode,
fieldSymbol,
"Dummy Name That I Don't Care"
);

source.Append($@"
public {newKeyword} static readonly BindableProperty {propName}Property = BindableProperty.Create(
nameof({propName}),
typeof({unNullableDataType}),
typeof({className}),
{defaultValue},
propertyChanged: (bindable, oldValue, newValue) =>
(({className})bindable).{propName} = ({dataType})newValue
public {bindablePropParam.NewKeyWord} static readonly BindableProperty {bindablePropParam.PropName}Property = BindableProperty.Create(
nameof({bindablePropParam.PropName}),
typeof({bindablePropParam.UnNullableFieldType}),
typeof({bindablePropParam.ClassType}),
{bindablePropParam.DefaultValue},
propertyChanged: {bindablePropParam.PropertyChangedDelegate}
);
public {newKeyword} {dataType} {propName}
public {bindablePropParam.NewKeyWord} {bindablePropParam.FieldType} {bindablePropParam.PropName}
{{
get => {fieldName};
get => {bindablePropParam.FieldName};
set
{{
OnPropertyChanging(nameof({propName}));
OnPropertyChanging(nameof({bindablePropParam.PropName}));
{fieldName} = value;
SetValue({className}.{propName}Property, {fieldName});
{bindablePropParam.FieldName} = value;
SetValue({bindablePropParam.ClassType}.{bindablePropParam.PropName}Property, {bindablePropParam.FieldName});
OnPropertyChanged(nameof({propName}));
OnPropertyChanged(nameof({bindablePropParam.PropName}));
}}
}}
");
}

private bool FieldNotIncludeAttributes(ISymbol symbol, ICollection<string> attributeNames)
private bool FieldNotIncludeAttributes(ISymbol symbol)
{
if (symbol is IFieldSymbol fieldSymbol)
{
return fieldSymbol.GetAttributes().Any(
attribute =>
{
var name = attribute?.AttributeClass?.Name;
return attributeNames.Contains(name!);
return ignoredAttributes.Contains(name!);
}
) is not true;
}
Expand Down
81 changes: 25 additions & 56 deletions BindablePropsSG/Generators/AttachedPropSG.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,74 +21,43 @@ protected override void ProcessField(StringBuilder source, ClassDeclarationSynta
SyntaxNode syntaxNode,
ISymbol fieldSymbol)
{
var fieldName = fieldSymbol.Name;
var pascalCaseFieldName = StringUtil.PascalCaseOf(fieldName);
var propName = $"{pascalCaseFieldName}Property";
var bindablePropParam =
SyntaxUtil.ExtractCreateBindablePropertyParam(classSyntax, syntaxNode, fieldSymbol, "AttachedProp");

if (pascalCaseFieldName.Length == 0 || pascalCaseFieldName == fieldName)
{
return;
}

var fieldSyntax = (FieldDeclarationSyntax)syntaxNode;
var defaultOnChangedDelegate = SyntaxUtil.GetDefaultOnChangedDelegate(
bindablePropParam.ClassType!,
bindablePropParam.PropName!,
bindablePropParam.FieldType!
);

var fieldType = fieldSyntax.Declaration.Type;

// typeof operation doesn't accept nullable data type
var unNullableDataType = fieldType.ToString();
if (unNullableDataType[unNullableDataType.Length - 1] == '?')
// For AttachedProp, we don't create a default function for PropertyChangingDelegate
if (defaultOnChangedDelegate == bindablePropParam.PropertyChangedDelegate)
{
unNullableDataType = unNullableDataType.Substring(0, unNullableDataType.Length - 1);
bindablePropParam.PropertyChangedDelegate = "null";
}

var newKeyword = fieldSyntax.Modifiers
.FirstOrDefault(keyword => keyword.Text.Equals("new"));

var className = classSyntax.Identifier;

var defaultFieldValue = SyntaxUtil.GetFieldDefaultValue(fieldSyntax) ?? "default";

var attributeSyntax = SyntaxUtil.GetAttributeByName(fieldSyntax, "AttachedProp");

var attributeArguments = attributeSyntax?.ArgumentList?.Arguments;

var defaultBindingMode = SyntaxUtil.GetAttributeParam(attributeArguments, "DefaultBindingMode") ?? "0";

var validateValueDelegate = SyntaxUtil.GetAttributeParam(attributeArguments, "ValidateValueDelegate") ?? "null";

var propertyChangedDelegate =
SyntaxUtil.GetAttributeParam(attributeArguments, "PropertyChangedDelegate") ?? "null";

var propertyChangingDelegate =
SyntaxUtil.GetAttributeParam(attributeArguments, "PropertyChangingDelegate") ?? "null";

var coerceValueDelegate = SyntaxUtil.GetAttributeParam(attributeArguments, "CoerceValueDelegate") ?? "null";

var createDefaultValueDelegate =
SyntaxUtil.GetAttributeParam(attributeArguments, "CreateDefaultValueDelegate") ?? "null";

source.Append($@"
public {newKeyword} static readonly BindableProperty {propName} = BindableProperty.CreateAttached(
""{pascalCaseFieldName}"",
typeof({unNullableDataType}),
typeof({className}),
{defaultFieldValue},
(BindingMode){defaultBindingMode},
{validateValueDelegate},
{propertyChangedDelegate},
{propertyChangingDelegate},
{coerceValueDelegate},
{createDefaultValueDelegate}
public {bindablePropParam.NewKeyWord} static readonly BindableProperty {bindablePropParam.PropName}Property = BindableProperty.CreateAttached(
""{bindablePropParam.PropName}"",
typeof({bindablePropParam.UnNullableFieldType}),
typeof({bindablePropParam.ClassType}),
{bindablePropParam.DefaultValue},
(BindingMode){bindablePropParam.BindingMode},
{bindablePropParam.ValidateValueDelegate},
{bindablePropParam.PropertyChangedDelegate},
{bindablePropParam.PropertyChangingDelegate},
{bindablePropParam.CoerceValueDelegate},
{bindablePropParam.CreateDefaultValueDelegate}
);
public {newKeyword} static {fieldType} Get{pascalCaseFieldName}(BindableObject view)
public {bindablePropParam.NewKeyWord} static {bindablePropParam.FieldType} Get{bindablePropParam.PropName}(BindableObject view)
{{
return ({fieldType})view.GetValue({propName});
return ({bindablePropParam.FieldType})view.GetValue({bindablePropParam.PropName}Property);
}}
{newKeyword} public static void Set{pascalCaseFieldName}(BindableObject view, {fieldType} value)
{bindablePropParam.NewKeyWord} public static void Set{bindablePropParam.PropName}(BindableObject view, {bindablePropParam.FieldType} value)
{{
view.SetValue({propName}, value);
view.SetValue({bindablePropParam.PropName}Property, value);
}}
");
}
Expand Down
Loading

0 comments on commit e786a4d

Please sign in to comment.