core_source.js

/**
 * @file core/source.js
 * @copyright @spmhome @_2025
 * @author Scott Meesseman @spmeesseman
 *//** */

const WpwBase = require("./base");
const WpwTscService = require("../services/tsc");
const { isWpwSourceCodeLanguage } = require("../utils");


/**
 * @class WpwSourceCode
 */
class WpwSourceCode extends WpwBase
{
    /**
     * @private
     * @type {boolean}
     */
    _autoConfig;
    /**
     * @private
     * @type {Required<IWpwSourceCodeConfig>}
     */
    _config;
    /**
     * @private
     * @type {boolean}
     */
    _defaultExclude;
    /**
     * @private
     * @type {WpwTscService}
     */
    _tsc;
    /**
     * @private
     * @type {WpwSourceCodeInfo}
     */
    _info;
    /**
     * @private
     * @type {WpwSourceCodeExtension}
     */
    _ext;
    /**
     * @private
     * @type {WpwSourceCodeLanguage}
     */
    _lng;
    /**
     * @private
     * @readonly
     * @type {WpwSourceCodeLanguage[]}
     */
    _jsTypes = [
        "javascript", "react.javascript", "expo.javascript", "reactnative"
    ];
    /**
     * @private
     * @readonly
     * @type {WpwSourceCodeLanguage[]}
     */
    _tsTypes = [ "typescript", "react.typescript", "expo.typescript" ];
    /**
     * @private
     * @readonly
     * @type {WpwSourceCodeLanguage[]}
     */
    _jstsTypes = [ "typescript-javascript", "javascript-typescript" ];
    /**
     * @private
     * @readonly
     * @type {WpwSourceCodeLanguage[]}
     */
    _tsjsTypes = [ "typescript-javascript" ];


    /**
     * @param {WpwSourceCodeConfig} options
	 * @param {WpwBuild} build
     */
    constructor(options, build)
    {
        super(options);
        this.build = build;
        this.logger = build.logger;
    };


    get autoConfig() { return this._autoConfig === true; }
    /** @private */set autoConfig(v) { this._autoConfig = v; }
    get config() { return this._config; }
    get configFile() { return this._info.file; }
    get compilerOptions() { return this._tsconfig?.compilerOptions || {}; }
    get dotext() { return /** @type {WpwSourceDotExtensionApp} */(`.${this.ext}`); }
    get defaultExclude() { return this._defaultExclude === true; }
    /** @private */set defaultExclude(v) { this._defaultExclude = v; }
    get ext() { return this._ext; };
    get ide() { return this._info.ide; }
    get info() { return this._info; }
    get isJs() { return this._jsTypes.includes(this._lng); }
    get isJsTs() { return this._jstsTypes.includes(this._lng); }
    get isTs() { return this._tsTypes.includes(this._lng); }
    get isTsJs() { return this._tsjsTypes.includes(this._lng); }
    get tsc() { return this._tsc; }
    get language() { return this._lng; }


    /**
     * @override
     */
    dispose() {}


    /**
	 * @param {string} lPad
     */
    async init(lPad)
    {
        const b = this.build,
              l = this.logger.write("configure sourcecode service", 1, lPad);

        await this.setLanguage(lPad);
        this._config = this._obj_.apply(this._config || {}, { language: this._lng, ext: this._ext }, this.initialConfig);
        this._tsconfig =  { compilerOptions: { allowJs: this.isJs || this.isJsTs }, files: [], include: [], exclude: [] };
        this._info = {
            dir: ".", file: "tsconfig.json", pathRel: "./tsconfig.json",
            path: "./tsconfig.json", language: this._lng, ext: this._ext, ide: "visualstudiocode-ms"
        };

        l.value("   rc.config.configFile", this._config.configFile, 2, lPad);
        l.value("   has rc.config.tsconfig", !!this._config.tsconfig, 2, lPad);
        l.value("   has rc.config.compilerOptions", !!this._config.compilerOptions, 2, lPad);

        if (!b.isTranspiled) {
            b.logger.write(`   tsc service not required for '${b.type}' build `, 1, lPad);
        }
        else
        {   l.write("   create tsc service", 1, lPad);
            this._tsc = new WpwTscService({ build: this.build });
            const scCfgInfo = await this._tsc.init(this._config, lPad + "   ");
            this._obj_.merge(this._info, scCfgInfo);
            if (this._tsc.compilerOptions.jsx?.startsWith("react") && !this._lng.startsWith("react"))
            {   this._lng = this._info.language = this._lng.includes("typescript") ? "react.typescript" : "react.javascript";
                this._ext = this._info.ext = this._lng === "react.typescript" ? "tsx" : "jsx";
            }
        }

        l.value("   source language", this._lng, 1, lPad);
        l.value("   source default extension", this.ext, 1, lPad);
        l.success("sourcecode service ready", 1, lPad);
    }


    /**
     * @private
	 * @param {string} lPad
     */
    async setLanguage(lPad)
    {
        const config = this.initialConfig,
              l = this.logger.write("determine language type", 1, lPad);

        if (!this.build.isTranspiled)
        {
            this._lng =  "none";
        }

        if (!config.language || !isWpwSourceCodeLanguage(config.language))
        {
            const src = this.build.getSrcPath();
            l.value("   sourcecode root directory", src, 1, lPad);

            const o = {
                cwd: src, maxDepth: 6, nodir: true,
                ignore: [ "**/*.d.ts", "**/model/**", "**/types/**", "**/typings/**/*", "**/node_modules/**" ]
            };

            const findFiles = this._fs_.findFiles,
            [ js, ts, jsx, tsx ] = (
                await Promise.all([
                    findFiles("**/*.js", o), findFiles("**/*.ts", o), findFiles("**/*.jsx", o), findFiles("**/*.tsx", o)
                ])
            ).map((f) => f.length);

            l.value("   # of javascript [.js] files found", js, 2, lPad);
            l.value("   # of typescript [.ts] files found", ts, 2, lPad);
            l.value("   # of javascript [.jsx] files found", jsx, 2, lPad);
            l.value("   # of typescript [.tsx] files found", tsx, 2, lPad);

            if (js > 0)
            {
                if (ts === 0 && tsx === 0) {
                    this._ext = "js";
                    this._lng = jsx === 0 ? "javascript" : "react.javascript";
                }
                else {
                    this._ext = "ts";
                    this._lng = js >= ts ? "javascript-typescript" : "typescript-javascript";
                }
            }
            else if (ts > 0)
            {
                if (jsx === 0) {
                    this._ext = "ts";
                    this._lng = tsx === 0 ? "typescript" : "react.typescript";
                }
                else {
                    this._ext = "ts";
                    this._lng = "typescript-javascript";
                }
            }
            else if (jsx > tsx)
            {
                this._ext = "jsx";
                this._lng = "react.javascript";
            }
            else if (tsx > jsx)
            {
                this._ext = "tsx";
                this._lng = "react.typescript";
            }
            else {
                this._ext = "js";
                this._lng = "javascript";
            }
        }
        else {
            this._lng = config.language;
        }

        if (!this._ext)
        {   this._ext = this.isTs || this.isTsJs ? "ts" :
                (this.isJs || this.isJsTs ? "js" : (this._lng.includes("javascript") ? "jsx": "tsx"));
        }

        l.write(`project language initially set to ${this._lng } [${this._ext}]`, 1, lPad);
    }
}


module.exports = WpwSourceCode;