<commit_before>﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Cake.Core;
using Cake.Core.Configuration;
using Cake.Core.Diagnostics;
using Cake.Core.IO;
using Cake.Core.Scripting;
using Cake.Core.Scripting.Analysis;
using Cake.Core.Scripting.Processors.Loading;
using Cake.Scripting.Abstractions;
using Cake.Scripting.Abstractions.Models;
using Cake.Scripting.CodeGen.Generators;
using Cake.Scripting.IO;
using Cake.Scripting.Reflection.Emitters;

namespace Cake.Scripting.CodeGen
{
    public sealed class CakeScriptGenerator : IScriptGenerationService
    {
        private readonly ICakeEnvironment _environment;
        private readonly ICakeLog _log;
        private readonly ICakeConfiguration _configuration;
        private readonly IGlobber _globber;
        private readonly IScriptAnalyzer _analyzer;
        private readonly IScriptProcessor _processor;
        private readonly IBufferedFileSystem _fileSystem;
        private readonly IScriptAliasFinder _aliasFinder;
        private readonly CakeMethodAliasGenerator _methodGenerator;
        private readonly CakePropertyAliasGenerator _propertyGenerator;

        public CakeScriptGenerator(
            IBufferedFileSystem fileSystem,
            ICakeEnvironment environment,
            IGlobber globber,
            ICakeConfiguration configuration,
            IScriptProcessor processor,
            IScriptAliasFinder aliasFinder,
            ICakeLog log,
            IEnumerable<ILoadDirectiveProvider> loadDirectiveProviders = null)
        {
            _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
            _environment = environment ?? throw new ArgumentNullException(nameof(environment));
            _globber = globber ?? throw new ArgumentNullException(nameof(globber));
            _log = log ?? throw new ArgumentNullException(nameof(log));
            _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
            _processor = processor ?? throw new ArgumentNullException(nameof(processor));
            _aliasFinder = aliasFinder ?? throw new ArgumentNullException(nameof(fileSystem));

            _analyzer = new ScriptAnalyzer(_fileSystem, _environment, _log, loadDirectiveProviders);

            var typeEmitter = new TypeEmitter();
            var parameterEmitter = new ParameterEmitter(typeEmitter);

            _methodGenerator = new CakeMethodAliasGenerator(typeEmitter, parameterEmitter);
            _propertyGenerator = new CakePropertyAliasGenerator(typeEmitter);
        }

        public CakeScript Generate(FileChange fileChange)
        {
            if (fileChange == null)
            {
                throw new ArgumentNullException(nameof(fileChange));
            }

            // Make the script path absolute.
            var scriptPath = new FilePath(fileChange.FileName).MakeAbsolute(_environment);

            // Prepare the file changes
            _log.Verbose("Handling file change...");
            HandleFileChange(scriptPath, fileChange);

            // Analyze the script file.
            _log.Verbose("Analyzing build script...");
            var result = _analyzer.Analyze(scriptPath);

            // Install addins.
            var addinRoot = GetAddinPath(_environment.WorkingDirectory);
            foreach (var addin in result.Addins)
            {
                try
                {
                    _log.Verbose("Installing addins...");
                    var addinReferences = _processor.InstallAddins(new[] { addin }, addinRoot);
                    foreach (var addinReference in addinReferences)
                    {
                        result.References.Add(addinReference.FullPath);
                    }
                }
                catch (Exception e)
                {
                    // Log and continue if it fails
                    _log.Error(e);
                }
            }

            // Load all references.
            _log.Verbose("Adding references...");
            var cakeRoot = GetCakePath(GetToolPath(_environment.WorkingDirectory));
            var references = new HashSet<FilePath>(GetDefaultReferences(cakeRoot));
            references.AddRange(result.References.Select(r => new FilePath(r)));

            // Find aliases
            _log.Verbose("Finding aliases...");
            var aliases = new List<CakeScriptAlias>();
            foreach (var reference in references)
            {
                aliases.AddRange(_aliasFinder.FindAliases(reference));
            }

            // Import all namespaces.
            _log.Verbose("Importing namespaces...");
            var namespaces = new HashSet<string>(result.Namespaces, StringComparer.Ordinal);
            namespaces.AddRange(GetDefaultNamespaces());
            namespaces.AddRange(aliases.SelectMany(alias => alias.Namespaces));

            // Set Host object
            _log.Verbose("Setting host object...");
            var hostObject = GetHostObject(cakeRoot);

            // Create the response.
            // ReSharper disable once UseObjectOrCollectionInitializer
            _log.Verbose("Creating response...");
            var response = new CakeScript();
            response.Host.TypeName = hostObject.TypeName;
            response.Host.AssemblyPath = hostObject.AssemblyPath;
            response.Source = string.Join("\n", result.Defines) +
                              string.Join("\n", result.UsingAliases) +
                              string.Join("\n", result.UsingStaticDirectives) +
                              GenerateSource(aliases) +
                              string.Join("\n", result.Lines);
            response.Usings.AddRange(namespaces);
            response.References.AddRange(references.Select(r => r.FullPath));

            // Return the response.
            return response;
        }

        private void HandleFileChange(FilePath path, FileChange fileChange)
        {
            if (fileChange.FromDisk)
            {
                _fileSystem.RemoveFileFromBuffer(path);
                return;
            }
            if (fileChange.LineChanges != null && fileChange.LineChanges.Any())
            {
                _fileSystem.UpdateFileBuffer(path, fileChange.LineChanges);
                return;<commit_msg>(GH-80) Moved addinRoot, cakeRoot and hostObject initialization to constructor.<commit_after>﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Cake.Core;
using Cake.Core.Configuration;
using Cake.Core.Diagnostics;
using Cake.Core.IO;
using Cake.Core.Scripting;
using Cake.Core.Scripting.Analysis;
using Cake.Core.Scripting.Processors.Loading;
using Cake.Scripting.Abstractions;
using Cake.Scripting.Abstractions.Models;
using Cake.Scripting.CodeGen.Generators;
using Cake.Scripting.IO;
using Cake.Scripting.Reflection.Emitters;
using ScriptHost = Cake.Scripting.Abstractions.Models.ScriptHost;

namespace Cake.Scripting.CodeGen
{
    public sealed class CakeScriptGenerator : IScriptGenerationService
    {
        private readonly ICakeEnvironment _environment;
        private readonly ICakeLog _log;
        private readonly ICakeConfiguration _configuration;
        private readonly IGlobber _globber;
        private readonly IScriptAnalyzer _analyzer;
        private readonly IScriptProcessor _processor;
        private readonly IBufferedFileSystem _fileSystem;
        private readonly IScriptAliasFinder _aliasFinder;
        private readonly CakeMethodAliasGenerator _methodGenerator;
        private readonly CakePropertyAliasGenerator _propertyGenerator;
        private readonly DirectoryPath _addinRoot;
        private readonly DirectoryPath _cakeRoot;
        private readonly ScriptHost _hostObject;

        public CakeScriptGenerator(
            IBufferedFileSystem fileSystem,
            ICakeEnvironment environment,
            IGlobber globber,
            ICakeConfiguration configuration,
            IScriptProcessor processor,
            IScriptAliasFinder aliasFinder,
            ICakeLog log,
            IEnumerable<ILoadDirectiveProvider> loadDirectiveProviders = null)
        {
            _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
            _environment = environment ?? throw new ArgumentNullException(nameof(environment));
            _globber = globber ?? throw new ArgumentNullException(nameof(globber));
            _log = log ?? throw new ArgumentNullException(nameof(log));
            _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
            _processor = processor ?? throw new ArgumentNullException(nameof(processor));
            _aliasFinder = aliasFinder ?? throw new ArgumentNullException(nameof(fileSystem));

            _analyzer = new ScriptAnalyzer(_fileSystem, _environment, _log, loadDirectiveProviders);

            var typeEmitter = new TypeEmitter();
            var parameterEmitter = new ParameterEmitter(typeEmitter);

            _methodGenerator = new CakeMethodAliasGenerator(typeEmitter, parameterEmitter);
            _propertyGenerator = new CakePropertyAliasGenerator(typeEmitter);
            _addinRoot = GetAddinPath(_environment.WorkingDirectory);
            _cakeRoot = GetCakePath(GetToolPath(_environment.WorkingDirectory));
            _hostObject = GetHostObject(_cakeRoot);
        }

        public CakeScript Generate(FileChange fileChange)
        {
            if (fileChange == null)
            {
                throw new ArgumentNullException(nameof(fileChange));
            }

            // Make the script path absolute.
            var scriptPath = new FilePath(fileChange.FileName).MakeAbsolute(_environment);

            // Prepare the file changes
            _log.Verbose("Handling file change...");
            HandleFileChange(scriptPath, fileChange);

            // Analyze the script file.
            _log.Verbose("Analyzing build script...");
            var result = _analyzer.Analyze(scriptPath);

            // Install addins.
            foreach (var addin in result.Addins)
            {
                try
                {
                    _log.Verbose("Installing addins...");
                    var addinReferences = _processor.InstallAddins(new[] { addin }, _addinRoot);
                    foreach (var addinReference in addinReferences)
                    {
                        result.References.Add(addinReference.FullPath);
                    }
                }
                catch (Exception e)
                {
                    // Log and continue if it fails
                    _log.Error(e);
                }
            }

            // Load all references.
            _log.Verbose("Adding references...");
            var references = new HashSet<FilePath>(GetDefaultReferences(_cakeRoot));
            references.AddRange(result.References.Select(r => new FilePath(r)));

            // Find aliases
            _log.Verbose("Finding aliases...");
            var aliases = new List<CakeScriptAlias>();
            foreach (var reference in references)
            {
                aliases.AddRange(_aliasFinder.FindAliases(reference));
            }

            // Import all namespaces.
            _log.Verbose("Importing namespaces...");
            var namespaces = new HashSet<string>(result.Namespaces, StringComparer.Ordinal);
            namespaces.AddRange(GetDefaultNamespaces());
            namespaces.AddRange(aliases.SelectMany(alias => alias.Namespaces));

            // Create the response.
            // ReSharper disable once UseObjectOrCollectionInitializer
            _log.Verbose("Creating response...");
            var response = new CakeScript();
            response.Host.TypeName = _hostObject.TypeName;
            response.Host.AssemblyPath = _hostObject.AssemblyPath;
            response.Source = string.Join("\n", result.Defines) +
                              string.Join("\n", result.UsingAliases) +
                              string.Join("\n", result.UsingStaticDirectives) +
                              GenerateSource(aliases) +
                              string.Join("\n", result.Lines);
            response.Usings.AddRange(namespaces);
            response.References.AddRange(references.Select(r => r.FullPath));

            // Return the response.
            return response;
        }

        private void HandleFileChange(FilePath path, FileChange fileChange)
        {
            if (fileChange.FromDisk)
            {
                _fileSystem.RemoveFileFromBuffer(path);
                return;
            }
            if (fileChange.LineChanges != null && fileChange.LineChanges.Any())
            {
                _fileSystem.UpdateFileBuffer(path, fileChange.LineChanges);
                return;
{'fraction_non_alphanumeric': 0.07925460260440054, 'fraction_numerical': 0.000149678191887442, 'mean_word_length': 2.1591016548463355, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '669469', 'n_tokens_mistral': 3213, 'n_tokens_neox': 3101, 'n_words': 743}