Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
96 changes: 74 additions & 22 deletions src/Documentation/DocumentationGenerator/DocumentationWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -18,6 +20,12 @@ namespace Microsoft.Quantum.Documentation
/// </summary>
public class DocumentationWriter
{
/// <summary>
/// An event that is raised on diagnostics about documentation
/// writing (e.g., if an I/O problem prevents writing to disk).
/// </summary>
public event Action<IRewriteStep.Diagnostic>? OnDiagnostic;

/// <summary>
/// Path to which output documentation files should be written.
/// </summary>
Expand All @@ -34,6 +42,36 @@ public class DocumentationWriter

private readonly string PackageLink;

private async Task TryWithExceptionsAsDiagnostics(string description, Func<Task> 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
)
);
}

/// <summary>
/// Initializes a new instance of the
/// <see cref="DocumentationWriter"/> class.
Expand All @@ -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";
}

/// <summary>
Expand Down Expand Up @@ -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}
Expand All @@ -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
);
}

Expand All @@ -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<string, object>
Expand All @@ -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()}
```

"
Expand All @@ -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
);
}
Expand All @@ -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",
_ => "<unknown>"
_ => "<unknown>",
};
var title = $@"{callable.FullName.Name.Value} {kind}";
var header = new Dictionary<string, object>
Expand All @@ -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#
Expand Down Expand Up @@ -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
);
}
Expand Down
16 changes: 15 additions & 1 deletion src/Documentation/DocumentationGenerator/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@ internal static Dictionary<string, ResolvedType> ToDictionaryOfDeclarations(this
ResolvedTypeKind.Tags.Range => "Range",
ResolvedTypeKind.Tags.String => "String",
ResolvedTypeKind.Tags.UnitType => "Unit",
_ => "__invalid__",
ResolvedTypeKind.Tags.InvalidType => "__invalid__",
_ => $"__invalid<{type.Resolution.ToString()}>__",
},
};

Expand All @@ -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<string>.Empty,
comments: QsComments.Empty
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class ProcessDocComments
public class TransformationState
{ }

private readonly DocumentationWriter? writer;
internal readonly DocumentationWriter? Writer;

/// <summary>
/// Initializes a new instance of the <see cref="ProcessDocComments"/> class.
Expand All @@ -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
Expand Down
10 changes: 9 additions & 1 deletion src/QsCompiler/CommandLineTool/Commands/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,14 @@ internal static bool IncorporateResponseFiles(BuildOptions options, out BuildOpt
/// </summary>
private static IEnumerable<string> SplitCommandLineArguments(string commandLine)
{
string TrimQuotes(string s) =>
s.StartsWith('"') && s.EndsWith('"')
? s.Substring(1, s.Length - 2)
: s;

var parmChars = commandLine?.ToCharArray() ?? Array.Empty<char>();
var inQuote = false;

for (int index = 0; index < parmChars.Length; index++)
{
var precededByBackslash = index > 0 && parmChars[index - 1] == '\\';
Expand All @@ -131,9 +137,10 @@ private static IEnumerable<string> SplitCommandLineArguments(string commandLine)
parmChars[index] = '\n';
}
}

return new string(parmChars)
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(arg => arg.Trim('"'));
.Select(arg => TrimQuotes(arg));
}

/// <summary>
Expand All @@ -148,6 +155,7 @@ private static BuildOptions FromResponseFiles(IEnumerable<string> 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<BuildOptions>(args);
return parsed.MapResult(
Expand Down
3 changes: 2 additions & 1 deletion src/QsCompiler/CommandLineTool/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ internal bool ParseAssemblyProperties(out Dictionary<string, string> parsed)
parsed = new Dictionary<string, string>();
foreach (var keyValue in this.AdditionalAssemblyProperties ?? Array.Empty<string>())
{
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;
}
Expand Down
8 changes: 4 additions & 4 deletions src/QuantumSdk/Sdk/Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
<!-- For the package ID that gets displayed in documentation, we default
to the actual package ID if it's set, but allow overriding with
QsharpDocsPackageId. -->
<_QscDocsPackageeId Condition="'$(PackageId)' != ''">$(PackageId)</_QscDocsPackageeId>
<_QscDocsPackageeId Condition="'$(QsharpDocsPackageId)' != ''">$(QsharpDocsPackageId)</_QscDocsPackageeId>
<_QscDocsPackageId Condition="'$(PackageId)' != ''">$(PackageId)</_QscDocsPackageId>
<_QscDocsPackageId Condition="'$(QsharpDocsPackageId)' != ''">$(QsharpDocsPackageId)</_QscDocsPackageId>
<_QscCommandIsExecutableFlag Condition="'$(ResolvedQsharpOutputType)' == 'QsharpExe'">--build-exe</_QscCommandIsExecutableFlag>
<_QscCommandOutputFlag>--output "$(GeneratedFilesOutputPath)"</_QscCommandOutputFlag>
<_QscCommandInputFlag Condition="@(QsharpCompile->Count()) &gt; 0">--input "@(QsharpCompile,'" "')"</_QscCommandInputFlag>
Expand All @@ -85,8 +85,8 @@
<_QscCommandPredefinedAssemblyProperties Condition="'$(DefaultSimulator)' != ''">$(_QscCommandPredefinedAssemblyProperties) DefaultSimulator:$(DefaultSimulator)</_QscCommandPredefinedAssemblyProperties>
<_QscCommandPredefinedAssemblyProperties Condition="'$(ExecutionTarget)' != ''">$(_QscCommandPredefinedAssemblyProperties) ExecutionTarget:$(ExecutionTarget)</_QscCommandPredefinedAssemblyProperties>
<_QscCommandPredefinedAssemblyProperties Condition="'$(ExposeReferencesViaTestNames)'">$(_QscCommandPredefinedAssemblyProperties) ExposeReferencesViaTestNames:true</_QscCommandPredefinedAssemblyProperties>
<_QscCommandPredefinedAssemblyProperties Condition="'$(_QscDocsPackageeId)' != ''">$(_QscCommandPredefinedAssemblyProperties) DocsPackageId:$(_QscDocsPackageeId)</_QscCommandPredefinedAssemblyProperties>
<_QscCommandPredefinedAssemblyProperties Condition="'$(QsharpDocsGeneration)'">$(_QscCommandPredefinedAssemblyProperties) DocsOutputPath:$(QsharpDocsOutputPath)</_QscCommandPredefinedAssemblyProperties>
<_QscCommandPredefinedAssemblyProperties Condition="'$(_QscDocsPackageId)' != ''">$(_QscCommandPredefinedAssemblyProperties) DocsPackageId:$(_QscDocsPackageeId)</_QscCommandPredefinedAssemblyProperties>
<_QscCommandPredefinedAssemblyProperties Condition="'$(QsharpDocsGeneration)'">$(_QscCommandPredefinedAssemblyProperties) DocsOutputPath:"$(QsharpDocsOutputPath)"</_QscCommandPredefinedAssemblyProperties>
<_QscCommandAssemblyPropertiesFlag>--assembly-properties $(_QscCommandPredefinedAssemblyProperties) $(QscCommandAssemblyProperties)</_QscCommandAssemblyPropertiesFlag>
<_QscPackageLoadFallbackFoldersFlag Condition="@(ResolvedPackageLoadFallbackFolders->Count()) &gt; 0">--package-load-fallback-folders "@(ResolvedPackageLoadFallbackFolders,'" "')"</_QscPackageLoadFallbackFoldersFlag>
<_QscCommandArgs>--proj "$(PathCompatibleAssemblyName)" $(_QscCommandIsExecutableFlag) $(_QscCommandInputFlag) $(_QscCommandOutputFlag) $(_QscCommandReferencesFlag) $(_QscCommandLoadFlag) $(_QscCommandRuntimeFlag) $(_QscCommandTargetDecompositionsFlag) $(_QscPackageLoadFallbackFoldersFlag) $(_QscCommandTestNamesFlag) $(_QscCommandAssemblyPropertiesFlag) $(AdditionalQscArguments)</_QscCommandArgs>
Expand Down