public static void DoWithDiagnosticAnnotations(BicepFile bicepFile, IEnumerable <IDiagnostic> diagnostics, Action <IEnumerable <IDiagnostic> > action)
 {
     using (new AssertionScope().WithVisualDiagnostics(bicepFile, diagnostics))
     {
         action(diagnostics);
     }
 }
Пример #2
0
        private static CommandOrCodeAction?DisableDiagnostic(DocumentUri documentUri,
                                                             DiagnosticCode diagnosticCode,
                                                             BicepFile bicepFile,
                                                             TextSpan span,
                                                             ImmutableArray <int> lineStarts)
        {
            if (diagnosticCode.String is null)
            {
                return(null);
            }

            var disabledDiagnosticsCache = bicepFile.DisabledDiagnosticsCache;

            (int diagnosticLine, _) = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, span.Position);

            TextEdit?textEdit;
            int      previousLine = diagnosticLine - 1;

            if (disabledDiagnosticsCache.TryGetDisabledNextLineDirective(previousLine) is { } disableNextLineDirectiveEndPositionAndCodes)
            {
                textEdit = new TextEdit
                {
                    Range   = new Range(previousLine, disableNextLineDirectiveEndPositionAndCodes.endPosition, previousLine, disableNextLineDirectiveEndPositionAndCodes.endPosition),
                    NewText = ' ' + diagnosticCode.String
                };
            }
Пример #3
0
        private static bool RewriteSyntax(IResourceTypeProvider resourceTypeProvider, Workspace workspace, Uri entryUri, Func <SemanticModel, SyntaxRewriteVisitor> rewriteVisitorBuilder)
        {
            var hasChanges         = false;
            var fileResolver       = new FileResolver();
            var dispatcher         = new ModuleDispatcher(new DefaultModuleRegistryProvider(fileResolver));
            var sourceFileGrouping = SourceFileGroupingBuilder.Build(fileResolver, dispatcher, workspace, entryUri);
            var compilation        = new Compilation(resourceTypeProvider, sourceFileGrouping);

            foreach (var(fileUri, sourceFile) in workspace.GetActiveSourceFilesByUri())
            {
                if (sourceFile is not BicepFile bicepFile)
                {
                    throw new InvalidOperationException("Expected a bicep source file.");
                }

                var newProgramSyntax = rewriteVisitorBuilder(compilation.GetSemanticModel(bicepFile)).Rewrite(bicepFile.ProgramSyntax);

                if (!object.ReferenceEquals(bicepFile.ProgramSyntax, newProgramSyntax))
                {
                    hasChanges = true;
                    var newFile = new BicepFile(fileUri, ImmutableArray <int> .Empty, newProgramSyntax);
                    workspace.UpsertSourceFile(newFile);

                    sourceFileGrouping = SourceFileGroupingBuilder.Build(fileResolver, dispatcher, workspace, entryUri);
                    compilation        = new Compilation(resourceTypeProvider, sourceFileGrouping);
                }
            }

            return(hasChanges);
        }
        private static TextSpan FromRange(BicepFile bicepFile, Range range)
        {
            var position = TextCoordinateConverter.GetOffset(bicepFile.LineStarts, range.Start.Line, range.Start.Character);
            var length   = TextCoordinateConverter.GetOffset(bicepFile.LineStarts, range.End.Line, range.End.Character) - position;

            return(new TextSpan(position, length));
        }
Пример #5
0
        public Binder(BicepFile bicepFile, ISymbolContext symbolContext)
        {
            // TODO use lazy or some other pattern for init
            this.bicepFile   = bicepFile;
            this.TargetScope = SyntaxHelper.GetTargetScope(bicepFile);
            var(declarations, outermostScopes) = DeclarationVisitor.GetDeclarations(bicepFile, symbolContext);
            var uniqueDeclarations = GetUniqueDeclarations(declarations);
            var builtInNamespaces  = GetBuiltInNamespaces(this.TargetScope);

            this.bindings       = GetBindings(bicepFile, uniqueDeclarations, builtInNamespaces, outermostScopes);
            this.cyclesBySymbol = GetCyclesBySymbol(bicepFile, this.bindings);

            // TODO: Avoid looping 5 times?
            this.FileSymbol = new FileSymbol(
                bicepFile.FileUri.LocalPath,
                bicepFile.ProgramSyntax,
                builtInNamespaces,
                outermostScopes,
                declarations.OfType <ParameterSymbol>(),
                declarations.OfType <VariableSymbol>(),
                declarations.OfType <ResourceSymbol>(),
                declarations.OfType <ModuleSymbol>(),
                declarations.OfType <OutputSymbol>(),
                bicepFile.FileUri);
        }
Пример #6
0
        public void RefreshCompilation(DocumentUri documentUri, bool reloadBicepConfig = false)
        {
            var compilationContext = this.GetCompilation(documentUri);

            if (compilationContext is null)
            {
                // This check handles the scenario when bicepconfig.json was updated, but we
                // couldn't find an entry for the documentUri in activeContexts.
                // This can happen if bicepconfig.json file was previously invalid, in which case
                // we wouldn't have upserted compilation. This is intentional as it's not possible to
                // compute diagnostics till errors in bicepconfig.json are fixed.
                // When errors are fixed in bicepconfig.json and file is saved, we'll get called into this
                // method again. CompilationContext will be null. We'll get the souceFile from workspace and
                // upsert compulation.
                if (reloadBicepConfig &&
                    workspace.TryGetSourceFile(documentUri.ToUri(), out ISourceFile? sourceFile) &&
                    sourceFile is BicepFile)
                {
                    UpsertCompilationInternal(documentUri, null, sourceFile, reloadBicepConfig);
                }
                return;
            }

            // TODO: This may cause race condition if the user is modifying the file at the same time
            // need to make a shallow copy so it counts as a different file even though all the content is identical
            // this was the easiest way to force the compilation to be regenerated
            var shallowCopy = new BicepFile(compilationContext.Compilation.SourceFileGrouping.EntryPoint);

            UpsertCompilationInternal(documentUri, null, shallowCopy, reloadBicepConfig);
        }
Пример #7
0
        private static string GetProgramText(BicepFile bicepFile)
        {
            var buffer  = new StringBuilder();
            var visitor = new PrintVisitor(buffer);

            visitor.Visit(bicepFile.ProgramSyntax);

            return(buffer.ToString());
        }
Пример #8
0
        public static (BicepFile bicepFile, bool hasChanges) Rewrite(Compilation prevCompilation, BicepFile bicepFile, Func <SemanticModel, SyntaxRewriteVisitor> rewriteVisitorBuilder)
        {
            var semanticModel    = new SemanticModel(prevCompilation, bicepFile, prevCompilation.SourceFileGrouping.FileResolver, prevCompilation.Configuration);
            var newProgramSyntax = rewriteVisitorBuilder(semanticModel).Rewrite(bicepFile.ProgramSyntax);

            if (object.ReferenceEquals(bicepFile.ProgramSyntax, newProgramSyntax))
            {
                return(bicepFile, false);
            }

            bicepFile = SourceFileFactory.CreateBicepFile(bicepFile.FileUri, newProgramSyntax.ToTextPreserveFormatting());
            return(bicepFile, true);
        }
Пример #9
0
        private static ImmutableDictionary <SyntaxBase, Symbol> GetBindings(
            BicepFile bicepFile,
            IReadOnlyDictionary <string, DeclaredSymbol> outermostDeclarations,
            ImmutableDictionary <string, NamespaceSymbol> builtInNamespaces,
            ImmutableArray <LocalScope> childScopes)
        {
            // bind identifiers to declarations
            var bindings = new Dictionary <SyntaxBase, Symbol>();
            var binder   = new NameBindingVisitor(outermostDeclarations, bindings, builtInNamespaces, childScopes);

            binder.Visit(bicepFile.ProgramSyntax);

            return(bindings.ToImmutableDictionary());
        }
Пример #10
0
 public Compilation(IResourceTypeProvider resourceTypeProvider, SourceFileGrouping sourceFileGrouping, ImmutableDictionary <ISourceFile, ISemanticModel>?modelLookup = null)
 {
     this.SourceFileGrouping      = sourceFileGrouping;
     this.ResourceTypeProvider    = resourceTypeProvider;
     this.lazySemanticModelLookup = sourceFileGrouping.SourceFiles.ToImmutableDictionary(
         sourceFile => sourceFile,
         sourceFile => (modelLookup is not null && modelLookup.TryGetValue(sourceFile, out var existingModel)) ?
         new(existingModel) :
         new Lazy <ISemanticModel>(() => sourceFile switch
     {
         BicepFile bicepFile => new SemanticModel(this, bicepFile, SourceFileGrouping.FileResolver),
         ArmTemplateFile armTemplateFile => new ArmTemplateSemanticModel(armTemplateFile),
         _ => throw new ArgumentOutOfRangeException(nameof(sourceFile)),
     }));
Пример #11
0
        public Compilation(INamespaceProvider namespaceProvider, SourceFileGrouping sourceFileGrouping, RootConfiguration configuration, ImmutableDictionary <ISourceFile, ISemanticModel>?modelLookup = null)
        {
            this.SourceFileGrouping = sourceFileGrouping;
            this.NamespaceProvider  = namespaceProvider;
            this.Configuration      = configuration;

            var fileResolver = SourceFileGrouping.FileResolver;

            this.lazySemanticModelLookup = sourceFileGrouping.SourceFiles.ToImmutableDictionary(
                sourceFile => sourceFile,
                sourceFile => (modelLookup is not null && modelLookup.TryGetValue(sourceFile, out var existingModel)) ?
                new(existingModel) :
                new Lazy <ISemanticModel>(() => sourceFile switch
            {
                BicepFile bicepFile => new SemanticModel(this, bicepFile, fileResolver, configuration),
                ArmTemplateFile armTemplateFile => new ArmTemplateSemanticModel(armTemplateFile),
                TemplateSpecFile templateSpecFile => new TemplateSpecSemanticModel(templateSpecFile),
                _ => throw new ArgumentOutOfRangeException(nameof(sourceFile)),
            }));
Пример #12
0
        public Binder(INamespaceProvider namespaceProvider, BicepFile bicepFile, ISymbolContext symbolContext)
        {
            // TODO use lazy or some other pattern for init
            this.bicepFile   = bicepFile;
            this.TargetScope = SyntaxHelper.GetTargetScope(bicepFile);
            var(declarations, outermostScopes) = DeclarationVisitor.GetDeclarations(namespaceProvider, TargetScope, bicepFile, symbolContext);
            var uniqueDeclarations = GetUniqueDeclarations(declarations);

            this.NamespaceResolver = GetNamespaceResolver(namespaceProvider, this.TargetScope, uniqueDeclarations);
            this.bindings          = NameBindingVisitor.GetBindings(bicepFile.ProgramSyntax, uniqueDeclarations, NamespaceResolver, outermostScopes);
            this.cyclesBySymbol    = GetCyclesBySymbol(bicepFile, this.bindings);

            this.FileSymbol = new FileSymbol(
                bicepFile.FileUri.LocalPath,
                bicepFile.ProgramSyntax,
                NamespaceResolver,
                outermostScopes,
                declarations,
                bicepFile.FileUri);
        }
Пример #13
0
        public SemanticModel(Compilation compilation, BicepFile sourceFile, IFileResolver fileResolver)
        {
            Trace.WriteLine($"Building semantic model for {sourceFile.FileUri}");

            Compilation  = compilation;
            SourceFile   = sourceFile;
            FileResolver = fileResolver;

            // create this in locked mode by default
            // this blocks accidental type or binding queries until binding is done
            // (if a type check is done too early, unbound symbol references would cause incorrect type check results)
            var symbolContext = new SymbolContext(compilation, this);

            SymbolContext = symbolContext;

            Binder      = new Binder(sourceFile, symbolContext);
            TypeManager = new TypeManager(compilation.ResourceTypeProvider, Binder, fileResolver);

            // name binding is done
            // allow type queries now
            symbolContext.Unlock();

            this.emitLimitationInfoLazy = new Lazy <EmitLimitationInfo>(() => EmitLimitationCalculator.Calculate(this));
            this.symbolHierarchyLazy    = new Lazy <SymbolHierarchy>(() =>
            {
                var hierarchy = new SymbolHierarchy();
                hierarchy.AddRoot(this.Root);

                return(hierarchy);
            });
            this.resourceAncestorsLazy = new Lazy <ResourceAncestorGraph>(() => ResourceAncestorGraph.Compute(this));
            this.ResourceMetadata      = new ResourceMetadataCache(this);

            // lazy loading the linter will delay linter rule loading
            // and configuration loading until the linter is actually needed
            this.linterAnalyzerLazy = new Lazy <LinterAnalyzer>(() => new LinterAnalyzer());

            this.allResourcesLazy = new Lazy <ImmutableArray <ResourceMetadata> >(() => GetAllResourceMetadata());

            // lazy load single use diagnostic set
            this.allDiagnostics = new Lazy <IEnumerable <IDiagnostic> >(() => AssembleDiagnostics(default));
Пример #14
0
        public static BicepFile RewriteMultiple(Compilation prevCompilation, BicepFile bicepFile, int rewritePasses, params Func <SemanticModel, SyntaxRewriteVisitor>[] rewriteVisitorBuilders)
        {
            // Changing the syntax changes the semantic model, so it's possible for rewriters to have dependencies on each other.
            // For example, fixing the casing of a type may fix type validation, causing another rewriter to apply.
            // To handle this, run the rewriters in a loop until we see no more changes.
            for (var i = 0; i < rewritePasses; i++)
            {
                var hasChanges = false;
                foreach (var rewriteVisitorBuilder in rewriteVisitorBuilders)
                {
                    var result = Rewrite(prevCompilation, bicepFile, rewriteVisitorBuilder);
                    hasChanges |= result.hasChanges;
                    bicepFile   = result.bicepFile;
                }

                if (!hasChanges)
                {
                    break;
                }
            }

            return(bicepFile);
        }
Пример #15
0
 /// <summary>
 /// Prints the program syntax with line numbers and diagnostics if a test fails in the given assertion scope.
 /// </summary>
 public static AssertionScope WithVisualDiagnostics(this AssertionScope assertionScope, BicepFile bicepFile, IEnumerable <IDiagnostic> diagnostics)
 => WithAnnotatedSource(
     assertionScope,
     bicepFile,
     "diagnostics",
     diagnostics.Select(x => new PrintHelper.Annotation(x.Span, $"[{x.Code} ({x.Level})] {x.Message}")));
Пример #16
0
 /// <summary>
 /// Prints the program syntax with line numbers and a cursor if a test fails in the given assertion scope.
 /// </summary>
 public static AssertionScope WithVisualCursor(this AssertionScope assertionScope, BicepFile bicepFile, IPositionable cursorPosition)
 => WithAnnotatedSource(
     assertionScope,
     bicepFile,
     "cursor info",
     new PrintHelper.Annotation(cursorPosition.Span, "cursor").AsEnumerable());
Пример #17
0
        private static async Task ValidateOffset(ILanguageClient client, DocumentUri uri, BicepFile bicepFile, int offset, FunctionSymbol?symbol, bool expectDecorator)
        {
            var position = PositionHelper.GetPosition(bicepFile.LineStarts, offset);
            var initial  = await RequestSignatureHelp(client, position, uri);

            // fancy method to give us some annotated source code to look at if any assertions fail :)
            using (new AssertionScope().WithVisualCursor(bicepFile, new TextSpan(offset, 0)))
            {
                if (symbol is not null)
                {
                    // real function should have valid signature help
                    AssertValidSignatureHelp(initial, symbol, expectDecorator);

                    if (initial !.Signatures.Count() >= 2)
                    {
                        // update index to 1 to mock user changing active signature
                        const int ExpectedActiveSignatureIndex = 1;
                        var       modified = initial with
                        {
                            ActiveSignature = ExpectedActiveSignatureIndex
                        };

                        var shouldRemember = await RequestSignatureHelp(client, position, uri, new SignatureHelpContext
                        {
                            ActiveSignatureHelp = modified,
                            IsRetrigger         = true,
                            TriggerKind         = SignatureHelpTriggerKind.ContentChange
                        });

                        // we passed the same signature help as content with a different active index
                        // should get the same index back
                        AssertValidSignatureHelp(shouldRemember, symbol, expectDecorator);
                        shouldRemember !.ActiveSignature.Should().Be(ExpectedActiveSignatureIndex);
                    }
                }
                else
                {
                    // not a real function - no signature help expected
                    initial.Should().BeNull();
                }
            }
        }
Пример #18
0
 public static BicepFileAssertions Should(this BicepFile bicepFile)
 {
     return(new BicepFileAssertions(bicepFile));
 }
Пример #19
0
        private static async Task <List <LocationOrLocationLinks> > RequestDefinitions(ILanguageClient client, BicepFile bicepFile, IEnumerable <int> cursors)
        {
            var results = new List <LocationOrLocationLinks>();

            foreach (var cursor in cursors)
            {
                var result = await client.RequestDefinition(new DefinitionParams()
                {
                    TextDocument = new TextDocumentIdentifier(bicepFile.FileUri),
                    Position     = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, cursor)
                });

                results.Add(result);
            }

            return(results);
        }
Пример #20
0
        public SemanticModel(Compilation compilation, BicepFile sourceFile, IFileResolver fileResolver, RootConfiguration configuration)
        {
            Trace.WriteLine($"Building semantic model for {sourceFile.FileUri}");

            Compilation   = compilation;
            SourceFile    = sourceFile;
            FileResolver  = fileResolver;
            Configuration = configuration;

            // create this in locked mode by default
            // this blocks accidental type or binding queries until binding is done
            // (if a type check is done too early, unbound symbol references would cause incorrect type check results)
            var symbolContext = new SymbolContext(compilation, this);

            SymbolContext = symbolContext;

            Binder      = new Binder(compilation.NamespaceProvider, sourceFile, symbolContext);
            TypeManager = new TypeManager(Binder, fileResolver);

            // name binding is done
            // allow type queries now
            symbolContext.Unlock();

            this.emitLimitationInfoLazy = new Lazy <EmitLimitationInfo>(() => EmitLimitationCalculator.Calculate(this));
            this.symbolHierarchyLazy    = new Lazy <SymbolHierarchy>(() =>
            {
                var hierarchy = new SymbolHierarchy();
                hierarchy.AddRoot(this.Root);

                return(hierarchy);
            });
            this.resourceAncestorsLazy = new Lazy <ResourceAncestorGraph>(() => ResourceAncestorGraph.Compute(this));
            this.ResourceMetadata      = new ResourceMetadataCache(this);

            // lazy loading the linter will delay linter rule loading
            // and configuration loading until the linter is actually needed
            this.linterAnalyzerLazy = new Lazy <LinterAnalyzer>(() => new LinterAnalyzer(configuration));

            this.allResourcesLazy = new Lazy <ImmutableArray <ResourceMetadata> >(() => GetAllResourceMetadata());

            // lazy load single use diagnostic set
            this.allDiagnostics = new Lazy <IEnumerable <IDiagnostic> >(() => AssembleDiagnostics());

            this.parameterTypePropertiesLazy = new Lazy <ImmutableArray <TypeProperty> >(() =>
            {
                var paramTypeProperties = new List <TypeProperty>();

                foreach (var param in this.Root.ParameterDeclarations.DistinctBy(p => p.Name))
                {
                    var typePropertyFlags = TypePropertyFlags.WriteOnly;
                    if (SyntaxHelper.TryGetDefaultValue(param.DeclaringParameter) == null)
                    {
                        // if there's no default value, it must be specified
                        typePropertyFlags |= TypePropertyFlags.Required;
                    }

                    var description = SemanticModelHelper.TryGetDescription(this, param.DeclaringParameter);
                    paramTypeProperties.Add(new TypeProperty(param.Name, param.Type, typePropertyFlags, description));
                }

                return(paramTypeProperties.ToImmutableArray());
            });

            this.outputTypePropertiesLazy = new Lazy <ImmutableArray <TypeProperty> >(() =>
            {
                var outputTypeProperties = new List <TypeProperty>();

                foreach (var output in this.Root.OutputDeclarations.DistinctBy(o => o.Name))
                {
                    var description = SemanticModelHelper.TryGetDescription(this, output.DeclaringOutput);
                    outputTypeProperties.Add(new TypeProperty(output.Name, output.Type, TypePropertyFlags.ReadOnly, description));
                }

                return(outputTypeProperties.ToImmutableArray());
            });
        }
Пример #21
0
        public SemanticModel(Compilation compilation, BicepFile sourceFile, IFileResolver fileResolver, IBicepAnalyzer linterAnalyzer)
        {
            Trace.WriteLine($"Building semantic model for {sourceFile.FileUri}");

            Compilation  = compilation;
            SourceFile   = sourceFile;
            FileResolver = fileResolver;

            // create this in locked mode by default
            // this blocks accidental type or binding queries until binding is done
            // (if a type check is done too early, unbound symbol references would cause incorrect type check results)
            var symbolContext = new SymbolContext(compilation, this);

            SymbolContext = symbolContext;

            Binder      = new Binder(compilation.NamespaceProvider, sourceFile, symbolContext);
            TypeManager = new TypeManager(compilation.Features, Binder, fileResolver);

            // name binding is done
            // allow type queries now
            symbolContext.Unlock();

            this.emitLimitationInfoLazy = new Lazy <EmitLimitationInfo>(() => EmitLimitationCalculator.Calculate(this));
            this.symbolHierarchyLazy    = new Lazy <SymbolHierarchy>(() =>
            {
                var hierarchy = new SymbolHierarchy();
                hierarchy.AddRoot(this.Root);

                return(hierarchy);
            });
            this.resourceAncestorsLazy = new Lazy <ResourceAncestorGraph>(() => ResourceAncestorGraph.Compute(this));
            this.ResourceMetadata      = new ResourceMetadataCache(this);

            LinterAnalyzer = linterAnalyzer;

            this.allResourcesLazy      = new Lazy <ImmutableArray <ResourceMetadata> >(() => GetAllResourceMetadata());
            this.declaredResourcesLazy = new Lazy <ImmutableArray <DeclaredResourceMetadata> >(() => this.AllResources.OfType <DeclaredResourceMetadata>().ToImmutableArray());

            // lazy load single use diagnostic set
            this.allDiagnostics = new Lazy <IEnumerable <IDiagnostic> >(() => AssembleDiagnostics());

            this.parametersLazy = new Lazy <ImmutableArray <ParameterMetadata> >(() =>
            {
                var parameters = new List <ParameterMetadata>();

                foreach (var param in this.Root.ParameterDeclarations.DistinctBy(p => p.Name))
                {
                    var description = SemanticModelHelper.TryGetDescription(this, param.DeclaringParameter);
                    var isRequired  = SyntaxHelper.TryGetDefaultValue(param.DeclaringParameter) == null;
                    if (param.Type is ResourceType resourceType)
                    {
                        // Resource type parameters are a special case, we need to convert to a dedicated
                        // type so we can compare differently for assignment.
                        var type = new UnboundResourceType(resourceType.TypeReference);
                        parameters.Add(new ParameterMetadata(param.Name, type, isRequired, description));
                    }
                    else
                    {
                        parameters.Add(new ParameterMetadata(param.Name, param.Type, isRequired, description));
                    }
                }

                return(parameters.ToImmutableArray());
            });

            this.outputsLazy = new Lazy <ImmutableArray <OutputMetadata> >(() =>
            {
                var outputs = new List <OutputMetadata>();

                foreach (var output in this.Root.OutputDeclarations.DistinctBy(o => o.Name))
                {
                    var description = SemanticModelHelper.TryGetDescription(this, output.DeclaringOutput);
                    if (output.Type is ResourceType resourceType)
                    {
                        // Resource type parameters are a special case, we need to convert to a dedicated
                        // type so we can compare differently for assignment and code generation.
                        var type = new UnboundResourceType(resourceType.TypeReference);
                        outputs.Add(new OutputMetadata(output.Name, type, description));
                    }
                    else
                    {
                        outputs.Add(new OutputMetadata(output.Name, output.Type, description));
                    }
                }

                return(outputs.ToImmutableArray());
            });
        }
Пример #22
0
 private static ImmutableDictionary <DeclaredSymbol, ImmutableArray <DeclaredSymbol> > GetCyclesBySymbol(BicepFile bicepFile, IReadOnlyDictionary <SyntaxBase, Symbol> bindings)
 {
     return(CyclicCheckVisitor.FindCycles(bicepFile.ProgramSyntax, bindings));
 }
Пример #23
0
        public static (Uri entrypointUri, ImmutableDictionary <Uri, string> filesToSave) DecompileFileWithModules(IResourceTypeProvider resourceTypeProvider, IFileResolver fileResolver, Uri entryJsonUri, Uri entryBicepUri)
        {
            var workspace      = new Workspace();
            var decompileQueue = new Queue <(Uri, Uri)>();

            decompileQueue.Enqueue((entryJsonUri, entryBicepUri));

            while (decompileQueue.Count > 0)
            {
                var(jsonUri, bicepUri) = decompileQueue.Dequeue();

                if (PathHelper.HasBicepExtension(jsonUri))
                {
                    throw new InvalidOperationException($"Cannot decompile the file with .bicep extension: {jsonUri}.");
                }

                if (workspace.TryGetSourceFile(bicepUri, out _))
                {
                    continue;
                }

                if (!fileResolver.TryRead(jsonUri, out var jsonInput, out _))
                {
                    throw new InvalidOperationException($"Failed to read {jsonUri}");
                }

                var(program, jsonTemplateUrisByModule) = TemplateConverter.DecompileTemplate(workspace, fileResolver, bicepUri, jsonInput);
                var bicepFile = new BicepFile(bicepUri, ImmutableArray <int> .Empty, program);
                workspace.UpsertSourceFile(bicepFile);

                foreach (var module in program.Children.OfType <ModuleDeclarationSyntax>())
                {
                    var moduleRelativePath = SyntaxHelper.TryGetModulePath(module, out _);
                    if (moduleRelativePath == null ||
                        !LocalModuleReference.Validate(moduleRelativePath, out _) ||
                        !Uri.TryCreate(bicepUri, moduleRelativePath, out var moduleUri))
                    {
                        // Do our best, but keep going if we fail to resolve a module file
                        continue;
                    }

                    if (!workspace.TryGetSourceFile(moduleUri, out _) && jsonTemplateUrisByModule.TryGetValue(module, out var linkedTemplateUri))
                    {
                        decompileQueue.Enqueue((linkedTemplateUri, moduleUri));
                    }
                }
            }

            RewriteSyntax(resourceTypeProvider, workspace, entryBicepUri, semanticModel => new ParentChildResourceNameRewriter(semanticModel));
            RewriteSyntax(resourceTypeProvider, workspace, entryBicepUri, semanticModel => new DependsOnRemovalRewriter(semanticModel));
            RewriteSyntax(resourceTypeProvider, workspace, entryBicepUri, semanticModel => new ForExpressionSimplifierRewriter(semanticModel));
            for (var i = 0; i < 5; i++)
            {
                // This is a little weird. If there are casing issues nested inside casing issues (e.g. in an object), then the inner casing issue will have no type information
                // available, as the compilation will not have associated a type with it (since there was no match on the outer object). So we need to correct the outer issue first,
                // and then move to the inner one. We need to recompute the entire compilation to do this. It feels simpler to just do this in passes over the file, rather than on demand.
                if (!RewriteSyntax(resourceTypeProvider, workspace, entryBicepUri, semanticModel => new TypeCasingFixerRewriter(semanticModel)))
                {
                    break;
                }
            }

            return(entryBicepUri, PrintFiles(workspace));
        }
Пример #24
0
        public static string PrintWithAnnotations(BicepFile bicepFile, IEnumerable <Annotation> annotations, int context, bool includeLineNumbers)
        {
            if (!annotations.Any())
            {
                return("");
            }

            var output       = new StringBuilder();
            var programLines = GetProgramTextLines(bicepFile);

            var annotationPositions = annotations.ToDictionary(
                x => x,
                x => TextCoordinateConverter.GetPosition(bicepFile.LineStarts, x.Span.Position));

            var annotationsByLine = annotationPositions.ToLookup(x => x.Value.line, x => x.Key);

            var minLine = annotationPositions.Values.Aggregate(int.MaxValue, (min, curr) => Math.Min(curr.line, min));
            var maxLine = annotationPositions.Values.Aggregate(0, (max, curr) => Math.Max(curr.line, max)) + 1;

            minLine = Math.Max(0, minLine - context);
            maxLine = Math.Min(bicepFile.LineStarts.Length, maxLine + context);
            var digits = maxLine.ToString().Length;

            for (var i = minLine; i < maxLine; i++)
            {
                var gutterOffset = 0;
                if (includeLineNumbers)
                {
                    var lineNumber = i + 1; // to match VSCode's line numbering (starting at 1)
                    output.Append(lineNumber.ToString().PadLeft(digits, '0'));
                    output.Append("| ");

                    gutterOffset = digits + 2;
                }
                output.Append(programLines[i]);
                output.Append('\n');

                var annotationsToDisplay = annotationsByLine[i].OrderBy(x => annotationPositions[x].character);
                foreach (var annotation in annotationsToDisplay)
                {
                    var position = annotationPositions[annotation];
                    output.Append(new String(' ', gutterOffset + position.character));

                    switch (annotation.Span.Length)
                    {
                    case 0:
                        output.Append("^");
                        break;

                    case int x:
                        // TODO handle annotation spanning multiple lines
                        output.Append(new String('~', x));
                        break;
                    }

                    output.Append(" ");
                    output.Append(annotation.Message);
                    output.Append('\n');
                }
            }

            return(output.ToString());
        }
Пример #25
0
        private static string[] GetProgramTextLines(BicepFile bicepFile)
        {
            var programText = bicepFile.ProgramSyntax.ToTextPreserveFormatting();

            return(StringUtils.ReplaceNewlines(programText, "\n").Split("\n"));
        }
 public static AssertionScope WithAnnotations <T>(this AssertionScope assertionScope, BicepFile bicepFile, string contextName, IEnumerable <T>?data, Func <T, string> messageFunc, Func <T, Range> rangeFunc)
 => Core.UnitTests.Assertions.AssertionScopeExtensions.WithAnnotatedSource(
     assertionScope,
     bicepFile,
     contextName,
     (data ?? Enumerable.Empty <T>()).Select(x => new PrintHelper.Annotation(FromRange(bicepFile, rangeFunc(x)), messageFunc(x))));
Пример #27
0
        public static AssertionScope WithAnnotatedSource(AssertionScope assertionScope, BicepFile bicepFile, string contextName, IEnumerable <PrintHelper.Annotation> annotations)
        {
            assertionScope.AddReportable(
                contextName,
                () => PrintHelper.PrintWithAnnotations(bicepFile, annotations, 1, true));

            return(assertionScope);
        }
Пример #28
0
        public static AssertionScope WithAnnotatedSource(AssertionScope assertionScope, BicepFile bicepFile, string contextName, IEnumerable <PrintHelper.Annotation> annotations)
        {
            // TODO: figure out how to set this only on failure, rather than always calculating it
            assertionScope.AddReportable(
                contextName,
                PrintHelper.PrintWithAnnotations(bicepFile, annotations, 1, true));

            return(assertionScope);
        }
Пример #29
0
            static IEnumerable <IDiagnostic> GetModuleDiagnosticsPerFile(SourceFileGrouping grouping, BicepFile bicepFile, ImmutableHashSet <ModuleDeclarationSyntax> originalModulesToRestore)
            {
                foreach (var module in bicepFile.ProgramSyntax.Declarations.OfType <ModuleDeclarationSyntax>())
                {
                    if (!originalModulesToRestore.Contains(module))
                    {
                        continue;
                    }

                    if (grouping.TryLookUpModuleErrorDiagnostic(module, out var error))
                    {
                        yield return(error);
                    }
                }
            }
Пример #30
0
        private Dictionary <string, string> GetTelemetryPropertiesForMainFile(SemanticModel sematicModel, BicepFile bicepFile, IEnumerable <Diagnostic> diagnostics)
        {
            Dictionary <string, string> properties = new();

            var declarationsInMainFile = bicepFile.ProgramSyntax.Declarations;

            properties.Add("Modules", declarationsInMainFile.Count(x => x is ModuleDeclarationSyntax).ToString());
            properties.Add("Parameters", declarationsInMainFile.Count(x => x is ParameterDeclarationSyntax).ToString());
            properties.Add("Resources", sematicModel.AllResources.Length.ToString());
            properties.Add("Variables", declarationsInMainFile.Count(x => x is VariableDeclarationSyntax).ToString());

            var localPath = bicepFile.FileUri.LocalPath;

            try
            {
                if (File.Exists(localPath))
                {
                    var fileInfo = new FileInfo(bicepFile.FileUri.LocalPath);
                    properties.Add("FileSizeInBytes", fileInfo.Length.ToString());
                }
            }
            catch (Exception)
            {
                // We should not throw in this case since it will block compilation.
                properties.Add("FileSizeInBytes", string.Empty);
            }

            properties.Add("LineCount", bicepFile.LineStarts.Length.ToString());
            properties.Add("Errors", diagnostics.Count(x => x.Severity == DiagnosticSeverity.Error).ToString());
            properties.Add("Warnings", diagnostics.Count(x => x.Severity == DiagnosticSeverity.Warning).ToString());

            return(properties);
        }