Skip to content
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ _pkginfo.txt
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# C# Dev Kit cache files
*.lscache

# Others
ClientBin/
Expand Down
70 changes: 33 additions & 37 deletions src/PAModel/CanvasDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@
namespace Microsoft.PowerPlatform.Formulas.Tools;

/// <summary>
/// Represents a PowerApps document. This can be save/loaded from a MsApp or Source representation.
/// This is a full in-memory representation of the msapp file.
/// Represents a PowerApps document. This can be save/loaded from a MsApp or Source representation.
/// This is a full in-memory representation of the msapp file.
/// </summary>
public class CanvasDocument
{
/// <summary>
/// Current source format version.
/// Current source format version.
/// </summary>
public static Version CurrentSourceVersion => SourceSerializer.CurrentSourceVersion;

// Rules for CanvasDocument
// - Save/Load must faithfully roundtrip an msapp exactly.
// - this is an in-memory representation - so it must parse/shard everything on load.
// - Save should not mutate any state.
// - Save/Load must faithfully roundtrip an msapp exactly.
// - this is an in-memory representation - so it must parse/shard everything on load.
// - Save should not mutate any state.

// Track all unknown "files". Ensures round-tripping isn't lossy.
// Track all unknown "files". Ensures round-tripping isn't lossy.
// Only contains files of FileKind.Unknown
internal Dictionary<FilePath, FileEntry> _unknownFiles = new();

Expand All @@ -42,7 +42,7 @@ public class CanvasDocument
internal EditorStateStore _editorStateStore;
internal TemplateStore _templateStore;

// Various data sources
// Various data sources
// This is references\dataSources.json
// Also includes entries for DataSources made from a DataComponent
// Key is parent entity name (datasource name for non cds data sources)
Expand Down Expand Up @@ -76,15 +76,15 @@ public class CanvasDocument
internal IDictionary<string, LocalDatabaseReferenceJson> _dataSourceReferences;

// Extracted from _properties.LibraryDependencies
// Must preserve server ordering.
// Must preserve server ordering.
internal ComponentDependencyInfo[] _libraryReferences;

internal FileEntry _logoFile;

// Save for roundtripping.
internal Entropy _entropy = new();

// Checksum from existing msapp.
// Checksum from existing msapp.
internal ChecksumJson _checksum;

// Track all asset files, key is file name
Expand All @@ -102,7 +102,7 @@ public class CanvasDocument
#region Save/Load

/// <summary>
/// Load an .msapp file for a Canvas Document.
/// Load an .msapp file for a Canvas Document.
/// </summary>
/// <param name="fullPathToMsApp">path to an .msapp file</param>
/// <returns>A tuple of the document and errors and warnings. If there are errors, the document is null. </returns>
Expand Down Expand Up @@ -178,32 +178,28 @@ public ErrorContainer SaveToSources(string pathToSourceDirectory, string verifyO
var errors = new ErrorContainer();
Wrapper(() => SourceSerializer.SaveAsSource(this, pathToSourceDirectory, errors), errors);


// Test that we can repack
if (!errors.HasErrors && verifyOriginalPath != null)
{
(var msApp2, var errors2) = LoadFromSources(pathToSourceDirectory);
var (msApp2, errors2) = LoadFromSources(pathToSourceDirectory);
if (errors2.HasErrors)
{
errors2.PostUnpackValidationFailed();
return errors2;
}

using (var temp = new TempFile())
using var temp = new TempFile();
errors2 = msApp2.SaveToMsAppValidation(temp.FullPath);
if (errors2.HasErrors)
{
errors2 = msApp2.SaveToMsAppValidation(temp.FullPath);
if (errors2.HasErrors)
{
errors2.PostUnpackValidationFailed();
return errors2;
}
errors2.PostUnpackValidationFailed();
return errors2;
}

var ok = MsAppTest.Compare(verifyOriginalPath, temp.FullPath, TextWriter.Null, errors2);
if (!ok)
{
errors2.PostUnpackValidationFailed();
return errors2;
}
if (!MsAppTest.Compare(verifyOriginalPath, temp.FullPath, errors2))
{
errors2.PostUnpackValidationFailed();
return errors2;
}
}

Expand All @@ -218,7 +214,7 @@ public static (CanvasDocument, ErrorContainer) MakeFromSources(string appName, s

#endregion

// Wrapper to ensure consistent invariants between loading a document, exception handling, and returning errors.
// Wrapper to ensure consistent invariants between loading a document, exception handling, and returning errors.
private static CanvasDocument Wrapper(Func<CanvasDocument> worker, ErrorContainer errors)
{
try
Expand Down Expand Up @@ -320,7 +316,7 @@ internal CanvasDocument(CanvasDocument other)
_localAssetInfoJson = other._localAssetInfoJson.JsonClone();
}

// iOrder is used to preserve ordering value for round-tripping.
// iOrder is used to preserve ordering value for round-tripping.
internal void AddDataSourceForLoad(DataSourceEntry ds, int? order = null)
{
// Key is parent entity name
Expand Down Expand Up @@ -376,7 +372,7 @@ internal void ApplyAfterMsAppLoadTransforms(ErrorContainer errors)
var componentInstanceTransform = new ComponentInstanceTransform(errors);
var componentDefTransform = new ComponentDefinitionTransform(errors, _templateStore, componentInstanceTransform);

// Transform component definitions and populate template set of component instances that need updates
// Transform component definitions and populate template set of component instances that need updates
foreach (var ctrl in _components)
{
AddComponentDefaults(ctrl.Value, templateDefaults);
Expand Down Expand Up @@ -433,7 +429,7 @@ internal void ApplyBeforeMsAppWriteTransforms(ErrorContainer errors)
var componentInstanceTransform = new ComponentInstanceTransform(errors);
var componentDefTransform = new ComponentDefinitionTransform(errors, _templateStore, componentInstanceTransform);

// Transform component definitions and populate template set of component instances that need updates
// Transform component definitions and populate template set of component instances that need updates
foreach (var ctrl in _components)
{
componentDefTransform.BeforeWrite(ctrl.Value);
Expand Down Expand Up @@ -466,15 +462,16 @@ private void AddComponentDefaults(BlockNode topParent, Dictionary<string, Contro
}


// Called after loading. This will check internal fields and fill in consistency data.
// Called after loading. This will check internal fields and fill in consistency data.
internal void OnLoadComplete(ErrorContainer errors)
{
// Do integrity checks.
// Do integrity checks.
if (_header == null)
{
errors.FormatNotSupported("Missing header file");
throw new DocumentException();
}

if (_properties == null)
{
errors.FormatNotSupported("Missing properties file");
Expand All @@ -488,7 +485,6 @@ internal void OnLoadComplete(ErrorContainer errors)
uniqueVisitor.Visit(control);
}


// Integrity checks.
foreach (var kv in _connections.NullOk())
{
Expand All @@ -502,7 +498,7 @@ internal void OnLoadComplete(ErrorContainer errors)
}
}

// Get ComponentIds for components we've imported.
// Get ComponentIds for components we've imported.
internal HashSet<string> GetImportedComponents()
{
var set = new HashSet<string>();
Expand Down Expand Up @@ -679,10 +675,10 @@ private void RestoreAssetFilePaths(ErrorContainer errors)
}
}

// Helper for traversing and ensuring unique control names.
// Helper for traversing and ensuring unique control names.
internal class UniqueControlNameVisitor
{
// Control names are case sensitive.
// Control names are case sensitive.
private readonly Dictionary<string, SourceLocation?> _names = new(StringComparer.Ordinal);
private readonly ErrorContainer _errors;

Expand All @@ -693,7 +689,7 @@ public UniqueControlNameVisitor(ErrorContainer errors)

public void Visit(BlockNode node)
{
// Ignore test templates here.
// Ignore test templates here.
// Test templates have control-like syntax, but allowed to repeat names:
// Step4 As TestStep:
if (AppTestTransform.IsTestSuite(node.Name.Kind.TypeName))
Expand Down
21 changes: 7 additions & 14 deletions src/PAModel/Checksum/ChecksumMaker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ internal static byte[] ChecksumFile<T>(string filename, byte[] bytes)
if (filename.EndsWith(".json", StringComparison.OrdinalIgnoreCase) ||
filename.EndsWith(".sarif", StringComparison.OrdinalIgnoreCase))
{
var key = ChecksumJsonFile<T>(filename, bytes);
return key;
return ChecksumJsonFile<T>(filename, bytes);
}
else
{
Expand Down Expand Up @@ -164,20 +163,14 @@ private static byte[] ChecksumJsonFile<T>(string filename, byte[] bytes)
where T : IHashMaker, new()
{
var s = new ReadOnlyMemory<byte>(bytes);
using (var doc = JsonDocument.Parse(s))
{
var je = doc.RootElement;

var ctx = new Context { Filename = filename };
using var doc = JsonDocument.Parse(s);
var je = doc.RootElement;

using (var hash = new T())
{
ChecksumJson(ctx, hash, je);
var ctx = new Context { Filename = filename };

var key = hash.GetFinalValue();
return key;
}
}
using var hash = new T();
ChecksumJson(ctx, hash, je);
return hash.GetFinalValue();
}

internal static string ChecksumToString(byte[] bytes)
Expand Down
6 changes: 3 additions & 3 deletions src/PAModel/Entropy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ internal class PropertyEntropy
public bool? WasLocalDatabaseReferencesEmpty { get; set; }

/// <summary>
/// Tracks whether TestStepsMetadata is empty or not.
/// Indicates when a Test has missing steps metadata property; which happens UX creates a new test, but the user doesn't add a step.
/// </summary>
public bool? DoesTestStepsMetadataExist { get; set; }
public HashSet<string> AppTestsMissingStepsMetadata { get; set; } = new HashSet<string>(StringComparer.Ordinal);

// Key is connection id, value is connection instance id
public Dictionary<string, string> LocalConnectionIDReferences { get; set; } = new Dictionary<string, string>(StringComparer.Ordinal);
public Dictionary<string, string> LocalConnectionIDReferences { get; set; } = new Dictionary<string, string>();

// Key is test rule, value is test screen id without Screen name
public Dictionary<string, string> RuleScreenIdWithoutScreen { get; set; } = new Dictionary<string, string>(StringComparer.Ordinal);
Expand Down
Loading
Loading