/// <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); }
private static bool IsTypeCorrectForAddSourceFileConfiguration(IType type, Workspace workspace, PathTable pathTable, AddSourceFileConfiguration configuration) { if (type.Symbol?.Declarations?.FirstOrDefault()?.Name?.Text == configuration.ArgumentTypeName) { var sourceFilename = type.Symbol?.Declarations?.FirstOrDefault()?.SourceFile?.FileName; if (sourceFilename != null) { var parsedMoudle = workspace.TryGetModuleBySpecFileName(AbsolutePath.Create(pathTable, sourceFilename)); if (parsedMoudle != null) { if (parsedMoudle?.Descriptor.Name == configuration.ArgumentTypeModuleName) { return(true); } } } } return(false); }