Skip to content

Commit ed7bb00

Browse files
authored
Merge pull request #510 from microsoft/dev/bertaygu/updateMarkdownLinter
Update markdown linter to utilize settings observers and DI.
2 parents b1b7d03 + 6b4559a commit ed7bb00

File tree

5 files changed

+74
-20
lines changed

5 files changed

+74
-20
lines changed
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
{
22
"MarkdownLinter.RunLinterOnCurrentFileCommand.DisplayName": "Run Linter on open file",
3-
"MarkdownLinter.RunLinterOnSolutionCommand.DisplayName": "Run Linter on solution"
3+
"MarkdownLinter.RunLinterOnSolutionCommand.DisplayName": "Run Linter on solution",
4+
"MarkdownLinter.Settings.Category.DisplayName": "Markdown Linter",
5+
"MarkdownLinter.Settings.Category.Description": "Markdown Linter Settings",
6+
"MarkdownLinter.Settings.DisabledRules.DisplayName": "Disabled rules",
7+
"MarkdownLinter.Settings.DisabledRules.Description": "List of disabled rules to pass to markdown-cli"
48
}

New_Extensibility_Model/Samples/MarkdownLinter/LinterUtilities.cs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace MarkdownLinter;
1111
using System.Globalization;
1212
using System.IO;
1313
using System.Text.RegularExpressions;
14+
using System.Threading;
1415
using System.Threading.Tasks;
1516
using Microsoft;
1617
using Microsoft.VisualStudio.Extensibility.Editor;
@@ -19,27 +20,44 @@ namespace MarkdownLinter;
1920
using Microsoft.VisualStudio.RpcContracts.DiagnosticManagement;
2021
using Microsoft.VisualStudio.Threading;
2122

23+
#pragma warning disable VSEXTPREVIEW_SETTINGS // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
24+
2225
/// <summary>
2326
/// Helper class for running linter on a string or file.
2427
/// </summary>
25-
internal static class LinterUtilities
28+
internal class LinterUtilities
2629
{
2730
private static readonly Regex LinterOutputRegex = new(@"(?<File>[^:]+):(?<Line>\d*)(:(?<Column>\d*))? (?<Error>.*)/(?<Description>.*)", RegexOptions.Compiled);
2831

32+
private readonly Settings.MarkdownLinterCategoryObserver settingsObserver;
33+
34+
public LinterUtilities(Settings.MarkdownLinterCategoryObserver settingsObserver)
35+
{
36+
this.settingsObserver = settingsObserver;
37+
}
38+
2939
/// <summary>
3040
/// Runs markdown linter on a file uri and returns diagnostic entries.
3141
/// </summary>
3242
/// <param name="fileUri">File uri to run markdown linter on.</param>
43+
/// <param name="cancellationToken">Cancellation token to monitor.</param>
3344
/// <returns>an enumeration of <see cref="DocumentDiagnostic"/> entries for warnings in the markdown file.</returns>
34-
public static async Task<IEnumerable<DocumentDiagnostic>> RunLinterOnFileAsync(Uri fileUri)
45+
public async Task<IEnumerable<DocumentDiagnostic>> RunLinterOnFileAsync(Uri fileUri, CancellationToken cancellationToken)
3546
{
3647
using var linter = new Process();
3748
var lineQueue = new AsyncQueue<string>();
3849

50+
var snapshot = await this.settingsObserver.GetSnapshotAsync(cancellationToken);
51+
string disabledRules = snapshot.DisabledRules.ValueOrDefault(string.Empty);
52+
53+
string args = "/c \"npx markdownlint-cli" +
54+
(disabledRules.Length > 0 ? $" --disable {disabledRules} --" : string.Empty) +
55+
$" \"{fileUri.LocalPath}\"\"";
56+
3957
linter.StartInfo = new ProcessStartInfo()
4058
{
41-
FileName = "node.exe",
42-
Arguments = $"\"{Environment.ExpandEnvironmentVariables("%APPDATA%\\npm\\node_modules\\markdownlint-cli\\markdownlint.js")}\" \"{fileUri.LocalPath}\"",
59+
FileName = "cmd.exe",
60+
Arguments = args,
4361
RedirectStandardError = true,
4462
UseShellExecute = false,
4563
CreateNoWindow = true,
@@ -76,18 +94,25 @@ public static async Task<IEnumerable<DocumentDiagnostic>> RunLinterOnFileAsync(U
7694
/// Runs markdown linter on a given text document and returns diagnostic entries.
7795
/// </summary>
7896
/// <param name="textDocument">Document to run markdown linter on.</param>
97+
/// <param name="cancellationToken">Cancellation token to monitor.</param>
7998
/// <returns>an enumeration of <see cref="DocumentDiagnostic"/> entries for warnings in the markdown file.</returns>
80-
public static async Task<IEnumerable<DocumentDiagnostic>> RunLinterOnDocumentAsync(ITextDocumentSnapshot textDocument)
99+
public async Task<IEnumerable<DocumentDiagnostic>> RunLinterOnDocumentAsync(ITextDocumentSnapshot textDocument, CancellationToken cancellationToken)
81100
{
82101
using var linter = new Process();
83102
var lineQueue = new AsyncQueue<string>();
84103

85104
var content = textDocument.Text.CopyToString();
86105

106+
var snapshot = await this.settingsObserver.GetSnapshotAsync(cancellationToken);
107+
string disabledRules = snapshot.DisabledRules.ValueOrDefault(string.Empty);
108+
109+
string args = "/k \"npx markdownlint-cli --stdin" +
110+
(disabledRules.Length > 0 ? $" --disable {disabledRules}" : string.Empty) + "\"";
111+
87112
linter.StartInfo = new ProcessStartInfo()
88113
{
89114
FileName = "cmd.exe",
90-
Arguments = $"/k \"{Environment.ExpandEnvironmentVariables("%APPDATA%\\npm\\markdownlint.cmd")}\" -s",
115+
Arguments = args,
91116
RedirectStandardError = true,
92117
RedirectStandardInput = true,
93118
UseShellExecute = false,
@@ -125,16 +150,6 @@ public static async Task<IEnumerable<DocumentDiagnostic>> RunLinterOnDocumentAsy
125150
return CreateDocumentDiagnosticsForOpenDocument(textDocument, markdownDiagnostics);
126151
}
127152

128-
/// <summary>
129-
/// Checks if the given path is a valid markdown file.
130-
/// </summary>
131-
/// <param name="localPath">Local file path to verify.</param>
132-
/// <returns>true if file is a markdown file, false otherwise.</returns>
133-
public static bool IsValidMarkdownFile(string localPath)
134-
{
135-
return localPath is not null && Path.GetExtension(localPath).Equals(".md", StringComparison.OrdinalIgnoreCase);
136-
}
137-
138153
private static IEnumerable<DocumentDiagnostic> CreateDocumentDiagnosticsForOpenDocument(ITextDocumentSnapshot document, IEnumerable<MarkdownDiagnosticInfo> diagnostics)
139154
{
140155
foreach (var diagnostic in diagnostics)

New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticsService.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace MarkdownLinter;
77
using System.Collections.Generic;
88
using System.Threading;
99
using System.Threading.Tasks;
10+
using System.Windows.Media.Animation;
1011
using Microsoft;
1112
using Microsoft.VisualStudio.Extensibility;
1213
using Microsoft.VisualStudio.Extensibility.Documents;
@@ -28,17 +29,20 @@ internal class MarkdownDiagnosticsService : DisposableObject
2829
#pragma warning restore CA2213 // Disposable fields should be disposed
2930
private readonly Dictionary<Uri, CancellationTokenSource> documentCancellationTokens;
3031
private readonly Task initializationTask;
32+
private readonly LinterUtilities linterUtilities;
3133
private OutputChannel? outputChannel;
3234
private DiagnosticsReporter? diagnosticsReporter;
3335

3436
/// <summary>
3537
/// Initializes a new instance of the <see cref="MarkdownDiagnosticsService"/> class.
3638
/// </summary>
3739
/// <param name="extensibility">Extensibility object.</param>
38-
public MarkdownDiagnosticsService(VisualStudioExtensibility extensibility)
40+
/// <param name="linterUtilities">Service for running the linter on markdown files.</param>
41+
public MarkdownDiagnosticsService(VisualStudioExtensibility extensibility, LinterUtilities linterUtilities)
3942
{
4043
this.extensibility = Requires.NotNull(extensibility, nameof(extensibility));
4144
this.documentCancellationTokens = new Dictionary<Uri, CancellationTokenSource>();
45+
this.linterUtilities = Requires.NotNull(linterUtilities, nameof(linterUtilities));
4246
this.initializationTask = Task.Run(this.InitializeAsync);
4347
}
4448

@@ -71,7 +75,7 @@ public async Task ProcessFileAsync(Uri documentUri, CancellationToken cancellati
7175

7276
try
7377
{
74-
var diagnostics = await LinterUtilities.RunLinterOnFileAsync(documentUri);
78+
var diagnostics = await this.linterUtilities.RunLinterOnFileAsync(documentUri, cancellationToken);
7579

7680
await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentUri, cancellationToken);
7781
await this.diagnosticsReporter!.ReportDiagnosticsAsync(diagnostics, cancellationToken);
@@ -151,7 +155,7 @@ private async Task ProcessDocumentAsync(ITextDocumentSnapshot documentSnapshot,
151155

152156
try
153157
{
154-
var diagnostics = await LinterUtilities.RunLinterOnDocumentAsync(documentSnapshot);
158+
var diagnostics = await this.linterUtilities.RunLinterOnDocumentAsync(documentSnapshot, cancellationToken);
155159

156160
await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentSnapshot, cancellationToken);
157161
await this.diagnosticsReporter!.ReportDiagnosticsAsync(diagnostics, cancellationToken);

New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtension.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ protected override void InitializeServices(IServiceCollection serviceCollection)
3838
{
3939
base.InitializeServices(serviceCollection);
4040

41+
// Add the settings observer created by MarkdownLinterSettingDefinitions to use in LinterUtilies.
42+
serviceCollection.AddSettingsObservers();
43+
44+
// Add linter utilities as a singleton, it depends on settings observer.
45+
serviceCollection.AddSingleton<LinterUtilities>();
46+
4147
// As of now, any instance that ingests VisualStudioExtensibility is required to be added as a scoped
4248
// service.
4349
serviceCollection.AddScoped<MarkdownDiagnosticsService>();
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace MarkdownLinter;
5+
6+
using Microsoft.VisualStudio.Extensibility;
7+
using Microsoft.VisualStudio.Extensibility.Settings;
8+
9+
#pragma warning disable VSEXTPREVIEW_SETTINGS // The settings API is currently in preview and marked as experimental
10+
11+
internal static class MarkdownLinterSettingDefinitions
12+
{
13+
[VisualStudioContribution]
14+
internal static SettingCategory MarkdownLinterCategory { get; } = new("markdownLinter", "%MarkdownLinter.Settings.Category.DisplayName%")
15+
{
16+
Description = "%MarkdownLinter.Settings.Category.Description%",
17+
GenerateObserverClass = true,
18+
};
19+
20+
[VisualStudioContribution]
21+
internal static Setting.String DisabledRules { get; } = new("disabledRules", "%MarkdownLinter.Settings.DisabledRules.DisplayName%", MarkdownLinterCategory, defaultValue: string.Empty)
22+
{
23+
Description = "%MarkdownLinter.Settings.DisabledRules.Description%",
24+
};
25+
}

0 commit comments

Comments
 (0)