예제 #1
0
        /// <summary>
        /// Performs the first compilation pass, which generates expression getters in order to verify that the expressions are valid code.
        /// </summary>
        private static CompilerResults PerformExpressionVerificationCompilationPass(LegacyExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, ConcurrentBag <String> referencedAssemblies, Boolean debug)
        {
            Parallel.ForEach(models, model =>
            {
                var lastSeenDataSourceAssembly = default(Assembly);
                for (var dataSourceType = model.DataSourceType; dataSourceType != null; dataSourceType = dataSourceType.BaseType)
                {
                    var dataSourceAssembly = dataSourceType.Assembly;
                    if (dataSourceAssembly != lastSeenDataSourceAssembly)
                    {
                        lastSeenDataSourceAssembly = dataSourceAssembly;
                        referencedAssemblies.Add(dataSourceAssembly.Location);
                    }
                }

                foreach (var reference in model.References)
                {
                    referencedAssemblies.Add(reference);
                }

                foreach (var expression in model.Expressions)
                {
                    expression.GenerateGetter = true;
                    expression.GenerateSetter = false;
                }

                WriteSourceCodeForDataSourceWrapper(state, model);
            });

            return(CompileDataSourceWrapperSources(state, null, models, referencedAssemblies, debug));
        }
예제 #2
0
        /// <summary>
        /// Compiles the specified data source wrapper sources into a managed assembly.
        /// </summary>
        private static CompilerResults CompileDataSourceWrapperSources(LegacyExpressionCompilerState state, String output, IEnumerable <DataSourceWrapperInfo> infos, IEnumerable <String> references, Boolean debug)
        {
            var options = new CompilerParameters();

            options.OutputAssembly          = output;
            options.GenerateExecutable      = false;
            options.GenerateInMemory        = true;
            options.IncludeDebugInformation = debug;
            options.TreatWarningsAsErrors   = false;
            options.ReferencedAssemblies.AddRange(references.Distinct().ToArray());

            var files = new List <String>();

            files.Add(WriteCompilerMetadataFile());

            foreach (var info in infos)
            {
                var path = state.GetWorkingFileForDataSourceWrapper(info);
                files.Add(path);

                File.WriteAllText(path, info.DataSourceWrapperSourceCode);
            }

            return(state.Compiler.CompileAssemblyFromFile(options, files.ToArray()));
        }
예제 #3
0
        /// <summary>
        /// Writes the source code of the specified collection of wrappers to the working directory.
        /// </summary>
        private static void WriteCompiledFilesToWorkingDirectory(LegacyExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models)
        {
            var workingDirectory = state.GetWorkingDirectory();

            Directory.CreateDirectory(workingDirectory);

            foreach (var model in models)
            {
                var path = Path.ChangeExtension(Path.Combine(workingDirectory, model.DataSourceWrapperName), "cs");
                File.WriteAllText(path, model.DataSourceWrapperSourceCode);
            }
        }
예제 #4
0
        /// <inheritdoc/>
        public BindingExpressionCompilationResult Compile(UltravioletContext uv, BindingExpressionCompilerOptions options)
        {
            Contract.Require(uv, nameof(uv));
            Contract.Require(options, nameof(options));

            if (String.IsNullOrEmpty(options.Input) || String.IsNullOrEmpty(options.Output))
            {
                throw new ArgumentException(PresentationStrings.InvalidCompilerOptions);
            }

            var compiler = CreateCodeProvider();
            var state    = new LegacyExpressionCompilerState(uv, compiler)
            {
                GenerateInMemory         = options.GenerateInMemory,
                WorkInTemporaryDirectory = options.WorkInTemporaryDirectory,
                WriteErrorsToFile        = options.WriteErrorsToFile
            };
            var dataSourceWrapperInfos = DataSourceLoader.GetDataSourceWrapperInfos(state, options.Input);

            var cacheFile = Path.ChangeExtension(options.Output, "cache");
            var cacheNew  = CompilerCache.FromDataSourceWrappers(this, dataSourceWrapperInfos);

            if (File.Exists(options.Output))
            {
                var cacheOld = CompilerCache.TryFromFile(cacheFile);
                if (cacheOld != null && !options.IgnoreCache && !cacheOld.IsDifferentFrom(cacheNew))
                {
                    return(BindingExpressionCompilationResult.CreateSucceeded());
                }
            }

            var result = CompileViewModels(state, dataSourceWrapperInfos, options.Output, options.GenerateDebugAssembly);

            if (result.Succeeded)
            {
                if (!options.GenerateInMemory)
                {
                    cacheNew.Save(cacheFile);
                }

                if (!options.WriteCompiledFilesToWorkingDirectory && !options.WorkInTemporaryDirectory)
                {
                    state.DeleteWorkingDirectory();
                }
            }

            if (options.WriteCompiledFilesToWorkingDirectory && !options.WorkInTemporaryDirectory)
            {
                WriteCompiledFilesToWorkingDirectory(state, dataSourceWrapperInfos);
            }

            return(result);
        }
예제 #5
0
        /// <summary>
        /// Performs the second compilation pass, which generates setters in order to determine which expressions support two-way bindings.
        /// </summary>
        private static CompilerResults PerformSetterEliminationCompilationPass(LegacyExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, ConcurrentBag <String> referencedAssemblies, Boolean debug)
        {
            Parallel.ForEach(models, model =>
            {
                foreach (var expression in model.Expressions)
                {
                    expression.GenerateGetter = true;
                    expression.GenerateSetter = true;
                }

                WriteSourceCodeForDataSourceWrapper(state, model);
            });
            return(CompileDataSourceWrapperSources(state, null, models, referencedAssemblies, debug));
        }
예제 #6
0
        /// <summary>
        /// Writes any compiler errors to the working directory.
        /// </summary>
        private static void WriteErrorsToWorkingDirectory(LegacyExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, CompilerResults results)
        {
            var logpath = Path.Combine(state.GetWorkingDirectory(), "Compilation Errors.txt");

            try
            {
                File.Delete(logpath);
            }
            catch (DirectoryNotFoundException) { }

            var logdir = Path.GetDirectoryName(logpath);

            Directory.CreateDirectory(logdir);

            // NOTE: Under Mono we seem to get warnings even when "Treat Warnings as Errors" is turned off.
            var trueErrors = results.Errors.Cast <CompilerError>().Where(x => !x.IsWarning).ToList();

            if (trueErrors.Count > 0)
            {
                var filesWithErrors       = trueErrors.Select(x => x.FileName).Where(x => !String.IsNullOrEmpty(x)).Distinct();
                var filesWithErrorsPretty = new Dictionary <String, String> {
                    { String.Empty, String.Empty }
                };

                foreach (var fileWithErrors in filesWithErrors)
                {
                    var modelNameForFile = models.Where(x => x.UniqueID.ToString() == Path.GetFileNameWithoutExtension(fileWithErrors))
                                           .Select(x => x.DataSourceWrapperName).SingleOrDefault();

                    var prettyFileName = Path.ChangeExtension(modelNameForFile, "cs");
                    filesWithErrorsPretty[fileWithErrors] = prettyFileName;

                    var fileWithErrorsSrc = Path.GetFullPath(fileWithErrors);
                    var fileWithErrorsDst = Path.Combine(state.GetWorkingDirectory(), prettyFileName);
                    File.Copy(fileWithErrorsSrc, fileWithErrorsDst, true);
                }

                var errorStrings = trueErrors.Select(x =>
                                                     String.Format("{0}\t{1}\t{2}\t{3}", x.ErrorNumber, x.ErrorText, filesWithErrorsPretty[x.FileName ?? String.Empty], x.Line));

                File.WriteAllLines(logpath, Enumerable.Union(new[] { "Code\tDescription\tFile\tLine" }, errorStrings));
            }
        }
예제 #7
0
        /// <summary>
        /// Compiles the specified collection of view models.
        /// </summary>
        private static BindingExpressionCompilationResult CompileViewModels(LegacyExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, String output, Boolean debug)
        {
            state.DeleteWorkingDirectory();

            var referencedAssemblies = GetDefaultReferencedAssemblies();

            var expressionVerificationResult =
                PerformExpressionVerificationCompilationPass(state, models, referencedAssemblies, debug);

            if (expressionVerificationResult.Errors.Cast <CompilerError>().Where(x => !x.IsWarning).Any())
            {
                if (state.WriteErrorsToFile)
                {
                    WriteErrorsToWorkingDirectory(state, models, expressionVerificationResult);
                }

                return(BindingExpressionCompilationResult.CreateFailed(CompilerStrings.FailedExpressionValidationPass,
                                                                       CreateBindingExpressionCompilationErrors(state, models, expressionVerificationResult.Errors)));
            }

            var setterEliminationPassResult =
                PerformSetterEliminationCompilationPass(state, models, referencedAssemblies, debug);

            var conversionFixupPassResult =
                PerformConversionFixupCompilationPass(state, models, referencedAssemblies, setterEliminationPassResult, debug);

            var finalPassResult =
                PerformFinalCompilationPass(state, state.GenerateInMemory ? null : output, models, referencedAssemblies, conversionFixupPassResult, debug);

            if (finalPassResult.Errors.Cast <CompilerError>().Where(x => !x.IsWarning).Any())
            {
                if (state.WriteErrorsToFile)
                {
                    WriteErrorsToWorkingDirectory(state, models, finalPassResult);
                }

                return(BindingExpressionCompilationResult.CreateFailed(CompilerStrings.FailedFinalPass,
                                                                       CreateBindingExpressionCompilationErrors(state, models, finalPassResult.Errors)));
            }

            return(BindingExpressionCompilationResult.CreateSucceeded(finalPassResult.CompiledAssembly));
        }
예제 #8
0
        /// <inheritdoc/>
        public BindingExpressionCompilationResult CompileSingleView(UltravioletContext uv, BindingExpressionCompilerOptions options)
        {
            Contract.Require(options, nameof(options));

            if (String.IsNullOrEmpty(options.Input))
            {
                throw new ArgumentException(PresentationStrings.InvalidCompilerOptions);
            }

            var definition = DataSourceLoader.CreateDataSourceDefinitionFromXml(options.RequestedViewModelNamespace, options.RequestedViewModelName, options.Input);

            if (definition == null)
            {
                return(BindingExpressionCompilationResult.CreateSucceeded());
            }

            var compiler = CreateCodeProvider();
            var state    = new LegacyExpressionCompilerState(uv, compiler)
            {
                GenerateInMemory         = options.GenerateInMemory,
                WorkInTemporaryDirectory = options.WorkInTemporaryDirectory,
                WriteErrorsToFile        = options.WriteErrorsToFile
            };
            var dataSourceWrapperInfo  = DataSourceLoader.GetDataSourceWrapperInfo(state, definition.Value);
            var dataSourceWrapperInfos = new[] { dataSourceWrapperInfo };

            var result = CompileViewModels(state, dataSourceWrapperInfos, null, options.GenerateDebugAssembly);

            if (result.Succeeded)
            {
                options.Output = dataSourceWrapperInfos[0].DataSourceWrapperSourceCode;
            }
            else
            {
                state.DeleteWorkingDirectory();
            }

            return(result);
        }
예제 #9
0
        /// <summary>
        /// Performs the final compilation pass, which removes invalid expression setters based on the results of the previous pass.
        /// </summary>
        private static CompilerResults PerformFinalCompilationPass(LegacyExpressionCompilerState state, String output, IEnumerable <DataSourceWrapperInfo> models, ConcurrentBag <String> referencedAssemblies, CompilerResults nullableFixupResult, Boolean debug)
        {
            var errors = nullableFixupResult.Errors.Cast <CompilerError>().ToList();

            Parallel.ForEach(models, model =>
            {
                var dataSourceWrapperFilename = Path.GetFileName(state.GetWorkingFileForDataSourceWrapper(model));
                var dataSourceWrapperErrors   = errors.Where(x => Path.GetFileName(x.FileName) == dataSourceWrapperFilename).ToList();

                foreach (var expression in model.Expressions)
                {
                    if (expression.GenerateSetter && dataSourceWrapperErrors.Any(x => x.Line >= expression.SetterLineStart && x.Line <= expression.SetterLineEnd))
                    {
                        expression.GenerateSetter = false;
                    }
                }

                WriteSourceCodeForDataSourceWrapper(state, model);
            });

            return(CompileDataSourceWrapperSources(state, output, models, referencedAssemblies, debug));
        }
예제 #10
0
        /// <summary>
        /// Performs the third compilation pass, which attempts to fix any errors caused by non-implicit conversions and nullable types that need to be cast to non-nullable types.
        /// </summary>
        private static CompilerResults PerformConversionFixupCompilationPass(LegacyExpressionCompilerState state, IEnumerable <DataSourceWrapperInfo> models, ConcurrentBag <String> referencedAssemblies, CompilerResults setterEliminationResult, Boolean debug)
        {
            var errors = setterEliminationResult.Errors.Cast <CompilerError>().ToList();

            var fixableErrorNumbers = new List <String>
            {
                "CS0266",
                "CS1502",
                "CS1503"
            };

            Parallel.ForEach(models, model =>
            {
                var dataSourceWrapperFilename = Path.GetFileName(state.GetWorkingFileForDataSourceWrapper(model));
                var dataSourceWrapperErrors   = errors.Where(x => Path.GetFileName(x.FileName) == dataSourceWrapperFilename).ToList();

                foreach (var expression in model.Expressions)
                {
                    var setterErrors     = dataSourceWrapperErrors.Where(x => x.Line >= expression.SetterLineStart && x.Line <= expression.SetterLineEnd).ToList();
                    var setterIsNullable = Nullable.GetUnderlyingType(expression.Type) != null;

                    expression.GenerateSetter = !setterErrors.Any() || (setterIsNullable && setterErrors.All(x => fixableErrorNumbers.Contains(x.ErrorNumber)));
                    expression.NullableFixup  = setterIsNullable;

                    if (setterErrors.Count == 1 && setterErrors.Single().ErrorNumber == "CS0266")
                    {
                        var error = setterErrors.Single();
                        var match = regexCS0266.Match(error.ErrorText);
                        expression.CS0266SourceType = match.Groups["source"].Value;
                        expression.CS0266TargetType = match.Groups["target"].Value;
                        expression.GenerateSetter   = true;
                    }
                }

                WriteSourceCodeForDataSourceWrapper(state, model);
            });
            return(CompileDataSourceWrapperSources(state, null, models, referencedAssemblies, debug));
        }
예제 #11
0
        /// <summary>
        /// Writes the source code for the specified data source wrapper.
        /// </summary>
        private static void WriteSourceCodeForDataSourceWrapper(LegacyExpressionCompilerState state,
                                                                DataSourceWrapperInfo dataSourceWrapperInfo)
        {
            using (var writer = new DataSourceWrapperWriter())
            {
                // Using statements
                var imports = Enumerable.Union(new[] { "System" }, dataSourceWrapperInfo.Imports).Distinct().OrderBy(x => x);
                foreach (var import in imports)
                {
                    writer.WriteLine("using {0};", import);
                }
                writer.WriteLine();

                // Namespace declaration
                var @namespace = dataSourceWrapperInfo.DataSourceDefinition.DataSourceWrapperNamespace;
                writer.WriteLine("namespace " + @namespace);
                writer.WriteLine("{");
                writer.WriteLine("#pragma warning disable 1591");
                writer.WriteLine("#pragma warning disable 0184");

                // Data source wrapper class - main
                WriteSourceCodeForDataSourceWrapperClass(state, dataSourceWrapperInfo, writer);

                // Data source wrapper class - dependent
                foreach (var dependentWrapperInfo in dataSourceWrapperInfo.DependentWrapperInfos)
                {
                    WriteSourceCodeForDataSourceWrapperClass(state, dependentWrapperInfo, writer);
                }

                // Namespace complete
                writer.WriteLine("#pragma warning restore 0184");
                writer.WriteLine("#pragma warning restore 1591");
                writer.WriteLine("}");

                dataSourceWrapperInfo.DataSourceWrapperSourceCode = writer.ToString();
            }
        }
예제 #12
0
        /// <summary>
        /// Converts a <see cref="CompilerErrorCollection"/> to a collection of <see cref="BindingExpressionCompilationError"/> objects.
        /// </summary>
        private static List <BindingExpressionCompilationError> CreateBindingExpressionCompilationErrors(LegacyExpressionCompilerState state,
                                                                                                         IEnumerable <DataSourceWrapperInfo> models, CompilerErrorCollection errors)
        {
            var result = new List <BindingExpressionCompilationError>();

            var workingDirectory = state.GetWorkingDirectory();

            var errorsByFile = errors.Cast <CompilerError>()
                               .Where(x => !x.IsWarning).GroupBy(x => Path.GetFileName(x.FileName)).ToDictionary(x => x.Key, x => x.ToList());

            foreach (var model in models)
            {
                var dataSourceWrapperFilename = Path.GetFileName(state.GetWorkingFileForDataSourceWrapper(model));
                var dataSourceErrors          = default(List <CompilerError>);
                if (errorsByFile.TryGetValue(dataSourceWrapperFilename, out dataSourceErrors))
                {
                    foreach (var dataSourceError in dataSourceErrors)
                    {
                        var fullPathToFile = model.DataSourceWrapperName;
                        if (state.WriteErrorsToFile)
                        {
                            fullPathToFile = Path.GetFullPath(Path.Combine(workingDirectory,
                                                                           Path.ChangeExtension(model.DataSourceWrapperName, "cs")));
                        }

                        result.Add(new BindingExpressionCompilationError(fullPathToFile,
                                                                         dataSourceError.Line, dataSourceError.Column, dataSourceError.ErrorNumber, dataSourceError.ErrorText));
                    }
                }
            }

            return(result);
        }
예제 #13
0
        /// <summary>
        /// Writes the source code for an individual data source wrapper class.
        /// </summary>
        private static void WriteSourceCodeForDataSourceWrapperClass(LegacyExpressionCompilerState state,
                                                                     DataSourceWrapperInfo dataSourceWrapperInfo, DataSourceWrapperWriter writer)
        {
            // Class declaration
            writer.WriteLine("// Generated by the UPF Binding Expression Compiler, version {0}", FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion);
            writer.WriteLine("[System.CLSCompliant(false)]");
            writer.WriteLine("[Ultraviolet.Presentation.WrappedDataSource(typeof({0}))]", CSharpLanguage.GetCSharpTypeName(dataSourceWrapperInfo.DataSourceType));
            writer.WriteLine("public sealed partial class {0} : {1}", dataSourceWrapperInfo.DataSourceWrapperName, CSharpLanguage.GetCSharpTypeName(typeof(CompiledDataSourceWrapper)));
            writer.WriteLine("{");

            // Constructors
            writer.WriteLine("#region Constructors");
            writer.WriteConstructor(dataSourceWrapperInfo);
            writer.WriteLine("#endregion");
            writer.WriteLine();

            // IDataSourceWrapper
            writer.WriteLine("#region IDataSourceWrapper");
            writer.WriteIDataSourceWrapperImplementation(dataSourceWrapperInfo);
            writer.WriteLine("#endregion");
            writer.WriteLine();

            // Methods
            writer.WriteLine("#region Methods");
            var methods = dataSourceWrapperInfo.DataSourceType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

            foreach (var method in methods)
            {
                if (!ExpressionUtil.NeedsWrapper(method))
                {
                    continue;
                }

                writer.WriteWrapperMethod(method);
            }
            writer.WriteLine("#endregion");
            writer.WriteLine();

            // Properties
            writer.WriteLine("#region Properties");
            var properties = dataSourceWrapperInfo.DataSourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

            foreach (var property in properties)
            {
                if (!ExpressionUtil.NeedsWrapper(property))
                {
                    continue;
                }

                writer.WriteWrapperProperty(property);
            }
            writer.WriteLine("#endregion");
            writer.WriteLine();

            // Fields
            writer.WriteLine("#region Fields");
            var fields = dataSourceWrapperInfo.DataSourceType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

            foreach (var field in fields)
            {
                if (!ExpressionUtil.NeedsWrapper(field))
                {
                    continue;
                }

                writer.WriteWrapperProperty(field);
            }
            writer.WriteLine("#endregion");
            writer.WriteLine();

            // Expressions
            writer.WriteLine("#region Expressions");
            for (int i = 0; i < dataSourceWrapperInfo.Expressions.Count; i++)
            {
                var expressionInfo = dataSourceWrapperInfo.Expressions[i];
                writer.WriteExpressionProperty(state, dataSourceWrapperInfo, expressionInfo, i);
            }
            writer.WriteLine("#endregion");
            writer.WriteLine();

            // Special-case binding delegates
            writer.WriteLine("#region Binding Delegates");
            if (typeof(Controls.ContentControl).IsAssignableFrom(dataSourceWrapperInfo.DataSourceType))
            {
                // ContentControl
                writer.WriteLine("public static readonly DataBindingGetter<System.Object> __GetContent = " +
                                 "new DataBindingGetter<System.Object>(vm => (({0})vm).Content);", dataSourceWrapperInfo.DataSourceWrapperName);
                writer.WriteLine("public static readonly DataBindingGetter<System.String> __GetContentStringFormat = " +
                                 "new DataBindingGetter<System.String>(vm => (({0})vm).ContentStringFormat);", dataSourceWrapperInfo.DataSourceWrapperName);
                writer.WriteLine("public static readonly DataBindingSetter<System.Object> __SetContent = " +
                                 "new DataBindingSetter<System.Object>((vm, value) => (({0})vm).Content = value);", dataSourceWrapperInfo.DataSourceWrapperName);
                writer.WriteLine("public static readonly DataBindingSetter<System.String> __SetContentStringFormat = " +
                                 "new DataBindingSetter<System.String>((vm, value) => (({0})vm).ContentStringFormat = value);", dataSourceWrapperInfo.DataSourceWrapperName);
            }
            writer.WriteLine("#endregion");

            // Class complete
            writer.WriteLine("}");
        }