Example #1
0
        /// <summary>
        /// Performs the analysis.
        /// </summary>
        public virtual bool AnalyzeSourceFile(Workspace workspace, AbsolutePath path, ISourceFile sourceFile)
        {
            if (m_specHandlers != null)
            {
                var context = new DiagnosticsContext(sourceFile, Logger, LoggingContext, PathTable, workspace);

                bool success = true;
                foreach (var node in NodeWalker.TraverseBreadthFirstAndSelf(sourceFile))
                {
                    // Only non-injected nodes are checked by the linter.
                    if (node.IsInjectedForDScript())
                    {
                        continue;
                    }

                    var handlers = m_specHandlers[(int)node.Kind];
                    if (handlers != null)
                    {
                        foreach (var handler in handlers)
                        {
                            success &= handler(node, context);
                        }
                    }
                }

                return(success);
            }

            return(true);
        }
Example #2
0
        private void VisitFile(ISourceFile node, DiagnosticContext context)
        {
            // Parallel node visitation may only make sense when specs are huge. The only case we know is Office, where
            // some specs have >600K statements. For specs of regular size parallel node visitation adds a significant sync overhead
            // (e.g. for analog it adds ~20% overhead to ast conversion)
            // Since UseLegacyOfficeLogic is an Office-specific flag, we are using that to special-case Office specs until they
            // are onboarded to DScript V2.
            // TODO: This needs to go!!!!!

            if (m_useLegacyOfficeLogic)
            {
                // Parallel visitation can only work if the handlers registered during the creation of rules
                // in <see cref="InitializeDiagnosticRules"/> are thread-safe. They are currently thread-safe.
                ParallelVisitNode(node, context);
            }
            else
            {
                // Synchronous node visitation
                foreach (var nestedNode in NodeWalker.TraverseBreadthFirstAndSelf(node))
                {
                    // Only non-injected nodes are checked by the linter.
                    if (nestedNode.IsInjectedForDScript())
                    {
                        continue;
                    }

                    Handle(nestedNode, context);
                }
            }
        }
Example #3
0
        /// <summary>
        /// Finds all the nodes at a given <paramref name="positions"/>.
        /// </summary>
        public static INode[] TryGetNodesAtPositions(ISourceFile sourceFile, IReadOnlyList <int> positions, Func <INode, bool> isNodeAcceptable, System.Threading.CancellationToken token)
        {
            Contract.Requires(sourceFile != null);

            INode[] nodeByPosition = new INode[positions.Count];
            foreach (var node in NodeWalker.TraverseBreadthFirstAndSelf(sourceFile))
            {
                if (token.IsCancellationRequested)
                {
                    break;
                }

                int startPosition = node.GetNodeStartPositionWithoutTrivia(sourceFile);
                int endPosition   = node.End;

                for (int i = 0; i < positions.Count; i++)
                {
                    int position = positions[i];
                    if (startPosition <= position && position <= endPosition)
                    {
                        if (nodeByPosition[i] == null || node.GetNodeWidth(sourceFile) <= nodeByPosition[i].GetNodeWidth(sourceFile))
                        {
                            if (isNodeAcceptable(node))
                            {
                                nodeByPosition[i] = node;
                            }
                        }
                    }
                }
            }

            return(nodeByPosition);
        }
Example #4
0
        private TestFunction TestHelper(string code, string expectedShortName, string expectedFullIdentifier, int expectedErrorCount, Dictionary <string, string> lkgFiles, params string[] expectedMessages)
        {
            TestFunction testFunction = null;
            var          parser       = new Parser();
            ISourceFile  sourceFile   = parser.ParseSourceFileContent("testFile.dsc", code, ParsingOptions.DefaultParsingOptions);

            Assert.Equal(0, sourceFile.ParseDiagnostics.Count);

            var binder = new Binder();

            binder.BindSourceFile(sourceFile, CompilerOptions.Empty);
            Assert.Equal(0, sourceFile.BindDiagnostics.Count);

            foreach (var node in NodeWalker.TraverseBreadthFirstAndSelf(sourceFile))
            {
                if (node.Kind == SyntaxKind.FunctionDeclaration)
                {
                    var functionDecl = (IFunctionDeclaration)node;
                    var success      = TestFunction.TryExtractFunction(Logger, sourceFile, functionDecl, lkgFiles, out testFunction);
                    Assert.Equal(expectedErrorCount == 0, success);
                    if (success)
                    {
                        Assert.NotNull(testFunction);
                        Assert.Equal(expectedShortName, testFunction.ShortName);
                        Assert.Equal(expectedFullIdentifier, testFunction.FullIdentifier);
                    }
                }
            }

            Logger.ValidateErrors(expectedErrorCount, expectedMessages);

            return(testFunction);
        }
Example #5
0
        private static void CheckFullyQualifiedName(string code, string expectedQualifiedName, bool preserveTrivia)
        {
            var sourceFile = ParseAndBind(code, preserveTrivia);
            var checker    = CreateTypeChecker(sourceFile);

            checker.GetDiagnostics();

            var node = NodeWalker.TraverseBreadthFirstAndSelf(sourceFile).First(n => n is VariableDeclaration || n is FunctionDeclaration || n is InterfaceDeclaration || n is TypeAliasDeclaration) as Node;

            Assert.Equal(expectedQualifiedName, checker.GetFullyQualifiedName(node.Symbol));
        }
Example #6
0
        /// <summary>
        /// Extracts a TestClass from a string
        /// </summary>
        public static bool TryExtractTestClass(Logger logger, ISourceFile sourceFile, IDictionary <string, string> lkgFiles, out TestClass testClass)
        {
            bool success       = true;
            var  testFunctions = new Dictionary <string, TestFunction>(StringComparer.OrdinalIgnoreCase);

            foreach (var node in NodeWalker.TraverseBreadthFirstAndSelf(sourceFile))
            {
                bool hasValidUnitTestDecorator;
                success &= TryCheckHasUnitTestDecorator(logger, sourceFile, node, out hasValidUnitTestDecorator);

                if (hasValidUnitTestDecorator)
                {
                    if (node.Kind != SyntaxKind.FunctionDeclaration)
                    {
                        logger.LogError(sourceFile, node, "UnitTest attribute is only allowed on top-level functions");
                        success = false;
                        continue;
                    }

                    TestFunction testFunction;
                    var          functionDeclaration = (IFunctionDeclaration)node;
                    if (!TestFunction.TryExtractFunction(logger, sourceFile, functionDeclaration, lkgFiles, out testFunction))
                    {
                        success = false;
                        continue;
                    }

                    TestFunction existingFunction;
                    if (testFunctions.TryGetValue(testFunction.ShortName, out existingFunction))
                    {
                        logger.LogError(sourceFile, node, C($"Duplicate test-definition. There are multiple tests with name '{testFunction.ShortName}': '{existingFunction.FullIdentifier}' and '{testFunction.FullIdentifier}'"));
                        success = false;
                        continue;
                    }

                    testFunctions.Add(testFunction.ShortName, testFunction);
                }
            }

            if (success && testFunctions.Count == 0)
            {
                logger.LogError(sourceFile.FileName, 0, 0, C($"No UnitTests found in file. Please decorate functions with @@{UnitTestName}"));
                testClass = null;
                return(false);
            }

            testClass = new TestClass(
                Path.GetFileNameWithoutExtension(sourceFile.FileName),
                sourceFile.FileName,
                testFunctions.Values.ToArray());
            return(success);
        }
Example #7
0
        public void WithQualifierIdentifierHasSourceFileAvailable()
        {
            var code       = @"
export declare const qualifier : {};
namespace A {}

export const x = A.withQualifier({});";
            var parsedFile = ParsingHelper.ParseSourceFile(code);

            var identifier = NodeWalker.TraverseBreadthFirstAndSelf(parsedFile).OfType <IIdentifier>().First(n => n.Text == "withQualifier");

            // It is important for the injected modifiers to have an associated source file.
            Assert.NotNull(identifier.SourceFile);
        }
Example #8
0
 private TNode FindNode <TNode>(INode root) where TNode : INode
 {
     return(NodeWalker.TraverseBreadthFirstAndSelf(root).OfType <TNode>().FirstOrDefault());
 }
Example #9
0
        private TNode FindFirstNode <TNode>(INode root) where TNode : class, INode
        {
            var allNodes = NodeWalker.TraverseBreadthFirstAndSelf(root).ToList();

            return(allNodes.Select(n => n.As <TNode>()).First(n => n != null));
        }
        /// <summary>
        /// Attempts to add a file to a source file list. The conditions that must be met are specified in the
        /// array of <paramref name="configurations"/>
        /// </summary>
        public static bool TryAddSourceFileToSourceFile(ITypeChecker checker, ISourceFile sourceFile, string sourceFileName, Workspace workspace, PathTable pathTable, AddSourceFileConfiguration[] configurations)
        {
            INode sourcesNode = null;

            try
            {
                // Use single or default to ensure that we only match a single sources property.
                // If we find more than one, we don't know which source file list to augment.
                // SingleOrDefault throws an InvalidOperationException if it finds more than one element
                // and returns default<T> if there are 0.
                sourcesNode = NodeWalker.TraverseBreadthFirstAndSelf(sourceFile).SingleOrDefault(node =>
                {
                    // We expect that the property for the source file list to be in an object literal
                    // and hence be a property assignment inside that object literal.
                    // The statement will look something like:
                    // const result = TargetType.build( { sources: [f`foo.cpp`] } );
                    if (node.Kind == SyntaxKind.PropertyAssignment &&
                        node.Cast <IPropertyAssignment>().Name.Kind == SyntaxKind.Identifier &&
                        node.Parent?.Kind == SyntaxKind.ObjectLiteralExpression)
                    {
                        var propertyName = node.Cast <IPropertyAssignment>().Name.Text;

                        // Now check the configurations to see if the any match as there
                        // can be different names (such as "references", "sources", etc.) as
                        // well as different functions, etc.

                        AddSourceFileConfiguration singleConfiguration = null;

                        try
                        {
                            // We use single or default to ensure that only one matching configuration is found.
                            // SingleOrDefault throws an InvalidOperationException if it finds more than one element
                            // and returns default<T> if there are 0.
                            singleConfiguration = configurations.SingleOrDefault(configuration =>
                            {
                                // Check to see if this is the correct property name.
                                if (propertyName != configuration.PropertyName)
                                {
                                    return(false);
                                }

                                // Now we will try to find the matching call expression (function name)
                                // The reason we are going to walk parent nodes is that we allow
                                // a "merge" or "override" to be nested inside the function call
                                // as long as the argument type and the expected module the type exists
                                // in match the configuration parameter.
                                var nodeParent = node.Parent.Parent;
                                while (nodeParent != null)
                                {
                                    if (nodeParent.Kind != SyntaxKind.CallExpression)
                                    {
                                        return(false);
                                    }

                                    var callExpression    = nodeParent.Cast <ICallExpression>();
                                    string calledFunction = string.Empty;

                                    // Depending on the module the function is being called from it may be a straight
                                    // call (such as "build()") or it could be an accessor if it was imported
                                    // from another module (such as "StaticLibrary.build()").
                                    if (callExpression.Expression?.Kind == SyntaxKind.PropertyAccessExpression)
                                    {
                                        var propertyAccessExpression = callExpression.Expression.Cast <IPropertyAccessExpression>();
                                        calledFunction = propertyAccessExpression.Name?.Text;
                                    }
                                    else if (callExpression.Expression?.Kind == SyntaxKind.Identifier)
                                    {
                                        calledFunction = callExpression.Expression.Cast <Identifier>().Text;
                                    }
                                    else
                                    {
                                        return(false);
                                    }

                                    // If the called function matches, and has the minimum number of parameters to contain our argument type
                                    // then verify it matches the type name given in the configuration.
                                    if (calledFunction == configuration.FunctionName && callExpression.Arguments?.Length > configuration.ArgumentPosition)
                                    {
                                        var type = checker.GetContextualType(callExpression.Arguments[configuration.ArgumentPosition]);
                                        if (type != null && IsTypeCorrectForAddSourceFileConfiguration(type, workspace, pathTable, configuration))
                                        {
                                            return(true);
                                        }
                                    }
                                    else if (DScriptUtilities.IsMergeOrOverrideCallExpression(callExpression))
                                    {
                                        // In the case of a merge or override function, we make sure it is the proper type and keep moving
                                        // up the parent chain to find the function call.
                                        var type = checker.GetTypeAtLocation(callExpression.TypeArguments[0]);
                                        if (type != null && IsTypeCorrectForAddSourceFileConfiguration(type, workspace, pathTable, configuration))
                                        {
                                            nodeParent = nodeParent.Parent;
                                            continue;
                                        }
                                    }

                                    return(false);
                                }

                                return(false);
                            });
                        }
                        catch (InvalidOperationException)
                        {
                            return(false);
                        }

                        return(singleConfiguration != null);
                    }

                    return(false);
                });
            }
            catch (InvalidOperationException)
            {
            }

            if (sourcesNode != null)
            {
                var propertyAssignment = sourcesNode.Cast <IPropertyAssignment>();
                // Will support array literals for now.
                var initializer = propertyAssignment.Initializer.As <IArrayLiteralExpression>();
                if (initializer == null)
                {
                    // TODO: potentially we could have a glob call here, and what we can do this:
                    // [...(oldExpression), newFile]
                    return(false);
                }

                var alreadyPresent = initializer.Elements.Any(element =>
                {
                    return(element.Kind == SyntaxKind.TaggedTemplateExpression &&
                           element.Cast <ITaggedTemplateExpression>().Template?.Text.Equals(sourceFileName, StringComparison.OrdinalIgnoreCase) == true);
                });

                if (!alreadyPresent)
                {
                    initializer.Elements.Add(new TaggedTemplateExpression("f", sourceFileName));
                    return(true);
                }
            }
            return(false);
        }