ما هي روزلين؟
Roslyn عبارة عن مجموعة من المجمعين ذوي المصادر المفتوحة وواجهة برمجة التطبيقات لتحليل الأكواد بلغات Microsoft C # و VisualBasic .NET.
محلل Roslyn هو أداة قوية لتحليل الكود وإيجاد الأخطاء وتحديدها.
بناء جملة شجرة والنموذج الدلالي
لتحليل الشفرة ، يجب أن يكون لديك فهم لشجرة بناء الجملة والنموذج الدلالي ، لأن هذين المكونين الرئيسيين للتحليل الثابت.
شجرة بناء الجملة هي عنصر مبني على أساس الكود المصدري للبرنامج ، وهو ضروري لتحليل الكود. أثناء تحليل الكود ، يتحرك على طوله.
كل رمز لديه شجرة بناء الجملة. لكائن الطبقة القادمة
class A { void Method() { } }
ستبدو شجرة بناء الجملة بالشكل التالي:

كائن النوع SyntaxTree هو شجرة بناء جملة. يمكن تمييز ثلاثة عناصر رئيسية في الشجرة: SyntaxNodes و SyntaxTokens و SyntaxTrivia.
تصف Syntaxnodes تصميمات بناء الجملة ، وهي التعريفات ، عوامل التشغيل ، التعبيرات ، إلخ. في C # ، تمثل بنيات بناء الجملة فئة من نوع SyntaxNode.
يصف Syntaxtokens عناصر مثل: المعرفات والكلمات الرئيسية والأحرف الخاصة. في C # ، وهو نوع من فئة SyntaxToken.
يصف Syntaxtrivia العناصر التي لن يتم تجميعها ، وهي المسافات والتسلسلات والتعليقات وتوجيهات المعالج الأولي. في C # ، يتم تعريفه بواسطة فئة من النوع SyntaxTrivia.
يمثل النموذج الدلالي معلومات حول الكائنات وأنواعها. بفضل هذه الأداة ، يمكنك إجراء تحليل عميق ومعقد. في C # ، يتم تعريفه بواسطة فئة من النوع SemanticModel.
خلق محلل
لإنشاء محلل ثابت ، تحتاج إلى تثبيت المكون .NETCompilerPlatformSDK التالي.
المهام الرئيسية التي تشكل أي محلل ما يلي:
- تسجيل الإجراءات.
الإجراءات هي عبارة عن تغييرات في الرمز يجب على المحلل بدءها للتحقق من الكود. عندما يكتشف VisualStudio تغييرات التعليمات البرمجية التي تتوافق مع الإجراء المسجل ، فإنه يستدعي طريقة محلل مسجل. - إنشاء التشخيص.
عند اكتشاف انتهاك ، يقوم المحلل بإنشاء كائن تشخيصي يستخدمه VisualStudio لإعلام المستخدم بالانتهاك.
هناك عدة خطوات لإنشاء واختبار محلل:
- إنشاء حل.
- تسجيل اسم ووصف محلل.
- تحذيرات وتوصيات محلل التقرير.
- تنفيذ إصلاح التعليمات البرمجية لقبول التوصيات.
- تحسين التحليل مع اختبارات الوحدة.
يتم تسجيل الإجراءات في تجاوز للأسلوب DiagnosticAnalyzer.Initialize (AnalysisContext) ، حيث يكون AnalysisContext هو الطريقة التي يتم بها البحث عن الكائن الذي تم تحليله.
يمكن للمحلل تقديم تصويب كود واحد أو أكثر. يحدد رمز التصحيح التغييرات التي تعالج المشكلة التي تم الإبلاغ عنها. يختار المستخدم التغييرات من واجهة المستخدم (المصابيح الكهربائية في المحرر) ، ويقوم VisualStudio بتغيير الرمز. يصف أسلوب RegisterCodeFixesAsync كيفية تغيير التعليمات البرمجية.
مثال
على سبيل المثال سنكتب محلل الحقول العامة. يجب أن يحذر هذا التطبيق المستخدم من الحقول العامة ويوفر القدرة على تغليف الحقل بخاصية.
إليك ما يجب أن تحصل عليه:

دعونا نتعرف على ما يجب القيام به
تحتاج أولا إلى إيجاد حل.

بعد إنشاء الحل ، نرى أن هناك بالفعل ثلاثة مشاريع.

نحتاج إلى فئتين:
1) Class AnalyzerPublicFieldsAnalyzer ، حيث نحدد معايير تحليل الكود لإيجاد الحقول العامة ووصف التحذير للمستخدم.
نشير إلى الخصائص التالية:
public const string DiagnosticId = "PublicField"; private const string Title = "Filed is public"; private const string MessageFormat = "Field '{0}' is public"; private const string Category = "Syntax"; private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
بعد ذلك ، نشير وفقًا للمعايير التي سيحدث فيها تحليل الحقول العامة.
private static void AnalyzeSymbol(SymbolAnalysisContext context) { var fieldSymbol = context.Symbol as IFieldSymbol; if (fieldSymbol != null && fieldSymbol.DeclaredAccessibility == Accessibility.Public && !fieldSymbol.IsConst && !fieldSymbol.IsAbstract && !fieldSymbol.IsStatic && !fieldSymbol.IsVirtual && !fieldSymbol.IsOverride && !fieldSymbol.IsReadOnly && !fieldSymbol.IsSealed && !fieldSymbol.IsExtern) { var diagnostic = Diagnostic.Create(Rule, fieldSymbol.Locations[0], fieldSymbol.Name); context.ReportDiagnostic(diagnostic); } }
نحصل على حقل لكائن من النوع IFieldSymbol ، والذي يحتوي على خصائص لتعريف مُعدِّلات الحقل واسمها وموقعها. ما نحتاجه للتشخيص.
يبقى لتهيئة محلل عن طريق تحديد في طريقة تجاوز
public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Field); }
2) ننتقل الآن إلى تغيير الكود المقترح بواسطة المستخدم بناءً على تحليل الكود. يحدث هذا في فئة AnalyzerPublicFieldsCodeFixProvider.
للقيام بذلك ، أشر إلى ما يلي:
private const string title = "Encapsulate field"; public sealed override ImmutableArray<string> FixableDiagnosticIds { get { return ImmutableArray.Create(AnalyzerPublicFieldsAnalyzer.DiagnosticId); } } public sealed override FixAllProvider GetFixAllProvider() { return WellKnownFixAllProviders.BatchFixer; } public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); var diagnostic = context.Diagnostics.First(); var diagnosticSpan = diagnostic.Location.SourceSpan; var initialToken = root.FindToken(diagnosticSpan.Start); context.RegisterCodeFix( CodeAction.Create(title, c => EncapsulateFieldAsync(context.Document, initialToken, c), AnalyzerPublicFieldsAnalyzer.DiagnosticId), diagnostic); }
ونحدد القدرة على تغليف الحقل بخاصية في أسلوب EncapsulateFieldAsync.
private async Task<Document> EncapsulateFieldAsync(Document document, SyntaxToken declaration, CancellationToken cancellationToken) { var field = FindAncestorOfType<FieldDeclarationSyntax>(declaration.Parent); var fieldType = field.Declaration.Type; ChangeNameFieldAndNameProperty(declaration.ValueText, out string fieldName, out string propertyName); var fieldDeclaration = CreateFieldDecaration(fieldName, fieldType); var propertyDeclaration = CreatePropertyDecaration(fieldName, propertyName, fieldType); var root = await document.GetSyntaxRootAsync(); var newRoot = root.ReplaceNode(field, new List<SyntaxNode> { fieldDeclaration, propertyDeclaration }); var newDocument = document.WithSyntaxRoot(newRoot); return newDocument; }
للقيام بذلك ، قم بإنشاء حقل خاص.
private FieldDeclarationSyntax CreateFieldDecaration(string fieldName, TypeSyntax fieldType) { var variableDeclarationField = SyntaxFactory.VariableDeclaration(fieldType) .AddVariables(SyntaxFactory.VariableDeclarator(fieldName)); return SyntaxFactory.FieldDeclaration(variableDeclarationField) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); }
ثم قم بإنشاء خاصية عامة تقوم بإرجاع واستلام هذا الحقل الخاص.
private PropertyDeclarationSyntax CreatePropertyDecaration(string fieldName, string propertyName, TypeSyntax propertyType) { var syntaxGet = SyntaxFactory.ParseStatement($"return {fieldName};"); var syntaxSet = SyntaxFactory.ParseStatement($"{fieldName} = value;"); return SyntaxFactory.PropertyDeclaration(propertyType, propertyName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) .AddAccessorListAccessors( SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithBody(SyntaxFactory.Block(syntaxGet)), SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithBody(SyntaxFactory.Block(syntaxSet))); }
في الوقت نفسه ، نحفظ نوع واسم المصدر. يتم إنشاء اسم الحقل على النحو التالي "_name" ، واسم الخاصية "الاسم".
مراجع
- مصادر جيثب
- برنامج .NET Compiler Platform SDK