diff --git a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs
index 536e156242..cfdcf70944 100644
--- a/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs
+++ b/src/Documentation/DocumentationGenerator/DocumentationGeneration.cs
@@ -70,14 +70,24 @@ public bool PreconditionVerification(QsCompilation compilation)
public bool Transformation(QsCompilation compilation, out QsCompilation transformed)
{
- transformed = new ProcessDocComments(
+ var docProcessor = new ProcessDocComments(
this.AssemblyConstants.TryGetValue("DocsOutputPath", out var path)
? path
: null,
this.AssemblyConstants.TryGetValue("DocsPackageId", out var packageName)
? packageName
: null
- ).OnCompilation(compilation);
+ );
+
+ if (docProcessor.Writer != null)
+ {
+ docProcessor.Writer.OnDiagnostic += diagnostic =>
+ {
+ this.diagnostics.Add(diagnostic);
+ };
+ }
+
+ transformed = docProcessor.OnCompilation(compilation);
return true;
}
diff --git a/src/Documentation/DocumentationGenerator/DocumentationWriter.cs b/src/Documentation/DocumentationGenerator/DocumentationWriter.cs
index ed6edb2ab5..4bba29230f 100644
--- a/src/Documentation/DocumentationGenerator/DocumentationWriter.cs
+++ b/src/Documentation/DocumentationGenerator/DocumentationWriter.cs
@@ -4,8 +4,10 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Threading.Tasks;
using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.Quantum.QsCompiler;
using Microsoft.Quantum.QsCompiler.Documentation;
using Microsoft.Quantum.QsCompiler.SyntaxTree;
@@ -18,6 +20,12 @@ namespace Microsoft.Quantum.Documentation
///
public class DocumentationWriter
{
+ ///
+ /// An event that is raised on diagnostics about documentation
+ /// writing (e.g., if an I/O problem prevents writing to disk).
+ ///
+ public event Action? OnDiagnostic;
+
///
/// Path to which output documentation files should be written.
///
@@ -34,6 +42,36 @@ public class DocumentationWriter
private readonly string PackageLink;
+ private async Task TryWithExceptionsAsDiagnostics(string description, Func action, DiagnosticSeverity severity = DiagnosticSeverity.Warning)
+ {
+ try
+ {
+ await action();
+ }
+ catch (Exception ex)
+ {
+ this.OnDiagnostic?.Invoke(new IRewriteStep.Diagnostic
+ {
+ Severity = severity,
+ Message = $"Exception raised when {description}:\n{ex.Message}",
+ Range = null,
+ Source = null,
+ Stage = IRewriteStep.Stage.Transformation,
+ });
+ }
+ }
+
+ private async Task WriteAllTextAsync(string filename, string contents)
+ {
+ await this.TryWithExceptionsAsDiagnostics(
+ $"writing output to {filename}",
+ async () => await File.WriteAllTextAsync(
+ Path.Join(this.OutputPath, $"{filename.ToLowerInvariant()}.md"),
+ contents
+ )
+ );
+ }
+
///
/// Initializes a new instance of the
/// class.
@@ -52,14 +90,28 @@ public DocumentationWriter(string outputPath, string? packageName)
this.packageName = packageName;
// If the output path is not null, make sure the directory exists.
- if (outputPath != null && !Directory.Exists(outputPath))
+ if (outputPath != null)
{
- Directory.CreateDirectory(outputPath);
+ this.OnDiagnostic?.Invoke(new IRewriteStep.Diagnostic
+ {
+ Severity = CodeAnalysis.DiagnosticSeverity.Info,
+ Message = $"Writing documentation output to: {outputPath}...",
+ Range = null,
+ Source = null,
+ Stage = IRewriteStep.Stage.Transformation,
+ });
+ if (!Directory.Exists(outputPath))
+ {
+ this.TryWithExceptionsAsDiagnostics(
+ "creating directory",
+ async () => Directory.CreateDirectory(outputPath)
+ ).Wait();
+ }
}
- PackageLink = PackageName == null
- ? ""
- : $"\nPackage: [{PackageName}](https://nuget.org/packages/{PackageName})\n";
+ this.PackageLink = this.PackageName == null
+ ? string.Empty
+ : $"\nPackage: [{this.PackageName}](https://nuget.org/packages/{this.PackageName})\n";
}
///
@@ -90,7 +142,7 @@ public async Task WriteOutput(QsNamespace ns, DocComment docComment)
// Q# metadata
["qsharp.kind"] = "namespace",
["qsharp.name"] = name,
- ["qsharp.summary"] = docComment.Summary
+ ["qsharp.summary"] = docComment.Summary,
};
var document = $@"
# {title}
@@ -102,9 +154,8 @@ public async Task WriteOutput(QsNamespace ns, DocComment docComment)
.WithYamlHeader(header);
// Open a file to write the new doc to.
- await File.WriteAllTextAsync(
- Path.Join(this.OutputPath, $"{name.ToLowerInvariant()}.md"),
- document
+ await this.WriteAllTextAsync(
+ name, document
);
}
@@ -121,6 +172,7 @@ await File.WriteAllTextAsync(
public async Task WriteOutput(QsCustomType type, DocComment docComment)
{
var namedItemDeclarations = type.TypeItems.ToDictionaryOfDeclarations();
+
// Make a new Markdown document for the type declaration.
var title = $"{type.FullName.Name.Value} user defined type";
var header = new Dictionary
@@ -137,18 +189,18 @@ public async Task WriteOutput(QsCustomType type, DocComment docComment)
["qsharp.kind"] = "udt",
["qsharp.namespace"] = type.FullName.Namespace.Value,
["qsharp.name"] = type.FullName.Name.Value,
- ["qsharp.summary"] = docComment.Summary
+ ["qsharp.summary"] = docComment.Summary,
};
var document = $@"
+# {title}
+
Namespace: [{type.FullName.Namespace.Value}](xref:{type.FullName.Namespace.Value})
{this.PackageLink}
-# {title}
-
{docComment.Summary}
```Q#
-{type.ToSyntax()}
+{type.WithoutDocumentationAndComments().ToSyntax()}
```
"
@@ -174,8 +226,8 @@ public async Task WriteOutput(QsCustomType type, DocComment docComment)
.WithYamlHeader(header);
// Open a file to write the new doc to.
- await File.WriteAllTextAsync(
- Path.Join(this.OutputPath, $"{type.FullName.Namespace.Value.ToLowerInvariant()}.{type.FullName.Name.Value.ToLowerInvariant()}.md"),
+ await this.WriteAllTextAsync(
+ $"{type.FullName.Namespace.Value}.{type.FullName.Name.Value}.md",
document
);
}
@@ -200,7 +252,7 @@ public async Task WriteOutput(QsCallable callable, DocComment docComment)
QsCallableKind.Tags.Function => "function",
QsCallableKind.Tags.Operation => "operation",
QsCallableKind.Tags.TypeConstructor => "type constructor",
- _ => ""
+ _ => "",
};
var title = $@"{callable.FullName.Name.Value} {kind}";
var header = new Dictionary
@@ -217,14 +269,14 @@ public async Task WriteOutput(QsCallable callable, DocComment docComment)
["qsharp.kind"] = kind,
["qsharp.namespace"] = callable.FullName.Namespace.Value,
["qsharp.name"] = callable.FullName.Name.Value,
- ["qsharp.summary"] = docComment.Summary
+ ["qsharp.summary"] = docComment.Summary,
};
var document = $@"
+# {title}
+
Namespace: [{callable.FullName.Namespace.Value}](xref:{callable.FullName.Namespace.Value})
{this.PackageLink}
-# {title}
-
{docComment.Summary}
```Q#
@@ -260,8 +312,8 @@ public async Task WriteOutput(QsCallable callable, DocComment docComment)
.WithYamlHeader(header);
// Open a file to write the new doc to.
- await File.WriteAllTextAsync(
- Path.Join(this.OutputPath, $"{callable.FullName.Namespace.Value.ToLowerInvariant()}.{callable.FullName.Name.Value.ToLowerInvariant()}.md"),
+ await this.WriteAllTextAsync(
+ $"{callable.FullName.Namespace.Value}.{callable.FullName.Name.Value}.md",
document
);
}
diff --git a/src/Documentation/DocumentationGenerator/Extensions.cs b/src/Documentation/DocumentationGenerator/Extensions.cs
index 7d726cf9c8..ba768ed96c 100644
--- a/src/Documentation/DocumentationGenerator/Extensions.cs
+++ b/src/Documentation/DocumentationGenerator/Extensions.cs
@@ -300,7 +300,8 @@ internal static Dictionary ToDictionaryOfDeclarations(this
ResolvedTypeKind.Tags.Range => "Range",
ResolvedTypeKind.Tags.String => "String",
ResolvedTypeKind.Tags.UnitType => "Unit",
- _ => "__invalid__",
+ ResolvedTypeKind.Tags.InvalidType => "__invalid__",
+ _ => $"__invalid<{type.Resolution.ToString()}>__",
},
};
@@ -320,6 +321,19 @@ internal static bool IsInCompilationUnit(this QsCallable callable) =>
internal static bool IsInCompilationUnit(this QsCustomType type) =>
type.SourceFile.Value.EndsWith(".qs");
+
+ internal static QsCustomType WithoutDocumentationAndComments(this QsCustomType type) =>
+ new QsCustomType(
+ fullName: type.FullName,
+ attributes: type.Attributes,
+ modifiers: type.Modifiers,
+ sourceFile: type.SourceFile,
+ location: type.Location,
+ type: type.Type,
+ typeItems: type.TypeItems,
+ documentation: ImmutableArray.Empty,
+ comments: QsComments.Empty
+ );
}
}
diff --git a/src/Documentation/DocumentationGenerator/ProcessDocComments.cs b/src/Documentation/DocumentationGenerator/ProcessDocComments.cs
index 891aadb190..a287bbd5c6 100644
--- a/src/Documentation/DocumentationGenerator/ProcessDocComments.cs
+++ b/src/Documentation/DocumentationGenerator/ProcessDocComments.cs
@@ -32,7 +32,7 @@ public class ProcessDocComments
public class TransformationState
{ }
- private readonly DocumentationWriter? writer;
+ internal readonly DocumentationWriter? Writer;
///
/// Initializes a new instance of the class.
@@ -51,12 +51,12 @@ public ProcessDocComments(
)
: base(new TransformationState(), TransformationOptions.Disabled)
{
- this.writer = outputPath == null
+ this.Writer = outputPath == null
? null
: new DocumentationWriter(outputPath, packageName);
// We provide our own custom namespace transformation, and expression kind transformation.
- this.Namespaces = new ProcessDocComments.NamespaceTransformation(this, this.writer);
+ this.Namespaces = new ProcessDocComments.NamespaceTransformation(this, this.Writer);
}
private class NamespaceTransformation
diff --git a/src/QsCompiler/CommandLineTool/Commands/Build.cs b/src/QsCompiler/CommandLineTool/Commands/Build.cs
index c91c1c5727..56b24d76c2 100644
--- a/src/QsCompiler/CommandLineTool/Commands/Build.cs
+++ b/src/QsCompiler/CommandLineTool/Commands/Build.cs
@@ -112,8 +112,14 @@ internal static bool IncorporateResponseFiles(BuildOptions options, out BuildOpt
///
private static IEnumerable SplitCommandLineArguments(string commandLine)
{
+ string TrimQuotes(string s) =>
+ s.StartsWith('"') && s.EndsWith('"')
+ ? s.Substring(1, s.Length - 2)
+ : s;
+
var parmChars = commandLine?.ToCharArray() ?? Array.Empty();
var inQuote = false;
+
for (int index = 0; index < parmChars.Length; index++)
{
var precededByBackslash = index > 0 && parmChars[index - 1] == '\\';
@@ -131,9 +137,10 @@ private static IEnumerable SplitCommandLineArguments(string commandLine)
parmChars[index] = '\n';
}
}
+
return new string(parmChars)
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
- .Select(arg => arg.Trim('"'));
+ .Select(arg => TrimQuotes(arg));
}
///
@@ -148,6 +155,7 @@ private static BuildOptions FromResponseFiles(IEnumerable responseFiles)
throw new ArgumentNullException(nameof(responseFiles));
}
var commandLine = string.Join(" ", responseFiles.Select(File.ReadAllText));
+ System.Diagnostics.Debugger.Launch();
var args = SplitCommandLineArguments(commandLine);
var parsed = Parser.Default.ParseArguments(args);
return parsed.MapResult(
diff --git a/src/QsCompiler/CommandLineTool/Options.cs b/src/QsCompiler/CommandLineTool/Options.cs
index de79138fe8..3d3402ed1c 100644
--- a/src/QsCompiler/CommandLineTool/Options.cs
+++ b/src/QsCompiler/CommandLineTool/Options.cs
@@ -90,7 +90,8 @@ internal bool ParseAssemblyProperties(out Dictionary parsed)
parsed = new Dictionary();
foreach (var keyValue in this.AdditionalAssemblyProperties ?? Array.Empty())
{
- var pieces = keyValue?.Split(":");
+ // NB: We use `count: 2` here to ensure that assembly constants can contain colons.
+ var pieces = keyValue?.Split(":", count: 2);
var valid = pieces != null && pieces.Length == 2;
success = valid && parsed.TryAdd(pieces[0].Trim().Trim('"'), pieces[1].Trim().Trim('"')) && success;
}
diff --git a/src/QuantumSdk/Sdk/Sdk.targets b/src/QuantumSdk/Sdk/Sdk.targets
index defcb6dafd..242f27bf0d 100644
--- a/src/QuantumSdk/Sdk/Sdk.targets
+++ b/src/QuantumSdk/Sdk/Sdk.targets
@@ -71,8 +71,8 @@
- <_QscDocsPackageeId Condition="'$(PackageId)' != ''">$(PackageId)
- <_QscDocsPackageeId Condition="'$(QsharpDocsPackageId)' != ''">$(QsharpDocsPackageId)
+ <_QscDocsPackageId Condition="'$(PackageId)' != ''">$(PackageId)
+ <_QscDocsPackageId Condition="'$(QsharpDocsPackageId)' != ''">$(QsharpDocsPackageId)
<_QscCommandIsExecutableFlag Condition="'$(ResolvedQsharpOutputType)' == 'QsharpExe'">--build-exe
<_QscCommandOutputFlag>--output "$(GeneratedFilesOutputPath)"
<_QscCommandInputFlag Condition="@(QsharpCompile->Count()) > 0">--input "@(QsharpCompile,'" "')"
@@ -85,8 +85,8 @@
<_QscCommandPredefinedAssemblyProperties Condition="'$(DefaultSimulator)' != ''">$(_QscCommandPredefinedAssemblyProperties) DefaultSimulator:$(DefaultSimulator)
<_QscCommandPredefinedAssemblyProperties Condition="'$(ExecutionTarget)' != ''">$(_QscCommandPredefinedAssemblyProperties) ExecutionTarget:$(ExecutionTarget)
<_QscCommandPredefinedAssemblyProperties Condition="'$(ExposeReferencesViaTestNames)'">$(_QscCommandPredefinedAssemblyProperties) ExposeReferencesViaTestNames:true
- <_QscCommandPredefinedAssemblyProperties Condition="'$(_QscDocsPackageeId)' != ''">$(_QscCommandPredefinedAssemblyProperties) DocsPackageId:$(_QscDocsPackageeId)
- <_QscCommandPredefinedAssemblyProperties Condition="'$(QsharpDocsGeneration)'">$(_QscCommandPredefinedAssemblyProperties) DocsOutputPath:$(QsharpDocsOutputPath)
+ <_QscCommandPredefinedAssemblyProperties Condition="'$(_QscDocsPackageId)' != ''">$(_QscCommandPredefinedAssemblyProperties) DocsPackageId:$(_QscDocsPackageeId)
+ <_QscCommandPredefinedAssemblyProperties Condition="'$(QsharpDocsGeneration)'">$(_QscCommandPredefinedAssemblyProperties) DocsOutputPath:"$(QsharpDocsOutputPath)"
<_QscCommandAssemblyPropertiesFlag>--assembly-properties $(_QscCommandPredefinedAssemblyProperties) $(QscCommandAssemblyProperties)
<_QscPackageLoadFallbackFoldersFlag Condition="@(ResolvedPackageLoadFallbackFolders->Count()) > 0">--package-load-fallback-folders "@(ResolvedPackageLoadFallbackFolders,'" "')"
<_QscCommandArgs>--proj "$(PathCompatibleAssemblyName)" $(_QscCommandIsExecutableFlag) $(_QscCommandInputFlag) $(_QscCommandOutputFlag) $(_QscCommandReferencesFlag) $(_QscCommandLoadFlag) $(_QscCommandRuntimeFlag) $(_QscCommandTargetDecompositionsFlag) $(_QscPackageLoadFallbackFoldersFlag) $(_QscCommandTestNamesFlag) $(_QscCommandAssemblyPropertiesFlag) $(AdditionalQscArguments)