From 618afe369fb4554212a1b3255c29702e0757a74a Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Wed, 26 May 2021 15:28:42 -0700 Subject: [PATCH 1/6] Started work on linting rules for documentation. --- .../DocumentationGeneration.cs | 88 +++++++- .../Linting/IDocumentationLintingRule.cs | 192 ++++++++++++++++++ .../ProcessDocComments.cs | 91 +++------ .../DataStructures/ReservedKeywords.fs | 1 + 4 files changed, 310 insertions(+), 62 deletions(-) create mode 100644 src/Documentation/DocumentationGenerator/Linting/IDocumentationLintingRule.cs diff --git a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs index 68aed7caa5..935853fc00 100644 --- a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs +++ b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs @@ -3,7 +3,10 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.Quantum.Documentation.Linting; using Microsoft.Quantum.QsCompiler; using Microsoft.Quantum.QsCompiler.SyntaxTree; using QsAssemblyConstants = Microsoft.Quantum.QsCompiler.ReservedKeywords.AssemblyConstants; @@ -16,6 +19,17 @@ namespace Microsoft.Quantum.Documentation /// public class DocumentationGeneration : IRewriteStep { + private static readonly ImmutableDictionary lintingRules; + + static DocumentationGeneration() + { + var rules = ImmutableDictionary.CreateBuilder(); + rules.Add("require-correct-input-names", (true, new RequireCorrectInputNames())); + rules.Add("require-examples", (EnableByDefault: false, new RequireExamplesOnPublicDeclarations())); + rules.Add("no-math-in-summary", (true, new NoMathInSummary())); + lintingRules = rules.ToImmutableDictionary(); + } + private string docsOutputPath = ""; private readonly List diagnostics; @@ -92,11 +106,81 @@ public bool PreconditionVerification(QsCompilation compilation) /// public bool Transformation(QsCompilation compilation, out QsCompilation transformed) { + // Find a list of linting rules to be enabled and disabled by + // by looking at the relevant assembly constant. + // We expect linting rule configurations to be formatted as a comma-separated + // list of rules, each one prefaced with either "ignore:", "warn:" + // or "error:", indicating the level of severity for each. + var lintingRulesConfig = ( + this.AssemblyConstants + .TryGetValue(QsAssemblyConstants.DocsPackageId, out var config) + ? config ?? "" + : "") + .Split(",") + .Select(rule => + { + var ruleParts = rule.Split(":", 2); + return (severity: ruleParts[0], ruleName: ruleParts[1]); + }) + .ToDictionary( + config => config.ruleName, + config => config.severity); + + // If any rules were specified that aren't present, warn about that + // now. + foreach ((var ruleName, var severity) in lintingRulesConfig) + { + if (!DocumentationGeneration.lintingRules.ContainsKey(ruleName)) + { + this.diagnostics.Add(new IRewriteStep.Diagnostic + { + Severity = DiagnosticSeverity.Info, + Message = $"Documentation linting rule {ruleName} was set to {severity}, but no such linting rule exists.", + Stage = IRewriteStep.Stage.Transformation, + }); + } + } + + // Actually populate the rules now. + var lintingRules = DocumentationGeneration + .lintingRules + .Select( + rule => ( + Name: rule.Key, + Severity: + ( + rule.Value.EnableByDefault, + lintingRulesConfig.TryGetValue(rule.Key, out var severity) ? severity : null) + switch + { + // We handle should happen when the user didn't + // override here. + (true, null) => DiagnosticSeverity.Warning, + (false, null) => DiagnosticSeverity.Hidden, + + // If the user did override, we can ignore + // EnableByDefault. + (_, "ignore") => DiagnosticSeverity.Hidden, + (_, "warning") => DiagnosticSeverity.Warning, + (_, "error") => DiagnosticSeverity.Error, + + // If we get down to this point, something went + // wrong; the given severity wasn't recognized. + (_, var unknown) => throw new Exception( + $"Documentation linting severity for rule {rule.Key} was set to {unknown}, but expected one of \"error\", \"warning\", or \"ignore\"") + }, + Rule: rule.Value.Rule)) + .Where(rule => rule.Severity != DiagnosticSeverity.Hidden) + .ToDictionary( + rule => rule.Name, + rule => (rule.Severity, rule.Rule)); + var docProcessor = new ProcessDocComments( this.docsOutputPath, this.AssemblyConstants.TryGetValue(QsAssemblyConstants.DocsPackageId, out var packageName) - ? packageName - : null); + ? packageName + : null, + lintingRules); docProcessor.OnDiagnostic += diagnostic => { diff --git a/src/Documentation/DocumentationGenerator/Linting/IDocumentationLintingRule.cs b/src/Documentation/DocumentationGenerator/Linting/IDocumentationLintingRule.cs new file mode 100644 index 0000000000..58889e768d --- /dev/null +++ b/src/Documentation/DocumentationGenerator/Linting/IDocumentationLintingRule.cs @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.Quantum.QsCompiler; +using Microsoft.Quantum.QsCompiler.Documentation; +using Microsoft.Quantum.QsCompiler.SyntaxTree; +using Range = Microsoft.Quantum.QsCompiler.DataTypes.Range; + +namespace Microsoft.Quantum.Documentation.Linting +{ + internal static class LintingExtensions + { + internal static void InvokeRules( + this IDictionary rules, + Func> invokeRule, + Action onDiagnostic) + { + foreach ((var lintName, (var severity, var lintRule)) in rules) + { + foreach (var raisedDiagnostic in invokeRule(lintRule)) + { + onDiagnostic( + raisedDiagnostic.AsDiagnostic(severity, lintName)); + } + } + } + } + + public class LintingMessage + { + public string? Message { get; set; } + + public Range? Range { get; set; } + + public string? Source { get; set; } + + public IRewriteStep.Diagnostic AsDiagnostic( + DiagnosticSeverity severity = DiagnosticSeverity.Warning, + string? ruleName = null) + => new IRewriteStep.Diagnostic + { + Message = $"{(ruleName == null ? "" : $"({ruleName}) ")}{this.Message}", + Range = this.Range, + Severity = severity, + Stage = IRewriteStep.Stage.Transformation, + Source = this.Source + }; + } + + public interface IDocumentationLintingRule + { + IEnumerable OnTypeDeclaration(QsCustomType type, DocComment comment) + { + yield break; + } + + IEnumerable OnCallableDeclaration(QsCallable callable, DocComment comment) + { + yield break; + } + } + + public class RequireExamplesOnPublicDeclarations : IDocumentationLintingRule + { + public IEnumerable OnCallableDeclaration(QsCallable callable, DocComment comment) + { + if (!callable.Access.IsPublic) + { + yield break; + } + + if (comment.Examples.IsEmpty) + { + yield return new LintingMessage + { + Message = $"Public callable {callable.FullName} does not have any examples.", + Source = callable.Source.AssemblyOrCodeFile, + Range = null, // TODO: provide more exact locations once supported by DocParser. + }; + } + } + + public IEnumerable OnTypeDeclaration(QsCustomType type, DocComment comment) + { + if (!type.Access.IsPublic) + { + yield break; + } + + if (comment.Examples.IsEmpty) + { + yield return new LintingMessage + { + Message = $"Public user-defined type {type.FullName} does not have any examples.", + Source = type.Source.AssemblyOrCodeFile, + Range = null, // TODO: provide more exact locations once supported by DocParser. + }; + } + } + } + + public class NoMathInSummary : IDocumentationLintingRule + { + private IEnumerable OnComment(DocComment comment, string name, string? source) + { + if (comment.Summary.Contains("$")) + { + yield return new LintingMessage + { + Message = $"Summary for {name} should not contain LaTeX notation.", + Source = source, + Range = null, // TODO: provide more exact locations once supported by DocParser. + }; + } + } + + public IEnumerable OnCallableDeclaration(QsCallable callable, DocComment comment) => + this.OnComment(comment, $"{callable.FullName.Namespace}.{callable.FullName.Name}", callable.Source.AssemblyOrCodeFile); + + public IEnumerable OnTypeDeclaration(QsCustomType type, DocComment comment) => + this.OnComment(comment, $"{type.FullName.Namespace}.{type.FullName.Name}", type.Source.AssemblyOrCodeFile); + } + + public class RequireCorrectInputNames : IDocumentationLintingRule + { + public IEnumerable OnCallableDeclaration(QsCallable callable, DocComment comment) + { + var callableName = + $"{callable.FullName.Namespace}.{callable.FullName.Name}"; + // Validate input and type parameter names. + var inputDeclarations = callable.ArgumentTuple.ToDictionaryOfDeclarations(); + var inputMessages = this.ValidateNames( + callableName, + "input", + name => inputDeclarations.ContainsKey(name), + comment.Input.Keys, + range: null, // TODO: provide more exact locations once supported by DocParser. + source: callable.Source.AssemblyOrCodeFile); + var typeParamMessages = this.ValidateNames( + callableName, + "type parameter", + name => callable.Signature.TypeParameters.Any( + typeParam => + typeParam is QsLocalSymbol.ValidName validName && + validName.Item == name.TrimStart('\'')), + comment.TypeParameters.Keys, + range: null, // TODO: provide more exact locations once supported by DocParser. + source: callable.Source.AssemblyOrCodeFile); + + return inputMessages.Concat(typeParamMessages); + } + + public IEnumerable OnTypeDeclaration(QsCustomType type, DocComment comment) + { + // Validate named item names. + var inputDeclarations = type.TypeItems.ToDictionaryOfDeclarations(); + return this.ValidateNames( + $"{type.FullName.Namespace}.{type.FullName.Name}", + "named item", + name => inputDeclarations.ContainsKey(name), + comment.Input.Keys, + range: null, // TODO: provide more exact locations once supported by DocParser. + source: type.Source.AssemblyOrCodeFile); + } + + private IEnumerable ValidateNames( + string symbolName, + string nameKind, + Func isNameValid, + IEnumerable actualNames, + Range? range = null, + string? source = null) + { + foreach (var name in actualNames) + { + if (!isNameValid(name)) + { + yield return new LintingMessage + { + Message = $"When documenting {symbolName}, found documentation for {nameKind} {name}, but no such {nameKind} exists.", + Range = range, + Source = source, + }; + } + } + } + } +} diff --git a/src/Documentation/DocumentationGenerator/ProcessDocComments.cs b/src/Documentation/DocumentationGenerator/ProcessDocComments.cs index 1f7fb68e33..2e9d8ab04b 100644 --- a/src/Documentation/DocumentationGenerator/ProcessDocComments.cs +++ b/src/Documentation/DocumentationGenerator/ProcessDocComments.cs @@ -3,13 +3,15 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using Microsoft.Quantum.Documentation.Linting; using Microsoft.Quantum.QsCompiler; using Microsoft.Quantum.QsCompiler.Documentation; using Microsoft.Quantum.QsCompiler.SyntaxTokens; using Microsoft.Quantum.QsCompiler.SyntaxTree; using Microsoft.Quantum.QsCompiler.Transformations.Core; - +using DiagnosticSeverity = Microsoft.CodeAnalysis.DiagnosticSeverity; using Range = Microsoft.Quantum.QsCompiler.DataTypes.Range; namespace Microsoft.Quantum.Documentation @@ -45,7 +47,16 @@ public class TransformationState /// if the documentation to be written by this object does not /// relate to a particular package. /// - public ProcessDocComments(string outputPath, string? packageName = null) + /// + /// A dictionary of named linting rules that will be applied to + /// each different callable and UDT definition to yield additional + /// diagnostics, as well as the severity that will be applied to + /// each such diagnostic. + /// + public ProcessDocComments( + string outputPath, + string? packageName = null, + IDictionary? lintingRules = null) : base(new TransformationState(), TransformationOptions.Disabled) { this.Writer = new DocumentationWriter(outputPath, packageName); @@ -54,7 +65,7 @@ public ProcessDocComments(string outputPath, string? packageName = null) this.OnDiagnostic?.Invoke(diagnostic); // We provide our own custom namespace transformation, and expression kind transformation. - this.Namespaces = new NamespaceTransformation(this, this.Writer); + this.Namespaces = new NamespaceTransformation(this, this.Writer, lintingRules); } private class NamespaceTransformation @@ -62,35 +73,17 @@ private class NamespaceTransformation { private readonly DocumentationWriter? writer; - internal NamespaceTransformation(ProcessDocComments parent, DocumentationWriter? writer) + private readonly ImmutableDictionary lintingRules; + + internal NamespaceTransformation( + ProcessDocComments parent, + DocumentationWriter? writer, + IDictionary? lintingRules = null) : base(parent) { this.writer = writer; - } - - private void ValidateNames( - string symbolName, - string nameKind, - Func isNameValid, - IEnumerable actualNames, - Range? range = null, - string? source = null) - { - foreach (var name in actualNames) - { - if (!isNameValid(name)) - { - (this.Transformation as ProcessDocComments)?.OnDiagnostic?.Invoke( - new IRewriteStep.Diagnostic - { - Message = $"When documenting {symbolName}, found documentation for {nameKind} {name}, but no such {nameKind} exists.", - Severity = CodeAnalysis.DiagnosticSeverity.Warning, - Range = range, - Source = source, - Stage = IRewriteStep.Stage.Transformation, - }); - } - } + this.lintingRules = lintingRules?.ToImmutableDictionary() + ?? ImmutableDictionary.Empty; } public override QsNamespace OnNamespace(QsNamespace ns) @@ -133,15 +126,10 @@ public override QsCustomType OnTypeDeclaration(QsCustomType type) deprecated: isDeprecated, replacement: replacement); - // Validate named item names. - var inputDeclarations = type.TypeItems.ToDictionaryOfDeclarations(); - this.ValidateNames( - $"{type.FullName.Namespace}.{type.FullName.Name}", - "named item", - name => inputDeclarations.ContainsKey(name), - docComment.Input.Keys, - range: null, // TODO: provide more exact locations once supported by DocParser. - source: type.Source.AssemblyOrCodeFile); + this.lintingRules.InvokeRules( + rule => rule.OnTypeDeclaration(type, docComment), + (this.Transformation as ProcessDocComments)?.OnDiagnostic + ?? ((_) => { })); if (type.Access.IsPublic) { @@ -177,28 +165,11 @@ public override QsCallable OnCallableDeclaration(QsCallable callable) callable.FullName.Name, deprecated: isDeprecated, replacement: replacement); - var callableName = - $"{callable.FullName.Namespace}.{callable.FullName.Name}"; - - // Validate input and type parameter names. - var inputDeclarations = callable.ArgumentTuple.ToDictionaryOfDeclarations(); - this.ValidateNames( - callableName, - "input", - name => inputDeclarations.ContainsKey(name), - docComment.Input.Keys, - range: null, // TODO: provide more exact locations once supported by DocParser. - source: callable.Source.AssemblyOrCodeFile); - this.ValidateNames( - callableName, - "type parameter", - name => callable.Signature.TypeParameters.Any( - typeParam => - typeParam is QsLocalSymbol.ValidName validName && - validName.Item == name.TrimStart('\'')), - docComment.TypeParameters.Keys, - range: null, // TODO: provide more exact locations once supported by DocParser. - source: callable.Source.AssemblyOrCodeFile); + + this.lintingRules.InvokeRules( + rule => rule.OnCallableDeclaration(callable, docComment), + (this.Transformation as ProcessDocComments)?.OnDiagnostic + ?? ((_) => { })); if (callable.Access.IsPublic) { diff --git a/src/QsCompiler/DataStructures/ReservedKeywords.fs b/src/QsCompiler/DataStructures/ReservedKeywords.fs index ad69423953..e9f8b0fdab 100644 --- a/src/QsCompiler/DataStructures/ReservedKeywords.fs +++ b/src/QsCompiler/DataStructures/ReservedKeywords.fs @@ -259,6 +259,7 @@ module AssemblyConstants = let PerfDataOutputPath = "PerfDataOutputPath" let DocsOutputPath = "DocsOutputPath" let DocsPackageId = "DocsPackageId" + let DocsLintingRules = "DocsLintingRules" let GenerateConcreteIntrinsic = "GenerateConcreteIntrinsic" /// The runtime capabilities supported by an execution target. The names of the capabilities here match the ones From c75b383c52198afc95d17438d98014b5581514bd Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Wed, 26 May 2021 15:37:25 -0700 Subject: [PATCH 2/6] Add linting rules to SDK and document. --- src/QuantumSdk/README.md | 56 ++++++++++++++++++++-------------- src/QuantumSdk/Sdk/Sdk.targets | 1 + 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/QuantumSdk/README.md b/src/QuantumSdk/README.md index 1cca1b01c5..57bb9e352e 100644 --- a/src/QuantumSdk/README.md +++ b/src/QuantumSdk/README.md @@ -118,56 +118,66 @@ in the project that implements the compilation step. The Sdk defines the following properties for each project using it: -- `QSharpLangVersion`: +- `QSharpLangVersion`: The version of the Q# language specification. -- `QuantumSdkVersion`: +- `QuantumSdkVersion`: The NuGet version of the Sdk package. The following properties can be configured to customize the build: -- `AdditionalQscArguments`: +- `AdditionalQscArguments`: May contain additional arguments to pass to the Q# command line compiler. Valid additional arguments are `--emit-dll`, or `--no-warn` followed by any number of integers specifying the warnings to ignore. -- `CSharpGeneration`: +- `CSharpGeneration`: Specifies whether to generate C# code as part of the compilation process. Setting this property to false may prevent certain interoperability features or integration with other pieces of the Quantum Development Kit. -- `DefaultSimulator`: +- `DefaultSimulator`: Specifies the simulator to use by default for execution. Valid values are QuantumSimulator, ToffoliSimulator, ResourcesEstimator, or the fully qualified name of a custom simulator. -- `IncludeQSharpCorePackages`: +- `IncludeQSharpCorePackages`: Specifies whether the packages providing the basic language support for Q# are referenced. This property is set to true by default. If set to false, the Sdk will not reference any Q# libraries. -- `IncludeProviderPackages`: +- `IncludeProviderPackages`: Specifies whether the packages for specific hardware providers should be automatically included based on the specified `ExecutionTarget`. This property is set to true by default. If set to false, the Sdk will not automatically reference any provider packages. -- `QscExe`: +- `QscExe`: The command to invoke the Q# compiler. The value set by default invokes the Q# compiler that is packaged as tool with the Sdk. The default value can be accessed via the `DefaultQscExe` property. -- `QscVerbosity`: +- `QscVerbosity`: Defines the verbosity of the Q# compiler. Recognized values are: Quiet, Minimal, Normal, Detailed, and Diagnostic. -- `PerfDataGeneration`: +- `PerfDataGeneration`: Specifies whether to generate performance analysis data for the compilation. The default value is "true" if `PerfDataOutputPath` is specified and "false" otherwise. Note that setting this property to `"false"` will disable generating performance data, even if `PerfDataOutputPath` is also set. -- `PerfDataOutputPath`: +- `PerfDataOutputPath`: Directory where the generated performance analysis data will be saved. If no directory is specified and `PerfDataGeneration` is set to "true", it will be set to "$(MSBuildProjectDirectory)/perf". -- `QirGeneration`: +- `QirGeneration`: Specifies whether to generate QIR for the compiled Q# code. The default value is "true" if `QirOutputPath` is specified and "false" otherwise. Note that setting this property to `"false"` will disable generating QIR, even if `QirOutputPath` is also set. -- `QirOutputPath`: +- `QirOutputPath`: Directory where the generated QIR will be saved. If no directory is specified and `QirGeneration` is set to "true", it will be set to "$(MSBuildProjectDirectory)/qir". -- `QSharpDocsGeneration`: +- `QSharpDocsGeneration`: Specifies whether to generate yml documentation for the compiled Q# code. The default value is "true" if `QSharpDocsOutputPath` is specified and "false" otherwise. Note that setting this property to `"false"` will disable generating docs, even if `QSharpDocsOutputPath` is also set. -- `QSharpDocsOutputPath`: +- `QSharpDocsOutputPath`: Directory where the generated documentation will be saved. If no directory is specified and `QSharpDocsGeneration` is set to "true", it will be set to "$(MSBuildProjectDirectory)/docs". -- `QSharpDocsPackageId`: +- `QSharpDocsPackageId`: Specifies the package ID that should appear in generated documentation. Set to `PackageId` by default, but can be overridden to allow for documenting parts of metapackages. +- `QSharpDocsLintingRules`: + Specifies which documentation linting rules should be applied to documentation comments, and whether those rules should be treated as warnings or errors. + + For example, `warning:no-math-in-summary,error:require-examples` will raise a warning when summaries contain LaTeX notation, and an error when public API comments do not include examples. + + Currently supported rules: + + - `require-correct-input-names` (default: `warning`): Validates that input and type parameter subheadings correspond to valid input and type parameter names. + - `require-examples` (default: `ignore`): Requires that any public function, operation, or UDT includes at least one example. + - `no-math-in-summary` (default: `warning`): Requires that summary sections do not contain LaTeX notation. [comment]: # (TODO: document QscBuildConfigExe, QscBuildConfigOutputPath) @@ -175,16 +185,16 @@ Specifies the package ID that should appear in generated documentation. Set to ` The following configurable item groups are used by the Sdk: -- `PackageLoadFallbackFolder`: +- `PackageLoadFallbackFolder`: Contains the directories where the Q# compiler will look for a suitable dll if a qsc reference or one if its dependencies cannot be found. By default, the project output path is included in this item group. -- `PackageReference`: +- `PackageReference`: Contains all referenced NuGet packages. Package references for which the `IsQscReference` attribute is set to "true" may extend the Q# compiler and any implemented rewrite steps will be executed as part of the compilation process. See [this section](#extending-the-q#-compiler) for more details. -- `ProjectReference`: +- `ProjectReference`: Contains all referenced projects. Project references for which the `IsQscReference` attribute is set to "true" may extend the Q# compiler and any implemented rewrite steps will be executed as part of the compilation process. See [this section](#extending-the-q#-compiler) for more details. -- `QSharpCompile`: +- `QSharpCompile`: Contains all Q# source files included in the compilation. # Sdk Packages # @@ -209,6 +219,6 @@ To avoid issues with conflicting packages, we load each Q# compiler extension in ## Known Issues ## The following issues and PRs may be of interest when using the Sdk: -> https://github.com/NuGet/Home/issues/8692 -> https://github.com/dotnet/runtime/issues/949 -> https://github.com/NuGet/NuGet.Client/pull/3170 +> https://github.com/NuGet/Home/issues/8692 +> https://github.com/dotnet/runtime/issues/949 +> https://github.com/NuGet/NuGet.Client/pull/3170 diff --git a/src/QuantumSdk/Sdk/Sdk.targets b/src/QuantumSdk/Sdk/Sdk.targets index 72d44d5e92..64f9656f04 100644 --- a/src/QuantumSdk/Sdk/Sdk.targets +++ b/src/QuantumSdk/Sdk/Sdk.targets @@ -109,6 +109,7 @@ <_QscCommandPredefinedAssemblyProperties Condition="'$(QSharpDocsGeneration)'">$(_QscCommandPredefinedAssemblyProperties)$(_NewLineIndent)DocsOutputPath:"$(QSharpDocsOutputPath)" <_QscCommandPredefinedAssemblyProperties Condition="'$(PerfDataGeneration)'">$(_QscCommandPredefinedAssemblyProperties)$(_NewLineIndent)PerfDataOutputPath:"$(PerfDataOutputPath)" <_QscCommandPredefinedAssemblyProperties Condition="'$(GenerateConcreteIntrinsic)'">$(_QscCommandPredefinedAssemblyProperties)$(_NewLineIndent)GenerateConcreteIntrinsic:$(GenerateConcreteIntrinsic) + <_QscCommandPredefinedAssemblyProperties Condition="'$(QSharpDocsLintingRules') != ''">$(_QscCommandPredefinedAssemblyProperties)$(_NewLineIndent)DocsLintingRules:$(QSharpDocsLintingRules) <_QscCommandAssemblyPropertiesFlag>$(_NewLine)--assembly-properties$(_QscCommandPredefinedAssemblyProperties) <_QscCommandAssemblyPropertiesFlag Condition="'$(QscCommandAssemblyProperties)' != ''">$(_QscCommandAssemblyPropertiesFlag)$(_NewLineIndent)$(QscCommandAssemblyProperties) $(_NewLine)$(AdditionalQscArguments) From 0a7cd422bdbb872b753c2aaa0e3722b3477afb2f Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Wed, 26 May 2021 15:53:41 -0700 Subject: [PATCH 3/6] Fix code style issues. --- .../DocumentationGenerator/DocumentationGeneration.cs | 10 +++++----- .../Linting/IDocumentationLintingRule.cs | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs index 935853fc00..83bfa97962 100644 --- a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs +++ b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs @@ -19,7 +19,7 @@ namespace Microsoft.Quantum.Documentation /// public class DocumentationGeneration : IRewriteStep { - private static readonly ImmutableDictionary lintingRules; + private static readonly ImmutableDictionary LintingRules; static DocumentationGeneration() { @@ -27,7 +27,7 @@ static DocumentationGeneration() rules.Add("require-correct-input-names", (true, new RequireCorrectInputNames())); rules.Add("require-examples", (EnableByDefault: false, new RequireExamplesOnPublicDeclarations())); rules.Add("no-math-in-summary", (true, new NoMathInSummary())); - lintingRules = rules.ToImmutableDictionary(); + LintingRules = rules.ToImmutableDictionary(); } private string docsOutputPath = ""; @@ -130,7 +130,7 @@ public bool Transformation(QsCompilation compilation, out QsCompilation transfor // now. foreach ((var ruleName, var severity) in lintingRulesConfig) { - if (!DocumentationGeneration.lintingRules.ContainsKey(ruleName)) + if (!DocumentationGeneration.LintingRules.ContainsKey(ruleName)) { this.diagnostics.Add(new IRewriteStep.Diagnostic { @@ -143,7 +143,7 @@ public bool Transformation(QsCompilation compilation, out QsCompilation transfor // Actually populate the rules now. var lintingRules = DocumentationGeneration - .lintingRules + .LintingRules .Select( rule => ( Name: rule.Key, @@ -167,7 +167,7 @@ public bool Transformation(QsCompilation compilation, out QsCompilation transfor // If we get down to this point, something went // wrong; the given severity wasn't recognized. (_, var unknown) => throw new Exception( - $"Documentation linting severity for rule {rule.Key} was set to {unknown}, but expected one of \"error\", \"warning\", or \"ignore\"") + $"Documentation linting severity for rule {rule.Key} was set to {unknown}, but expected one of \"error\", \"warning\", or \"ignore\""), }, Rule: rule.Value.Rule)) .Where(rule => rule.Severity != DiagnosticSeverity.Hidden) diff --git a/src/Documentation/DocumentationGenerator/Linting/IDocumentationLintingRule.cs b/src/Documentation/DocumentationGenerator/Linting/IDocumentationLintingRule.cs index 58889e768d..ffca2d7715 100644 --- a/src/Documentation/DocumentationGenerator/Linting/IDocumentationLintingRule.cs +++ b/src/Documentation/DocumentationGenerator/Linting/IDocumentationLintingRule.cs @@ -47,7 +47,7 @@ public IRewriteStep.Diagnostic AsDiagnostic( Range = this.Range, Severity = severity, Stage = IRewriteStep.Stage.Transformation, - Source = this.Source + Source = this.Source, }; } @@ -131,6 +131,7 @@ public IEnumerable OnCallableDeclaration(QsCallable callable, Do { var callableName = $"{callable.FullName.Namespace}.{callable.FullName.Name}"; + // Validate input and type parameter names. var inputDeclarations = callable.ArgumentTuple.ToDictionaryOfDeclarations(); var inputMessages = this.ValidateNames( From 37ab12cac19afd60062aed1205049c5655cbc507 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Wed, 26 May 2021 16:23:18 -0700 Subject: [PATCH 4/6] Fix typo in SDK XML. --- src/QuantumSdk/Sdk/Sdk.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QuantumSdk/Sdk/Sdk.targets b/src/QuantumSdk/Sdk/Sdk.targets index 64f9656f04..3f711843d8 100644 --- a/src/QuantumSdk/Sdk/Sdk.targets +++ b/src/QuantumSdk/Sdk/Sdk.targets @@ -109,7 +109,7 @@ <_QscCommandPredefinedAssemblyProperties Condition="'$(QSharpDocsGeneration)'">$(_QscCommandPredefinedAssemblyProperties)$(_NewLineIndent)DocsOutputPath:"$(QSharpDocsOutputPath)" <_QscCommandPredefinedAssemblyProperties Condition="'$(PerfDataGeneration)'">$(_QscCommandPredefinedAssemblyProperties)$(_NewLineIndent)PerfDataOutputPath:"$(PerfDataOutputPath)" <_QscCommandPredefinedAssemblyProperties Condition="'$(GenerateConcreteIntrinsic)'">$(_QscCommandPredefinedAssemblyProperties)$(_NewLineIndent)GenerateConcreteIntrinsic:$(GenerateConcreteIntrinsic) - <_QscCommandPredefinedAssemblyProperties Condition="'$(QSharpDocsLintingRules') != ''">$(_QscCommandPredefinedAssemblyProperties)$(_NewLineIndent)DocsLintingRules:$(QSharpDocsLintingRules) + <_QscCommandPredefinedAssemblyProperties Condition="'$(QSharpDocsLintingRules)' != ''">$(_QscCommandPredefinedAssemblyProperties)$(_NewLineIndent)DocsLintingRules:$(QSharpDocsLintingRules) <_QscCommandAssemblyPropertiesFlag>$(_NewLine)--assembly-properties$(_QscCommandPredefinedAssemblyProperties) <_QscCommandAssemblyPropertiesFlag Condition="'$(QscCommandAssemblyProperties)' != ''">$(_QscCommandAssemblyPropertiesFlag)$(_NewLineIndent)$(QscCommandAssemblyProperties) $(_NewLine)$(AdditionalQscArguments) From 2a43334f6b7175b001f057d958db4bf9f38807ae Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Wed, 26 May 2021 17:21:00 -0700 Subject: [PATCH 5/6] Ignore null and whitespace in lint rules lists. --- .../DocumentationGenerator/DocumentationGeneration.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs index 83bfa97962..e2578b7db4 100644 --- a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs +++ b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs @@ -117,9 +117,15 @@ public bool Transformation(QsCompilation compilation, out QsCompilation transfor ? config ?? "" : "") .Split(",") + .Where(rule => !string.IsNullOrWhiteSpace(rule)) .Select(rule => { var ruleParts = rule.Split(":", 2); + if (ruleParts.Length != 2) + { + throw new Exception($"Error parsing documentation linting rule specification \"{rule}\"; expected a specification of the form \"severity:rule-name\"."); + } + return (severity: ruleParts[0], ruleName: ruleParts[1]); }) .ToDictionary( @@ -130,7 +136,7 @@ public bool Transformation(QsCompilation compilation, out QsCompilation transfor // now. foreach ((var ruleName, var severity) in lintingRulesConfig) { - if (!DocumentationGeneration.LintingRules.ContainsKey(ruleName)) + if (!LintingRules.ContainsKey(ruleName)) { this.diagnostics.Add(new IRewriteStep.Diagnostic { @@ -142,8 +148,7 @@ public bool Transformation(QsCompilation compilation, out QsCompilation transfor } // Actually populate the rules now. - var lintingRules = DocumentationGeneration - .LintingRules + var lintingRules = LintingRules .Select( rule => ( Name: rule.Key, From 48472816d9c76bbdf59bf48eb7af97802d25d787 Mon Sep 17 00:00:00 2001 From: Christopher Granade Date: Wed, 26 May 2021 17:59:00 -0700 Subject: [PATCH 6/6] Actually use the correct assembly constant. --- .../DocumentationGenerator/DocumentationGeneration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs index e2578b7db4..f9c498c2e0 100644 --- a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs +++ b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs @@ -113,7 +113,7 @@ public bool Transformation(QsCompilation compilation, out QsCompilation transfor // or "error:", indicating the level of severity for each. var lintingRulesConfig = ( this.AssemblyConstants - .TryGetValue(QsAssemblyConstants.DocsPackageId, out var config) + .TryGetValue(QsAssemblyConstants.DocsLintingRules, out var config) ? config ?? "" : "") .Split(",")