forked from unoplatform/uno
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: UnoInitializeComponentAnalyzer
(cherry picked from commit 33df5dc)
- Loading branch information
1 parent
81ec091
commit 583210a
Showing
2 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// https://github.com/dotnet/roslyn/blob/e1e7d07474a64f4da5c8b0b7f57082de5ce6a22b/src/Compilers/Core/Portable/Text/SourceTextStream.cs | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Text; | ||
|
||
namespace Microsoft.CodeAnalysis.Text | ||
{ | ||
/// <summary> | ||
/// A read-only, non-seekable <see cref="Stream"/> over a <see cref="SourceText"/>. | ||
/// </summary> | ||
internal sealed class SourceTextStream : Stream | ||
{ | ||
private readonly SourceText _source; | ||
private readonly Encoding _encoding; | ||
private readonly Encoder _encoder; | ||
|
||
private readonly int _minimumTargetBufferCount; | ||
private int _position; | ||
private int _sourceOffset; | ||
private readonly char[] _charBuffer; | ||
private int _bufferOffset; | ||
private int _bufferUnreadChars; | ||
private bool _preambleWritten; | ||
|
||
private static readonly Encoding s_utf8EncodingWithNoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false); | ||
|
||
public SourceTextStream(SourceText source, int bufferSize = 2048, bool useDefaultEncodingIfNull = false) | ||
{ | ||
Debug.Assert(source.Encoding != null || useDefaultEncodingIfNull); | ||
|
||
_source = source; | ||
_encoding = source.Encoding ?? s_utf8EncodingWithNoBOM; | ||
_encoder = _encoding.GetEncoder(); | ||
_minimumTargetBufferCount = _encoding.GetMaxByteCount(charCount: 1); | ||
_sourceOffset = 0; | ||
_position = 0; | ||
_charBuffer = new char[Math.Min(bufferSize, _source.Length)]; | ||
_bufferOffset = 0; | ||
_bufferUnreadChars = 0; | ||
_preambleWritten = false; | ||
} | ||
|
||
public override bool CanRead | ||
{ | ||
get { return true; } | ||
} | ||
|
||
public override bool CanSeek | ||
{ | ||
get { return false; } | ||
} | ||
|
||
public override bool CanWrite | ||
{ | ||
get { return false; } | ||
} | ||
|
||
public override void Flush() | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public override long Length | ||
{ | ||
get { throw new NotSupportedException(); } | ||
} | ||
|
||
public override long Position | ||
{ | ||
get { return _position; } | ||
set { throw new NotSupportedException(); } | ||
} | ||
|
||
public override int Read(byte[] buffer, int offset, int count) | ||
{ | ||
if (count < _minimumTargetBufferCount) | ||
{ | ||
// The buffer must be able to hold at least one character from the | ||
// SourceText stream. Returning 0 for that case isn't correct because | ||
// that indicates end of stream vs. insufficient buffer. | ||
throw new ArgumentException($"{nameof(count)} must be greater than or equal to {_minimumTargetBufferCount}", nameof(count)); | ||
} | ||
|
||
int originalCount = count; | ||
|
||
if (!_preambleWritten) | ||
{ | ||
int bytesWritten = WritePreamble(buffer, offset, count); | ||
offset += bytesWritten; | ||
count -= bytesWritten; | ||
} | ||
|
||
while (count >= _minimumTargetBufferCount && _position < _source.Length) | ||
{ | ||
if (_bufferUnreadChars == 0) | ||
{ | ||
FillBuffer(); | ||
} | ||
|
||
int charsUsed, bytesUsed; | ||
bool ignored; | ||
_encoder.Convert(_charBuffer, _bufferOffset, _bufferUnreadChars, buffer, offset, count, flush: false, charsUsed: out charsUsed, bytesUsed: out bytesUsed, completed: out ignored); | ||
_position += charsUsed; | ||
_bufferOffset += charsUsed; | ||
_bufferUnreadChars -= charsUsed; | ||
offset += bytesUsed; | ||
count -= bytesUsed; | ||
} | ||
|
||
// Return value is the number of bytes read | ||
return originalCount - count; | ||
} | ||
|
||
private int WritePreamble(byte[] buffer, int offset, int count) | ||
{ | ||
_preambleWritten = true; | ||
byte[] preambleBytes = _encoding.GetPreamble(); | ||
if (preambleBytes == null) | ||
{ | ||
return 0; | ||
} | ||
|
||
int length = Math.Min(count, preambleBytes.Length); | ||
Array.Copy(preambleBytes, 0, buffer, offset, length); | ||
return length; | ||
} | ||
|
||
private void FillBuffer() | ||
{ | ||
int charsToRead = Math.Min(_charBuffer.Length, _source.Length - _sourceOffset); | ||
_source.CopyTo(_sourceOffset, _charBuffer, 0, charsToRead); | ||
_sourceOffset += charsToRead; | ||
_bufferOffset = 0; | ||
_bufferUnreadChars = charsToRead; | ||
} | ||
|
||
public override long Seek(long offset, SeekOrigin origin) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public override void SetLength(long value) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
|
||
public override void Write(byte[] buffer, int offset, int count) | ||
{ | ||
throw new NotSupportedException(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Xml; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
using Microsoft.CodeAnalysis.Text; | ||
|
||
namespace Uno.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class UnoInitializeComponentAnalyzer : DiagnosticAnalyzer | ||
{ | ||
internal const string Title = "Call 'InitializeComponent()' from code-behind"; | ||
internal const string MessageFormat = "Type '{0}' should call 'InitializeComponent()' from its constructor"; | ||
internal const string Category = "Correctness"; | ||
|
||
internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor( | ||
#pragma warning disable RS2008 // Enable analyzer release tracking | ||
"Uno0006", | ||
#pragma warning restore RS2008 // Enable analyzer release tracking | ||
Title, | ||
MessageFormat, | ||
Category, | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true, | ||
helpLinkUri: "https://aka.platform.uno/UNO0006" | ||
); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
|
||
context.RegisterCompilationStartAction(context => | ||
{ | ||
// .NET doesn't have ConcurrentHashSet. https://github.com/dotnet/runtime/issues/39919 | ||
var xClasses = new ConcurrentDictionary<string, byte>(); | ||
foreach (var xamlFile in context.Options.AdditionalFiles) | ||
{ | ||
if (!xamlFile.Path.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
continue; | ||
} | ||
var stream = new SourceTextStream(xamlFile.GetText(context.CancellationToken)); | ||
using (XmlReader reader = XmlReader.Create(stream)) | ||
{ | ||
reader.MoveToContent(); | ||
var xClass = reader.GetAttribute("x:Class"); | ||
if (!string.IsNullOrEmpty(xClass)) | ||
{ | ||
xClasses.TryAdd(xClass, 0); | ||
} | ||
} | ||
} | ||
context.RegisterOperationAction(context => | ||
{ | ||
var invocation = (IInvocationOperation)context.Operation; | ||
if (invocation.TargetMethod.Name == "InitializeComponent" && invocation.TargetMethod.Parameters.Length == 0 && | ||
context.ContainingSymbol is IMethodSymbol { MethodKind: MethodKind.Constructor } constructor && constructor.Parameters.Length == 0) | ||
{ | ||
xClasses.TryRemove(context.ContainingSymbol.ContainingType.ToDisplayString(), out _); | ||
} | ||
}, OperationKind.Invocation); | ||
context.RegisterCompilationEndAction(context => | ||
{ | ||
foreach (var xClass in xClasses) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create(Rule, Location.None, xClass.Key)); | ||
} | ||
}); | ||
}); | ||
} | ||
} |