public static MSBuildRootDocument Parse( ITextSource textSource, string filePath, MSBuildRootDocument previous, MSBuildSchemaProvider schemaProvider, IRuntimeInformation runtimeInfo, ITaskMetadataBuilder taskBuilder, CancellationToken token) { var xmlParser = new XmlTreeParser(new XmlRootState()); var(xdocument, _) = xmlParser.Parse(textSource.CreateReader()); var propVals = new PropertyValueCollector(true); var doc = new MSBuildRootDocument(filePath) { XDocument = xdocument, Text = textSource, RuntimeInformation = runtimeInfo }; var importedFiles = new HashSet <string> (StringComparer.OrdinalIgnoreCase); if (filePath != null) { try { doc.Schema = previous?.Schema ?? schemaProvider.GetSchema(filePath, null); } catch (Exception ex) { LoggingService.LogError("Error loading schema", ex); } importedFiles.Add(filePath); } var parseContext = new MSBuildParserContext( runtimeInfo, doc, previous, importedFiles, filePath, propVals, taskBuilder, schemaProvider, token); if (filePath != null) { doc.FileEvaluationContext = new MSBuildFileEvaluationContext(parseContext.RuntimeEvaluationContext, filePath, filePath); } else { doc.FileEvaluationContext = parseContext.RuntimeEvaluationContext; } string MakeRelativeMSBuildPathAbsolute(string path) { var dir = Path.GetDirectoryName(doc.Filename); path = path.Replace('\\', Path.DirectorySeparatorChar); return(Path.GetFullPath(Path.Combine(dir, path))); } Import TryImportFile(string label, string possibleFile) { try { var fi = new FileInfo(possibleFile); if (fi.Exists) { var imp = parseContext.GetCachedOrParse(label, possibleFile, null, fi.LastWriteTimeUtc); doc.AddImport(imp); return(imp); } } catch (Exception ex) when(parseContext.IsNotCancellation(ex)) { LoggingService.LogError($"Error importing '{possibleFile}'", ex); } return(null); } Import TryImportSibling(string ifHasThisExtension, string thenTryThisExtension) { if (filePath == null) { return(null); } var extension = Path.GetExtension(filePath); if (string.Equals(ifHasThisExtension, extension, StringComparison.OrdinalIgnoreCase)) { var siblingFilename = Path.ChangeExtension(filePath, thenTryThisExtension); return(TryImportFile("(implicit)", siblingFilename)); } return(null); } void TryImportIntellisenseImports(MSBuildSchema schema) { foreach (var intellisenseImport in schema.IntelliSenseImports) { TryImportFile("(from schema)", MakeRelativeMSBuildPathAbsolute(intellisenseImport)); } } try { //if this is a targets file, try to import the props _at the top_ var propsImport = TryImportSibling(".targets", ".props"); // this currently only happens in the root file // it's a quick hack to allow files to get some basic intellisense by // importing the files _that they themselves expect to be imported from_. // we also try to load them from the sibling props, as a paired targets/props // will likely share a schema file. var schema = doc.Schema ?? propsImport?.Document?.Schema; if (schema != null) { TryImportIntellisenseImports(doc.Schema); } doc.Build(xdocument, textSource, parseContext); //if this is a props file, try to import the targets _at the bottom_ var targetsImport = TryImportSibling(".props", ".targets"); //and if we didn't load intellisense import already, try to load them from the sibling targets if (schema == null && targetsImport?.Document?.Schema != null) { TryImportIntellisenseImports(targetsImport.Document.Schema); } } catch (Exception ex) when(parseContext.IsNotCancellation(ex)) { LoggingService.LogError($"Error building document '{filePath ?? "[unnamed]"}'", ex); } try { var binpath = parseContext.RuntimeInformation.BinPath; foreach (var t in Directory.GetFiles(binpath, "*.tasks")) { doc.LoadTasks(parseContext, "(core tasks)", t); } foreach (var t in Directory.GetFiles(binpath, "*.overridetasks")) { doc.LoadTasks(parseContext, "(core overridetasks)", t); } } catch (Exception ex) when(parseContext.IsNotCancellation(ex)) { LoggingService.LogError("Error resolving tasks", ex); } try { if (previous != null) { // try to recover some values that may have been collected from the imports, as they // will not have been re-evaluated var fx = previous.Frameworks.FirstOrDefault(); if (fx != null) { propVals.Collect("TargetFramework", fx.GetShortFolderName()); propVals.Collect("TargetFrameworkVersion", FrameworkInfoProvider.FormatDisplayVersion(fx.Version)); propVals.Collect("TargetFrameworkIdentifier", fx.Framework); } } doc.Frameworks = propVals.GetFrameworks(); } catch (Exception ex) { LoggingService.LogError("Error determining project framework", ex); doc.Frameworks = new List <NuGetFramework> (); } try { //this has to run in a second pass so that it runs after all the schemas are loaded var validator = new MSBuildDocumentValidator(); validator.Run(doc.XDocument, textSource, doc); } catch (Exception ex) when(parseContext.IsNotCancellation(ex)) { LoggingService.LogError("Error in validation", ex); } return(doc); }
public static MSBuildRootDocument Parse( string filename, ITextSource textSource, MSBuildRootDocument previous, MSBuildSchemaProvider schemaProvider, IRuntimeInformation runtimeInfo, CancellationToken token) { var xmlParser = new XmlParser(new XmlRootState(), true); try { xmlParser.Parse(textSource.CreateReader()); } catch (Exception ex) { LoggingService.LogError("Unhandled error parsing xml document", ex); } var xdocument = xmlParser.Nodes.GetRoot(); if (xdocument != null && xdocument.RootElement != null) { if (!xdocument.RootElement.IsEnded) { xdocument.RootElement.End(xmlParser.Location); } } //FIXME: unfortunately the XML parser's regions only have line+col locations, not offsets //so we need to create an ITextDocument to extract tag bodies //we should fix this by changing the parser to use offsets for the tag locations ITextDocument textDoc = textSource as ITextDocument ?? TextEditorFactory.CreateNewDocument(textSource, filename, MSBuildTextEditorExtension.MSBuildMimeType); var propVals = new PropertyValueCollector(true); string projectPath = filename; var doc = new MSBuildRootDocument(filename) { XDocument = xdocument, Text = textDoc, RuntimeInformation = runtimeInfo }; doc.Errors.AddRange(xmlParser.Errors); try { doc.Schema = previous?.Schema ?? schemaProvider.GetSchema(filename, null); } catch (Exception ex) { LoggingService.LogError("Error loading schema", ex); } var importedFiles = new HashSet <string> (StringComparer.OrdinalIgnoreCase) { filename }; var taskBuilder = new TaskMetadataBuilder(doc); var extension = Path.GetExtension(filename); string MakeRelativeMSBuildPathAbsolute(string path) { var dir = Path.GetDirectoryName(doc.Filename); path = path.Replace('\\', Path.DirectorySeparatorChar); return(Path.GetFullPath(Path.Combine(dir, path))); } Import TryImportFile(string possibleFile) { try { var fi = new FileInfo(possibleFile); if (fi.Exists) { var imp = doc.GetCachedOrParse(importedFiles, previous, possibleFile, null, fi.LastWriteTimeUtc, projectPath, propVals, taskBuilder, schemaProvider, token); doc.Imports.Add(possibleFile, imp); return(imp); } } catch (Exception ex) { LoggingService.LogError($"Error importing '{possibleFile}'", ex); } return(null); } Import TryImportSibling(string ifHasThisExtension, string thenTryThisExtension) { if (string.Equals(ifHasThisExtension, extension, StringComparison.OrdinalIgnoreCase)) { var siblingFilename = Path.ChangeExtension(filename, thenTryThisExtension); return(TryImportFile(siblingFilename)); } return(null); } void TryImportIntellisenseImports(MSBuildSchema schema) { foreach (var intellisenseImport in schema.IntelliSenseImports) { TryImportFile(MakeRelativeMSBuildPathAbsolute(intellisenseImport)); } } try { //if this is a targets file, try to import the props _at the top_ var propsImport = TryImportSibling(".targets", ".props"); // this currently only happens in the root file // it's a quick hack to allow files to get some basic intellisense by // importing the files _that they themselves expect to be imported from_. // we also try to load them from the sibling props, as a paired targets/props // will likely share a schema file. var schema = doc.Schema ?? propsImport?.Document?.Schema; if (schema != null) { TryImportIntellisenseImports(doc.Schema); } doc.Build( xdocument, textDoc, runtimeInfo, propVals, taskBuilder, (imp, sdk) => doc.ResolveImport(importedFiles, previous, projectPath, filename, imp, sdk, propVals, taskBuilder, schemaProvider, token) ); //if this is a props file, try to import the targets _at the bottom_ var targetsImport = TryImportSibling(".props", ".targets"); //and if we didn't load intellisense import already, try to load them from the sibling targets if (schema == null && targetsImport?.Document?.Schema != null) { TryImportIntellisenseImports(targetsImport.Document.Schema); } } catch (Exception ex) { LoggingService.LogError($"Error building document '{projectPath}'", ex); } try { var binpath = doc.RuntimeInformation.GetBinPath(); foreach (var t in Directory.GetFiles(binpath, "*.tasks")) { doc.LoadTasks(importedFiles, previous, t, propVals, taskBuilder, schemaProvider, token); } foreach (var t in Directory.GetFiles(binpath, "*.overridetasks")) { doc.LoadTasks(importedFiles, previous, t, propVals, taskBuilder, schemaProvider, token); } } catch (Exception ex) { LoggingService.LogError("Error resolving tasks", ex); } try { if (previous != null) { // try to recover some values that may have been collected from the imports, as they // will not have been re-evaluated var fx = previous.Frameworks.FirstOrDefault(); if (fx != null) { propVals.Collect("TargetFramework", fx.GetShortFolderName()); propVals.Collect("TargetFrameworkVersion", FrameworkInfoProvider.FormatDisplayVersion(fx.Version)); propVals.Collect("TargetFrameworkIdentifier", fx.Framework); } } doc.Frameworks = propVals.GetFrameworks(); } catch (Exception ex) { LoggingService.LogError("Error determining project framework", ex); doc.Frameworks = new List <NuGetFramework> (); } try { //this has to run in a second pass so that it runs after all the schemas are loaded var validator = new MSBuildDocumentValidator(); validator.Run(doc.XDocument, filename, textDoc, doc); } catch (Exception ex) { LoggingService.LogError("Error in validation", ex); } return(doc); }
//note: the value is unescaped, so offsets within it are not valid void VisitPureLiteral(ValueInfo info, MSBuildValueKind kind, string value, int offset) { IReadOnlyList <ConstantInfo> knownVals = info.Values ?? kind.GetSimpleValues(false); if (knownVals != null && knownVals.Count != 0) { foreach (var kv in knownVals) { if (string.Equals(kv.Name, value, StringComparison.OrdinalIgnoreCase)) { return; } } AddErrorWithArgs(CoreDiagnostics.UnknownValue, DescriptionFormatter.GetKindNoun(info), info.Name, value); return; } switch (kind) { case MSBuildValueKind.Guid: case MSBuildValueKind.ProjectKindGuid: if (!Guid.TryParseExact(value, "B", out _)) { AddErrorWithArgs(CoreDiagnostics.InvalidGuid, value); } break; case MSBuildValueKind.Int: if (!long.TryParse(value, out _)) { AddErrorWithArgs(CoreDiagnostics.InvalidInteger, value); } break; case MSBuildValueKind.Bool: if (!bool.TryParse(value, out _)) { AddErrorWithArgs(CoreDiagnostics.InvalidBool, value); } break; case MSBuildValueKind.Url: if (!Uri.TryCreate(value, UriKind.Absolute, out _)) { AddErrorWithArgs(CoreDiagnostics.InvalidUrl, value); } break; case MSBuildValueKind.Version: if (!Version.TryParse(value, out _)) { AddErrorWithArgs(CoreDiagnostics.InvalidVersion, value); } break; /* * FIXME: these won't work as-is, as inference will add them to the schema * case MSBuildValueKind.TargetName: * if (Document.GetSchemas ().GetTarget (value) == null) { * AddErrorWithArgs (CoreDiagnostics.UndefinedTarget, value); * } * break; * case MSBuildValueKind.PropertyName: * if (Document.GetSchemas ().GetProperty (value) == null) { * AddErrorWithArgs (CoreDiagnostics.UnknownProperty, value); * } * break; * case MSBuildValueKind.ItemName: * if (Document.GetSchemas ().GetItem (value) == null) { * AddErrorWithArgs (CoreDiagnostics.UnknownProperty, value); * } * break; */ case MSBuildValueKind.Lcid: if (int.TryParse(value, out int lcid) && lcid > 0) { try { CultureInfo.GetCultureInfo(lcid); } catch (CultureNotFoundException) { AddErrorWithArgs(CoreDiagnostics.UnknownLcid, value); } } else { AddErrorWithArgs(CoreDiagnostics.InvalidLcid, value); } break; case MSBuildValueKind.TargetFramework: if (!FrameworkInfoProvider.Instance.IsFrameworkShortNameValid(value)) { AddErrorWithArgs(CoreDiagnostics.UnknownTargetFramework, value); } break; case MSBuildValueKind.TargetFrameworkIdentifier: if (!FrameworkInfoProvider.Instance.IsFrameworkIdentifierValid(value)) { AddErrorWithArgs(CoreDiagnostics.UnknownTargetFrameworkIdentifier, value); } break; case MSBuildValueKind.TargetFrameworkVersion: { if (!Version.TryParse(value.TrimStart('v', 'V'), out Version fxv)) { AddErrorWithArgs(CoreDiagnostics.InvalidVersion, value); break; } fxv = new Version(Math.Max(fxv.Major, 0), Math.Max(fxv.Minor, 0), Math.Max(fxv.Revision, 0), Math.Max(fxv.Build, 0)); if (Document is MSBuildRootDocument d && d.Frameworks.Count > 0) { bool foundMatch = false; foreach (var fx in d.Frameworks) { if (FrameworkInfoProvider.AreVersionsEquivalent(fx.Version, fxv) && FrameworkInfoProvider.Instance.IsFrameworkVersionValid(fx.Framework, fxv)) { foundMatch = true; } } if (!foundMatch) { AddErrorWithArgs(CoreDiagnostics.UnknownTargetFrameworkVersion, value, d.Frameworks[0].Framework); } } break; } case MSBuildValueKind.TargetFrameworkProfile: { if (Document is MSBuildRootDocument d && d.Frameworks.Count > 0) { bool foundMatch = false; foreach (var fx in d.Frameworks) { if (fx.Profile == value && FrameworkInfoProvider.Instance.IsFrameworkProfileValid(fx.Framework, fx.Version, value)) { foundMatch = true; } } if (!foundMatch) { AddErrorWithArgs(CoreDiagnostics.UnknownTargetFrameworkProfile, value, d.Frameworks[0].Framework, d.Frameworks[0].Version); } } break; } } void AddError(MSBuildDiagnosticDescriptor d) => Document.Diagnostics.Add(d, new TextSpan(offset, value.Length)); void AddErrorWithArgs(MSBuildDiagnosticDescriptor d, params object[] args) => Document.Diagnostics.Add(d, new TextSpan(offset, value.Length), args); }
public static MSBuildRootDocument Parse( string filename, ITextSource textSource, MSBuildRootDocument previous, MSBuildSchemaProvider schemaProvider, IRuntimeInformation runtimeInfo, CancellationToken token) { var xmlParser = new XmlParser(new XmlRootState(), true); try { xmlParser.Parse(textSource.CreateReader()); } catch (Exception ex) { LoggingService.LogError("Unhandled error parsing xml document", ex); } var xdocument = xmlParser.Nodes.GetRoot(); if (xdocument != null && xdocument.RootElement != null) { if (!xdocument.RootElement.IsEnded) { xdocument.RootElement.End(xmlParser.Location); } } //FIXME: unfortunately the XML parser's regions only have line+col locations, not offsets //so we need to create an ITextDocument to extract tag bodies //we should fix this by changing the parser to use offsets for the tag locations ITextDocument textDoc = textSource as ITextDocument ?? TextEditorFactory.CreateNewDocument(textSource, filename, MSBuildTextEditorExtension.MSBuildMimeType); var propVals = new PropertyValueCollector(true); string projectPath = filename; var doc = new MSBuildRootDocument(filename); doc.XDocument = xdocument; doc.Text = textDoc; doc.RuntimeInformation = runtimeInfo; doc.Errors.AddRange(xmlParser.Errors); var importedFiles = new HashSet <string> (StringComparer.OrdinalIgnoreCase); importedFiles.Add(filename); var taskBuilder = new TaskMetadataBuilder(doc); try { doc.Build( xdocument, textDoc, runtimeInfo, propVals, taskBuilder, (imp, sdk) => doc.ResolveImport(importedFiles, previous, projectPath, filename, imp, sdk, propVals, taskBuilder, schemaProvider, token) ); } catch (Exception ex) { LoggingService.LogError("Error building document", ex); } try { var binpath = doc.RuntimeInformation.GetBinPath(); foreach (var t in Directory.GetFiles(binpath, "*.tasks")) { doc.LoadTasks(importedFiles, previous, t, propVals, taskBuilder, schemaProvider, token); } foreach (var t in Directory.GetFiles(binpath, "*.overridetasks")) { doc.LoadTasks(importedFiles, previous, t, propVals, taskBuilder, schemaProvider, token); } } catch (Exception ex) { LoggingService.LogError("Error resolving tasks", ex); } try { if (previous != null) { // try to recover some values that may have been collected from the imports, as they // will not have been re-evaluated var fx = previous.Frameworks.FirstOrDefault(); if (fx != null) { propVals.Collect("TargetFramework", fx.GetShortFolderName()); propVals.Collect("TargetFrameworkVersion", FrameworkInfoProvider.FormatDisplayVersion(fx.Version)); propVals.Collect("TargetFrameworkIdentifier", fx.Framework); } } doc.Frameworks = propVals.GetFrameworks(); } catch (Exception ex) { LoggingService.LogError("Error determining project framework", ex); doc.Frameworks = new List <NuGetFramework> (); } try { doc.Schema = previous?.Schema ?? schemaProvider.GetSchema(filename, null); } catch (Exception ex) { LoggingService.LogError("Error loading schema", ex); } try { //this has to run in a second pass so that it runs after all the schemas are loaded var validator = new MSBuildDocumentValidator(); validator.Run(doc.XDocument, filename, textDoc, doc); } catch (Exception ex) { LoggingService.LogError("Error in validation", ex); } return(doc); }