/// <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); }
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); } } }
/// <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); }
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); }
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)); }
/// <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); }
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); }
private TNode FindNode <TNode>(INode root) where TNode : INode { return(NodeWalker.TraverseBreadthFirstAndSelf(root).OfType <TNode>().FirstOrDefault()); }
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); }