UNPKG

noshift.js

Version:
1,565 lines (1,543 loc) 47.3 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // src/cli.ts var import_commander = require("commander"); var import_fs5 = require("fs"); var import_path8 = __toESM(require("path"), 1); // src/commands/dev.ts var import_fs2 = require("fs"); var import_path2 = __toESM(require("path"), 1); // src/convert.ts var noShiftMap = { // "^@^@^@": "```", // マルチバックチック "^4^[": "${", // テンプレート式展開開始 "^0": "^", "^1": "!", "^2": '"', "^3": "#", "^4": "$", "^5": "%", "^7": "'", "^8": "(", "^9": ")", "^-": "=", "^^": "~", "^\\": "_", "^@": "`", "^[": "{", "^]": "}", "^;": "+", "^:": "*", "^,": "<", "^.": ">", "^/": "?" }; var symbolToNoshift = Object.fromEntries( Object.entries(noShiftMap).filter(([key, value]) => key.length === 2 && value.length === 1).map(([key, value]) => [value, key]) ); var keywordReplacements = [ ["@or^-", "|="], ["@and^-", "&="], ["or^-", "||="], ["and^-", "&&="], ["@or", "|"], ["@and", "&"] ]; var wordKeywordReplacements = [ ["or", "||"], ["and", "&&"] ]; function convertNsjsToJs(nsjsCode, options = {}) { const capitalizeInStrings = options.capitalizeInStrings !== false; let jsCode = ""; let i = 0; const len_ns = nsjsCode.length; const STATE = { NORMAL: "NORMAL", // 普通のコード IN_DQ_STRING: "IN_DQ_STRING", // " … " の中 IN_SQ_STRING: "IN_SQ_STRING", // ' … ' の中 IN_BT_SINGLE_STRING: "IN_BT_SINGLE_STRING", // ` … ` の中 IN_BT_MULTI_STRING: "IN_BT_MULTI_STRING", // ``` … ``` の中 IN_TEMPLATE_EXPRESSION: "IN_TEMPLATE_EXPRESSION", // ${ … } の中 RAW_DQ_IN_EXPR: "RAW_DQ_IN_EXPR", // テンプレート式内の " … " の中 (NoShift 変換なし) RAW_SQ_IN_EXPR: "RAW_SQ_IN_EXPR", // テンプレート式内の ' … ' の中 (NoShift 変換なし) IN_LINE_COMMENT: "IN_LINE_COMMENT", // // … の中 IN_BLOCK_COMMENT: "IN_BLOCK_COMMENT" // /^: … ^:/ の中 }; let currentState = STATE.NORMAL; const stateStack = []; const sortedNsKeys = Object.keys(noShiftMap).sort( (a, b) => b.length - a.length ); function tryConsumeNsjsSequence() { let allowGeneral = false; if (currentState === STATE.NORMAL || currentState === STATE.IN_TEMPLATE_EXPRESSION) { allowGeneral = true; } for (const nsKey of sortedNsKeys) { if (!nsjsCode.startsWith(nsKey, i)) continue; if ((nsKey === "^4^[" || nsjsCode.startsWith("^4[", i)) && (currentState === STATE.IN_BT_SINGLE_STRING || currentState === STATE.IN_BT_MULTI_STRING)) { jsCode += "${"; i += nsKey === "^4^[" ? nsKey.length : 3; stateStack.push(currentState); currentState = STATE.IN_TEMPLATE_EXPRESSION; return true; } if (nsKey === "^]" && currentState === STATE.IN_TEMPLATE_EXPRESSION) { jsCode += "}"; i += nsKey.length; currentState = stateStack.pop(); return true; } if (nsKey === "^2" && currentState === STATE.NORMAL) { jsCode += '"'; i += nsKey.length; stateStack.push(currentState); currentState = STATE.IN_DQ_STRING; return true; } if (nsKey === "^2" && currentState === STATE.IN_DQ_STRING) { jsCode += '"'; i += nsKey.length; currentState = stateStack.pop(); return true; } if (nsKey === "^7" && currentState === STATE.NORMAL) { jsCode += "'"; i += nsKey.length; stateStack.push(currentState); currentState = STATE.IN_SQ_STRING; return true; } if (nsKey === "^7" && currentState === STATE.IN_SQ_STRING) { jsCode += "'"; i += nsKey.length; currentState = stateStack.pop(); return true; } if (nsKey === "^@" && (currentState === STATE.NORMAL || currentState === STATE.IN_TEMPLATE_EXPRESSION)) { jsCode += "`"; i += nsKey.length; stateStack.push(currentState); currentState = STATE.IN_BT_SINGLE_STRING; return true; } if (nsKey === "^@" && currentState === STATE.IN_BT_SINGLE_STRING) { jsCode += "`"; i += nsKey.length; currentState = stateStack.pop(); return true; } if (nsKey === "^2" && currentState === STATE.IN_TEMPLATE_EXPRESSION) { jsCode += '"'; i += nsKey.length; stateStack.push(currentState); currentState = STATE.RAW_DQ_IN_EXPR; return true; } if (nsKey === "^7" && currentState === STATE.IN_TEMPLATE_EXPRESSION) { jsCode += "'"; i += nsKey.length; stateStack.push(currentState); currentState = STATE.RAW_SQ_IN_EXPR; return true; } if (nsKey === "^2" && currentState === STATE.RAW_DQ_IN_EXPR) { jsCode += '"'; i += nsKey.length; currentState = stateStack.pop(); return true; } if (nsKey === "^7" && currentState === STATE.RAW_SQ_IN_EXPR) { jsCode += "'"; i += nsKey.length; currentState = stateStack.pop(); return true; } if (allowGeneral) { jsCode += noShiftMap[nsKey]; i += nsKey.length; return true; } } return false; } while (i < len_ns) { let consumed = false; if (currentState === STATE.IN_DQ_STRING) { if (nsjsCode.startsWith("\\^6", i)) { jsCode += "^6"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\^2", i)) { jsCode += "^2"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\\\", i)) { jsCode += "\\\\\\\\"; i += 2; consumed = true; } } else if (currentState === STATE.IN_SQ_STRING) { if (nsjsCode.startsWith("\\^6", i)) { jsCode += "^6"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\^7", i)) { jsCode += "^7"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\\\", i)) { jsCode += "\\\\\\\\"; i += 2; consumed = true; } } else if (currentState === STATE.RAW_DQ_IN_EXPR) { if (nsjsCode.startsWith("\\^6", i)) { jsCode += "^6"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\^2", i)) { jsCode += "^2"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\\\", i)) { jsCode += "\\\\\\\\"; i += 2; consumed = true; } else if (nsjsCode.startsWith("^2", i)) { jsCode += '"'; i += 2; currentState = stateStack.pop(); consumed = true; } if (consumed) { continue; } else { jsCode += nsjsCode[i]; i += 1; continue; } } else if (currentState === STATE.RAW_SQ_IN_EXPR) { if (nsjsCode.startsWith("\\^6", i)) { jsCode += "^6"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\^7", i)) { jsCode += "^7"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\\\", i)) { jsCode += "\\\\\\\\"; i += 2; consumed = true; } else if (nsjsCode.startsWith("^7", i)) { jsCode += "'"; i += 2; currentState = stateStack.pop(); consumed = true; } if (consumed) { continue; } else { jsCode += nsjsCode[i]; i += 1; continue; } } else if (currentState === STATE.IN_BT_SINGLE_STRING) { if (nsjsCode.startsWith("\\^6", i)) { jsCode += "^6"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\^@", i)) { jsCode += "^@"; i += 3; consumed = true; } else if (nsjsCode.startsWith("\\\\", i)) { jsCode += "\\\\\\\\"; i += 2; consumed = true; } } else if (currentState === STATE.IN_BT_MULTI_STRING) { if (nsjsCode.startsWith("\\\\", i)) { jsCode += "\\\\\\\\"; i += 2; consumed = true; } } if (!consumed && currentState !== STATE.IN_LINE_COMMENT && currentState !== STATE.IN_BLOCK_COMMENT) { if (nsjsCode.startsWith("^6", i)) { const inString = currentState === STATE.IN_DQ_STRING || currentState === STATE.IN_SQ_STRING || currentState === STATE.IN_BT_SINGLE_STRING || currentState === STATE.IN_BT_MULTI_STRING; if (!inString || capitalizeInStrings) { i += 2; if (i < len_ns) { jsCode += nsjsCode[i].toUpperCase(); i += 1; } consumed = true; } } } if (!consumed) { if (currentState === STATE.NORMAL && nsjsCode.startsWith("//", i)) { jsCode += "//"; i += 2; stateStack.push(currentState); currentState = STATE.IN_LINE_COMMENT; consumed = true; } else if (currentState === STATE.IN_LINE_COMMENT) { if (nsjsCode[i] === "\n") { jsCode += "\n"; i += 1; currentState = stateStack.pop(); } else { jsCode += nsjsCode[i]; i += 1; } consumed = true; } else if (currentState === STATE.NORMAL && nsjsCode.startsWith("/^:", i)) { jsCode += "/*"; i += 3; stateStack.push(currentState); currentState = STATE.IN_BLOCK_COMMENT; consumed = true; } else if (currentState === STATE.IN_BLOCK_COMMENT && nsjsCode.startsWith("^:/", i)) { jsCode += "*/"; i += 3; currentState = stateStack.pop(); consumed = true; } else if (currentState === STATE.IN_BLOCK_COMMENT) { jsCode += nsjsCode[i]; i += 1; consumed = true; } } if (!consumed && (currentState === STATE.NORMAL || currentState === STATE.IN_TEMPLATE_EXPRESSION)) { for (const [kw, replacement] of keywordReplacements) { if (nsjsCode.startsWith(kw, i)) { jsCode += replacement; i += kw.length; consumed = true; break; } } if (!consumed) { for (const [kw, replacement] of wordKeywordReplacements) { if (nsjsCode.startsWith(kw, i)) { const prevChar = i > 0 ? nsjsCode[i - 1] : " "; const prevIsBoundary = !/[a-zA-Z0-9_$]/.test(prevChar); const afterPos = i + kw.length; const nextChar = afterPos < len_ns ? nsjsCode[afterPos] : " "; const nextIsBoundary = !/[a-zA-Z0-9_$]/.test(nextChar); if (prevIsBoundary && nextIsBoundary) { jsCode += replacement; i += kw.length; consumed = true; break; } } } } } if (!consumed) { consumed = tryConsumeNsjsSequence(); } if (!consumed) { jsCode += nsjsCode[i]; i += 1; } } if (stateStack.length > 0) { if (!options._silent) { console.warn( `Warning: Unmatched literal/templating states. Final state: ${currentState}, Remaining stack: ${stateStack.join( ", " )}` ); } } return jsCode; } function checkUppercaseWarnings(nsjsCode, options = {}) { const capitalizeInStrings = options.capitalizeInStrings !== false; const warnings = []; const lines = nsjsCode.split("\n"); let inDQ = false; let inSQ = false; let inBT = false; let inLineComment = false; let inBlockComment = false; const firstLine = lines[0] ?? ""; const hasNoShiftShebang = firstLine.startsWith("#^1"); const hasRawShebang = firstLine.startsWith("#!"); if (hasRawShebang) { warnings.push({ line: 1, column: 1, char: "#", message: "Shebang '#!' found. Use '#^1' instead." }); } for (let lineNum = 0; lineNum < lines.length; lineNum++) { const line = lines[lineNum]; inLineComment = false; if (lineNum === 0 && (hasNoShiftShebang || hasRawShebang)) { continue; } for (let col = 0; col < line.length; col++) { const ch = line[col]; const next = line[col + 1]; if (ch === "\\" && next === "^") { col += 2; continue; } if (inBlockComment && ch === "^" && next === ":" && line[col + 2] === "/") { inBlockComment = false; col += 2; continue; } if (inBlockComment) continue; if (!inDQ && !inSQ && !inBT && ch === "/" && next === "/") { inLineComment = true; break; } if (!inDQ && !inSQ && !inBT && ch === "/" && next === "^" && line[col + 2] === ":") { inBlockComment = true; col += 2; continue; } if (inLineComment) continue; if (ch === "^" && next === "6") { col += 2; continue; } if (ch === "^" && next === "2") { inDQ = !inDQ; col += 1; continue; } if (ch === "^" && next === "7") { inSQ = !inSQ; col += 1; continue; } if (ch === "^" && next === "@") { inBT = !inBT; col += 1; continue; } if (inDQ || inSQ || inBT) { if (capitalizeInStrings && /[A-Z]/.test(ch)) { warnings.push({ line: lineNum + 1, column: col + 1, char: ch, message: `Uppercase letter '${ch}' found in string. Use ^6${ch.toLowerCase()} instead.` }); } continue; } if (ch === "^" && next && /[0-9\-^\\@\[\];:,./]/.test(next)) { col += 1; continue; } if (/[A-Z]/.test(ch)) { warnings.push({ line: lineNum + 1, column: col + 1, char: ch, message: `Uppercase letter '${ch}' found. Use ^6${ch.toLowerCase()} instead.` }); } else if (symbolToNoshift[ch] && ch !== "_" && ch !== "#") { warnings.push({ line: lineNum + 1, column: col + 1, char: ch, message: `Symbol '${ch}' found. Use ${symbolToNoshift[ch]} instead.` }); } else if (ch === "_") { warnings.push({ line: lineNum + 1, column: col + 1, char: ch, message: "Underscore '_' found in code. Use ^\\ instead." }); } else if (ch === "#") { warnings.push({ line: lineNum + 1, column: col + 1, char: ch, message: "Hash '#' found in code. Use ^3 instead." }); } } } return warnings; } var validCaretKeys = new Set(Object.keys(noShiftMap).map((k) => k[1])); validCaretKeys.add("6"); function diagnose(nsjsCode) { const errors = []; const lines = nsjsCode.split("\n"); let state = "NORMAL"; const stateStack = []; const openPositions = []; for (let lineNum = 0; lineNum < lines.length; lineNum++) { const line = lines[lineNum]; if (state === "LINE_COMMENT") { state = stateStack.pop() || "NORMAL"; } for (let col = 0; col < line.length; col++) { const ch = line[col]; const next = col + 1 < line.length ? line[col + 1] : void 0; const next2 = col + 2 < line.length ? line[col + 2] : void 0; if (ch === "\\" && next === "^") { col += 2; continue; } if (ch === "\\" && next === "\\") { col += 1; continue; } if (state === "BLOCK_COMMENT") { if (ch === "^" && next === ":" && next2 === "/") { state = stateStack.pop() || "NORMAL"; openPositions.pop(); col += 2; } continue; } if (state === "LINE_COMMENT") { continue; } if (state === "DQ") { if (ch === "^" && next === "2") { state = stateStack.pop() || "NORMAL"; openPositions.pop(); col += 1; } continue; } if (state === "SQ") { if (ch === "^" && next === "7") { state = stateStack.pop() || "NORMAL"; openPositions.pop(); col += 1; } continue; } if (state === "BT") { if (ch === "^" && next === "4" && (next2 === "^" || next2 === "[")) { if (next2 === "^" && col + 3 < line.length && line[col + 3] === "[") { stateStack.push(state); openPositions.push({ line: lineNum + 1, column: col + 1, type: "TEMPLATE_EXPR" }); state = "TEMPLATE_EXPR"; col += 3; } else if (next2 === "[") { stateStack.push(state); openPositions.push({ line: lineNum + 1, column: col + 1, type: "TEMPLATE_EXPR" }); state = "TEMPLATE_EXPR"; col += 2; } continue; } if (ch === "^" && next === "@") { state = stateStack.pop() || "NORMAL"; openPositions.pop(); col += 1; } continue; } if (state === "TEMPLATE_EXPR") { if (ch === "^" && next === "]") { state = stateStack.pop() || "NORMAL"; openPositions.pop(); col += 1; continue; } } if (ch === "/" && next === "/") { stateStack.push(state); state = "LINE_COMMENT"; break; } if (ch === "/" && next === "^" && next2 === ":") { stateStack.push(state); openPositions.push({ line: lineNum + 1, column: col + 1, type: "BLOCK_COMMENT" }); state = "BLOCK_COMMENT"; col += 2; continue; } if (ch === "^" && next === "2") { stateStack.push(state); openPositions.push({ line: lineNum + 1, column: col + 1, type: "DQ" }); state = "DQ"; col += 1; continue; } if (ch === "^" && next === "7") { stateStack.push(state); openPositions.push({ line: lineNum + 1, column: col + 1, type: "SQ" }); state = "SQ"; col += 1; continue; } if (ch === "^" && next === "@") { stateStack.push(state); openPositions.push({ line: lineNum + 1, column: col + 1, type: "BT" }); state = "BT"; col += 1; continue; } if (ch === "^" && next === "6") { if (col + 2 >= line.length && lineNum === lines.length - 1) { errors.push({ line: lineNum + 1, column: col + 1, message: "^6 at end of file with no following character to capitalize." }); } col += 2; continue; } if (ch === "^" && next !== void 0) { if (!validCaretKeys.has(next)) { errors.push({ line: lineNum + 1, column: col + 1, message: `Unknown sequence '^${next}'.` }); } col += 1; continue; } if (ch === "^" && next === void 0 && lineNum === lines.length - 1) { errors.push({ line: lineNum + 1, column: col + 1, message: "Lone '^' at end of file." }); } } } while (openPositions.length > 0) { const pos = openPositions.pop(); const labels = { DQ: "string literal (^2...^2)", SQ: "string literal (^7...^7)", BT: "template literal (^@...^@)", BLOCK_COMMENT: "block comment (/^:...^:/)", TEMPLATE_EXPR: "template expression (^4^[...^])" }; errors.push({ line: pos.line, column: pos.column, message: `Unclosed ${labels[pos.type] || pos.type} opened here.` }); } return errors; } // src/config.ts var import_fs = require("fs"); var import_path = __toESM(require("path"), 1); var DEFAULT_CONFIG = { compileroptions: { rootdir: "src", outdir: "dist", warnuppercase: true, capitalizeinstrings: true, noheader: false } }; async function loadConfig(cwd = process.cwd()) { const configPath = import_path.default.join(cwd, "nsjsconfig.json"); try { const raw = await import_fs.promises.readFile(configPath, "utf-8"); const userConfig = JSON.parse(raw); return { compileroptions: { ...DEFAULT_CONFIG.compileroptions, ...userConfig.compileroptions ?? {} } }; } catch (e) { if (e.code === "ENOENT") { return DEFAULT_CONFIG; } throw new Error(`Failed to parse nsjsconfig.json: ${e.message}`); } } // package.json var package_default = { name: "noshift.js", version: "0.15.1", description: "Joke language.", bin: { nsc: "./dist/cli.cjs" }, main: "./dist/index.cjs", module: "./dist/index.mjs", types: "./dist/index.d.ts", exports: { ".": { import: { types: "./dist/index.d.mts", default: "./dist/index.mjs" }, require: { types: "./dist/index.d.cts", default: "./dist/index.cjs" } } }, type: "module", scripts: { build: "tsup", test: "vitest run", "test:watch": "vitest", typecheck: "tsc --noEmit", format: "prettier --write ./src/" }, repository: { type: "git", url: "git+https://github.com/otoneko1102/NoShift.js.git" }, keywords: [ "noshift", "nsjs", "joke", "language", "lang" ], author: "otoneko.", license: "MIT", bugs: { url: "https://github.com/otoneko1102/NoShift.js/issues" }, homepage: "https://noshift.js.org", dependencies: { commander: "^14.0.3" }, devDependencies: { "@types/node": "^25.3.0", prettier: "^3.8.1", tsup: "^8.5.1", tsx: "^4.21.0", typescript: "^5.9.3", vitest: "^4.0.18" }, engines: { node: ">=22.12.0" } }; // src/header.ts function getVersion() { return package_default.version; } function addHeader(js, version2) { const ver = version2 ?? getVersion(); const header = `// Generated by NoShift.js ${ver}`; if (js.startsWith("#!")) { const newlineIndex = js.indexOf("\n"); if (newlineIndex === -1) { return js + "\n" + header + "\n"; } const shebang = js.slice(0, newlineIndex + 1); const rest = js.slice(newlineIndex + 1); return shebang + header + "\n" + rest; } return header + "\n" + js; } // src/signal-handler.ts var isHandlerRegistered = false; var cleanupCallbacks = []; function handleSigint(cleanup) { if (cleanup) { cleanupCallbacks.push(cleanup); } if (!isHandlerRegistered) { isHandlerRegistered = true; process.on("SIGINT", async () => { console.log("\n"); for (const cb of cleanupCallbacks) { try { await cb(); } catch { } } process.exit(0); }); } } // src/logger.ts var colors = { reset: "\x1B[0m", bright: "\x1B[1m", dim: "\x1B[2m", red: "\x1B[31m", green: "\x1B[32m", yellow: "\x1B[33m", blue: "\x1B[34m", magenta: "\x1B[35m", cyan: "\x1B[36m", gray: "\x1B[90m" }; function success(message) { console.log(`${colors.green}\u2713${colors.reset} ${message}`); } function error(message) { console.error(`${colors.red}\u2717${colors.reset} ${message}`); } function info(message) { console.log(`${colors.blue}\u2139${colors.reset} ${message}`); } function warn(message) { console.log(`${colors.yellow}\u26A0${colors.reset} ${message}`); } function step(message) { console.log(`${colors.cyan}\u2192${colors.reset} ${message}`); } function dim(message) { console.log(`${colors.dim}${message}${colors.reset}`); } function highlight(text) { return `${colors.cyan}${text}${colors.reset}`; } function errorCode(code, message) { console.error(`${colors.red}error ${code}:${colors.reset} ${message}`); } // src/commands/dev.ts function timestamp() { return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB"); } async function findNsjsFiles(dir) { let entries; try { entries = await import_fs2.promises.readdir(dir, { withFileTypes: true }); } catch { return null; } const files = []; for (const entry of entries) { const fullPath = import_path2.default.join(dir, entry.name); if (entry.isDirectory()) { const nested = await findNsjsFiles(fullPath); if (nested) files.push(...nested); } else if (entry.name.endsWith(".nsjs") && !entry.name.startsWith("_")) { files.push(fullPath); } } return files; } async function compileFile(file, rootDir, outDir, cwd, convertOptions = {}, noHeader = false) { const relative = import_path2.default.relative(rootDir, file).replace(/\\/g, "/"); const destPath = import_path2.default.join(outDir, import_path2.default.relative(rootDir, file)).replace(/\.nsjs$/, ".js"); const code = await import_fs2.promises.readFile(file, "utf-8"); const syntaxErrors = diagnose(code); if (syntaxErrors.length > 0) { const rel = relative; for (const e of syntaxErrors) { errorCode("NS1", `${rel}:${e.line}:${e.column} - ${e.message}`); } throw new Error(`${syntaxErrors.length} syntax error(s)`); } let js = convertNsjsToJs(code, convertOptions); if (!noHeader) { js = addHeader(js); } await import_fs2.promises.mkdir(import_path2.default.dirname(destPath), { recursive: true }); await import_fs2.promises.writeFile(destPath, js, "utf-8"); dim( `[${timestamp()}] ${relative} \u2192 ${import_path2.default.relative(cwd, destPath).replace(/\\/g, "/")}` ); } async function dev(cliOptions = {}) { const cwd = process.cwd(); let config; try { config = await loadConfig(cwd); } catch (e) { errorCode("NS0", e.message); process.exit(1); } const rootDir = import_path2.default.resolve(cwd, config.compileroptions.rootdir); const outDir = import_path2.default.resolve(cwd, config.compileroptions.outdir); const convertOptions = { capitalizeInStrings: config.compileroptions.capitalizeinstrings !== false }; const files = await findNsjsFiles(rootDir); if (files === null) { errorCode( "NS0", `rootdir '${config.compileroptions.rootdir}' not found.` ); process.exit(1); } info(`Starting compilation in watch mode...`); await import_fs2.promises.mkdir(outDir, { recursive: true }); const noHeader = cliOptions.noHeader || config.compileroptions.noheader; for (const file of files) { try { await compileFile(file, rootDir, outDir, cwd, convertOptions, noHeader); } catch (e) { const rel = import_path2.default.relative(rootDir, file).replace(/\\/g, "/"); errorCode("NS1", `${rel}: ${e.message}`); } } info( `Watching for file changes in '${highlight(config.compileroptions.rootdir)}'... (Press Ctrl+C to stop)` ); console.log(""); handleSigint(() => { info("Stopped watching."); }); const debounceMap = /* @__PURE__ */ new Map(); const DEBOUNCE_MS = 100; (0, import_fs2.watch)(rootDir, { recursive: true }, (_eventType, filename) => { if (!filename) return; if (!filename.endsWith(".nsjs")) return; if (import_path2.default.basename(filename).startsWith("_")) return; if (debounceMap.has(filename)) { clearTimeout(debounceMap.get(filename)); } debounceMap.set( filename, setTimeout(async () => { debounceMap.delete(filename); const absPath = import_path2.default.join(rootDir, filename); try { await compileFile( absPath, rootDir, outDir, cwd, convertOptions, noHeader ); } catch (e) { if (e.code === "ENOENT") { } else { errorCode( "NS1", `${filename.replace(/\\/g, "/")}: ${e.message}` ); } } }, DEBOUNCE_MS) ); }); await new Promise(() => { }); } // src/commands/init.ts var import_promises2 = require("fs/promises"); var import_path3 = __toESM(require("path"), 1); // src/prompt.ts var import_promises = __toESM(require("readline/promises"), 1); async function askInput(question, defaultValue) { const rl = import_promises.default.createInterface({ input: process.stdin, output: process.stdout }); const suffix = defaultValue ? ` (${defaultValue})` : ""; try { const answer = await rl.question(`${question}${suffix}: `); return answer.trim() || defaultValue || ""; } finally { rl.close(); } } async function askConfirm(question, defaultYes = true) { const rl = import_promises.default.createInterface({ input: process.stdin, output: process.stdout }); const hint = defaultYes ? "Y/n" : "y/N"; try { const answer = await rl.question(`${question} (${hint}): `); const trimmed = answer.trim().toLowerCase(); if (trimmed === "") return defaultYes; return trimmed === "y" || trimmed === "yes"; } finally { rl.close(); } } // src/commands/init.ts var DEFAULT_CONFIG2 = { compileroptions: { rootdir: "src", outdir: "dist", warnuppercase: true, capitalizeinstrings: true, noheader: false } }; var PRETTIERRC_FILES = [ ".prettierrc", ".prettierrc.json", ".prettierrc.yml", ".prettierrc.yaml", ".prettierrc.json5", ".prettierrc.cjs", ".prettierrc.mjs", "prettier.config.js", "prettier.config.cjs", "prettier.config.mjs" ]; var PLUGIN_NAME = "prettier-plugin-noshift.js"; async function addPluginToExistingConfig(filePath) { const basename = import_path3.default.basename(filePath); const isJson = basename === ".prettierrc" || basename === ".prettierrc.json"; if (!isJson) { warn( `Found ${basename} \u2014 please add "${PLUGIN_NAME}" to the plugins array manually.` ); return; } try { const raw = await (0, import_promises2.readFile)(filePath, "utf-8"); const config = JSON.parse(raw); const plugins = Array.isArray(config.plugins) ? config.plugins : []; if (plugins.includes(PLUGIN_NAME)) { info(`${basename} already contains "${PLUGIN_NAME}".`); return; } plugins.push(PLUGIN_NAME); config.plugins = plugins; await (0, import_promises2.writeFile)(filePath, JSON.stringify(config, null, 2) + "\n"); success(`Added "${PLUGIN_NAME}" to ${basename}`); } catch (err) { error(`Failed to update ${basename}: ${err.message}`); warn(`Please add "${PLUGIN_NAME}" to the plugins array manually.`); } } async function createPrettierConfig() { const prettierConfig = { semi: true, singleQuote: false, trailingComma: "es5", plugins: [PLUGIN_NAME] }; await (0, import_promises2.writeFile)( ".prettierrc", JSON.stringify(prettierConfig, null, 2) + "\n" ); success("Created .prettierrc"); } async function init() { handleSigint(); const cwd = process.cwd(); const configPath = import_path3.default.join(cwd, "nsjsconfig.json"); let configExists = false; try { await (0, import_promises2.access)(configPath); configExists = true; } catch { } if (configExists) { warn("nsjsconfig.json already exists in the current directory."); const overwrite = await askConfirm("Overwrite?", false); if (!overwrite) { info("Skipped nsjsconfig.json"); } else { await (0, import_promises2.writeFile)( configPath, JSON.stringify(DEFAULT_CONFIG2, null, 2) + "\n" ); success("Overwritten nsjsconfig.json"); } } else { await (0, import_promises2.writeFile)(configPath, JSON.stringify(DEFAULT_CONFIG2, null, 2) + "\n"); success("Created nsjsconfig.json"); } dim( ` compileroptions.rootdir : ${DEFAULT_CONFIG2.compileroptions.rootdir}` ); dim( ` compileroptions.outdir : ${DEFAULT_CONFIG2.compileroptions.outdir}` ); const usePrettier = await askConfirm("Set up Prettier?", true); if (usePrettier) { let existingFile = null; for (const name of PRETTIERRC_FILES) { try { await (0, import_promises2.access)(import_path3.default.join(cwd, name)); existingFile = name; break; } catch { } } if (existingFile) { await addPluginToExistingConfig(import_path3.default.join(cwd, existingFile)); } else { await createPrettierConfig(); } const ignorePath = import_path3.default.join(cwd, ".prettierignore"); let ignoreExists = false; try { await (0, import_promises2.access)(ignorePath); ignoreExists = true; } catch { } if (!ignoreExists) { await (0, import_promises2.writeFile)(ignorePath, "dist/\nnode_modules/\n"); success("Created .prettierignore"); } } console.log(""); } // src/commands/clean.ts var import_promises3 = require("fs/promises"); var import_path4 = __toESM(require("path"), 1); async function clean() { handleSigint(); const cwd = process.cwd(); let config; try { config = await loadConfig(cwd); } catch (e) { errorCode("NS0", e.message); process.exit(1); } const outDir = import_path4.default.resolve(cwd, config.compileroptions.outdir); try { await (0, import_promises3.access)(outDir); } catch { info( `Nothing to clean (${highlight(config.compileroptions.outdir)} does not exist).` ); return; } await (0, import_promises3.rm)(outDir, { recursive: true, force: true }); success(`Deleted ${highlight(config.compileroptions.outdir)}`); } // src/commands/run.ts var import_fs3 = require("fs"); var import_path5 = __toESM(require("path"), 1); var import_child_process = require("child_process"); async function run(file, cliOptions = {}) { handleSigint(); if (!file) { file = await askInput("File path"); if (!file) { error("File path is required."); process.exit(1); } } const cwd = process.cwd(); let config; try { config = await loadConfig(cwd); } catch { config = { compileroptions: { rootdir: "src", outdir: "dist", warnuppercase: true, capitalizeinstrings: true, noheader: false } }; } const convertOptions = { capitalizeInStrings: config.compileroptions.capitalizeinstrings !== false }; const filePath = import_path5.default.resolve(cwd, file); let code; try { code = await import_fs3.promises.readFile(filePath, "utf-8"); } catch { errorCode("NS2", `File not found: ${filePath}`); process.exit(1); } const syntaxErrors = diagnose(code); if (syntaxErrors.length > 0) { const relative = import_path5.default.relative(cwd, filePath).replace(/\\/g, "/"); for (const e of syntaxErrors) { errorCode( "NS1", `${relative}:${e.line}:${e.column} - ${e.message}` ); } error(`Found ${syntaxErrors.length} syntax error(s).`); process.exit(1); } const noHeader = cliOptions.noHeader || config.compileroptions.noheader; let js = convertNsjsToJs(code, convertOptions); if (!noHeader) { js = addHeader(js); } const dir = import_path5.default.dirname(filePath); const base = import_path5.default.basename(filePath, ".nsjs"); const tempFile = import_path5.default.join(dir, `${base}.__nsc_tmp__.mjs`); try { await import_fs3.promises.writeFile(tempFile, js, "utf-8"); await new Promise((resolve, reject) => { const child = (0, import_child_process.spawn)(process.execPath, [tempFile], { stdio: "inherit", env: process.env }); child.on("close", (exitCode) => { if (exitCode === 0) resolve(); else reject( Object.assign(new Error(`Process exited with code ${exitCode}`), { code: exitCode }) ); }); child.on("error", reject); }); } catch (e) { if (e.code !== 0) { const exitCode = e.code; process.exit(typeof exitCode === "number" ? exitCode : 1); } } finally { await import_fs3.promises.unlink(tempFile).catch(() => { }); } } // src/commands/create.ts var import_child_process2 = require("child_process"); var import_promises4 = __toESM(require("fs/promises"), 1); var import_path6 = __toESM(require("path"), 1); async function create(projectNameArg, options = {}) { handleSigint(); const cwd = process.cwd(); let projectName = projectNameArg; if (!projectName) { projectName = await askInput("Project name", "my-noshift-app"); } let useLinter; if (options.linter === false) { useLinter = false; } else { useLinter = await askConfirm("Use @noshift.js/lint?", true); } let usePrettier; if (options.prettier === false) { usePrettier = false; } else { usePrettier = await askConfirm("Use Prettier?", true); } const projectPath = import_path6.default.join(cwd, projectName); step("Creating project directory ..."); await import_promises4.default.mkdir(projectPath, { recursive: true }); dim(` ${projectPath}`); process.chdir(projectPath); step("Initializing npm ..."); (0, import_child_process2.execSync)("npm init -y", { stdio: "ignore" }); const pkgPath = import_path6.default.join(projectPath, "package.json"); const pkg = JSON.parse(await import_promises4.default.readFile(pkgPath, "utf-8")); pkg.scripts = {}; if (useLinter) { pkg.scripts.lint = "nslint"; } if (usePrettier) { pkg.scripts.format = "prettier --write ./src"; } pkg.scripts.compile = "nsc"; pkg.scripts.dev = "nsc watch"; pkg.scripts.clean = "nsc clean"; pkg.scripts.script = "nsc run"; await import_promises4.default.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n"); const nsjsconfig = { compileroptions: { rootdir: "src", outdir: "dist", warnuppercase: true, capitalizeinstrings: true, noheader: false } }; await import_promises4.default.writeFile( "nsjsconfig.json", JSON.stringify(nsjsconfig, null, 2) + "\n" ); success("Created nsjsconfig.json"); if (useLinter) { step("Installing @noshift.js/lint ..."); (0, import_child_process2.execSync)("npm install --save-dev @noshift.js/lint", { stdio: "ignore" }); let linterConfig; try { const { createDefaultConfig } = await import("@noshift.js/lint"); linterConfig = createDefaultConfig(); } catch { linterConfig = { rules: { "unclosed-string": "error", "unclosed-comment": "error", "unclosed-template-expr": "error", "unknown-caret-sequence": "error", "lone-caret": "error", "capitalize-eof": "error", "uppercase-in-code": "warning", "trailing-whitespace": "off", "no-consecutive-blank-lines": "off" } }; } await import_promises4.default.writeFile( "nsjslinter.json", JSON.stringify(linterConfig, null, 2) + "\n" ); success("Created nsjslinter.json"); } if (usePrettier) { step("Installing Prettier & prettier-plugin-noshift.js ..."); (0, import_child_process2.execSync)("npm install --save-dev prettier prettier-plugin-noshift.js", { stdio: "ignore" }); const prettierConfig = { semi: true, singleQuote: false, trailingComma: "es5", plugins: ["prettier-plugin-noshift.js"] }; await import_promises4.default.writeFile( ".prettierrc", JSON.stringify(prettierConfig, null, 2) + "\n" ); success("Created .prettierrc"); await import_promises4.default.writeFile(".prettierignore", "dist/\nnode_modules/\n"); success("Created .prettierignore"); } step("Installing noshift.js ..."); (0, import_child_process2.execSync)("npm install --save-dev noshift.js", { stdio: "ignore" }); step("Creating project files ..."); await import_promises4.default.mkdir("src", { recursive: true }); await import_promises4.default.writeFile( "src/index.nsjs", "console.log^8^2^6hello, ^6world!^2^9;\n" ); await import_promises4.default.writeFile(".gitignore", "node_modules/\ndist/\n"); const readme = `# ${projectName} A [NoShift.js](https://github.com/otoneko1102/NoShift.js) project. ## Compile \`\`\`bash nsc \`\`\` ## Dev (watch mode) \`\`\`bash nsc watch \`\`\` `; await import_promises4.default.writeFile("README.md", readme); console.log(""); success("Project created successfully!"); console.log(""); info("Next steps:"); console.log(` ${highlight(`cd ${projectName}`)}`); console.log(` ${highlight("nsc")}`); console.log(""); } // src/commands/compile.ts var import_fs4 = require("fs"); var import_path7 = __toESM(require("path"), 1); async function findNsjsFiles2(dir) { let entries; try { entries = await import_fs4.promises.readdir(dir, { withFileTypes: true }); } catch { return null; } const files = []; for (const entry of entries) { const fullPath = import_path7.default.join(dir, entry.name); if (entry.isDirectory()) { const nested = await findNsjsFiles2(fullPath); if (nested) files.push(...nested); } else if (entry.name.endsWith(".nsjs") && !entry.name.startsWith("_")) { files.push(fullPath); } } return files; } async function compile(cliOptions = {}) { handleSigint(); const cwd = process.cwd(); let config; try { config = await loadConfig(cwd); } catch (e) { errorCode("NS0", e.message); process.exit(1); } const rootDir = import_path7.default.resolve(cwd, config.compileroptions.rootdir); const outDir = import_path7.default.resolve(cwd, config.compileroptions.outdir); const files = await findNsjsFiles2(rootDir); if (files === null) { errorCode( "NS0", `rootdir '${config.compileroptions.rootdir}' not found.` ); process.exit(1); } if (files.length === 0) { info("No .nsjs files found."); return; } await import_fs4.promises.mkdir(outDir, { recursive: true }); let compiled = 0; let errors = 0; let totalWarnings = 0; const warnUppercase = config.compileroptions.warnuppercase !== false; const convertOptions = { capitalizeInStrings: config.compileroptions.capitalizeinstrings !== false }; for (const file of files) { const relative = import_path7.default.relative(rootDir, file); const destPath = import_path7.default.join(outDir, relative).replace(/\.nsjs$/, ".js"); try { const code = await import_fs4.promises.readFile(file, "utf-8"); const syntaxErrors = diagnose(code); if (syntaxErrors.length > 0) { for (const e of syntaxErrors) { errorCode( "NS1", `${relative.replace(/\\/g, "/")}:${e.line}:${e.column} - ${e.message}` ); } errors += syntaxErrors.length; continue; } if (warnUppercase) { const warnings = checkUppercaseWarnings(code, convertOptions); for (const w of warnings) { warn( `${relative.replace(/\\/g, "/")}:${w.line}:${w.column} - ${w.message}` ); totalWarnings++; } } let js = convertNsjsToJs(code, convertOptions); const noHeader = cliOptions.noHeader || config.compileroptions.noheader; if (!noHeader) { js = addHeader(js); } await import_fs4.promises.mkdir(import_path7.default.dirname(destPath), { recursive: true }); await import_fs4.promises.writeFile(destPath, js, "utf-8"); dim( ` ${relative.replace(/\\/g, "/")} \u2192 ${import_path7.default.relative(cwd, destPath).replace(/\\/g, "/")}` ); compiled++; } catch (e) { errorCode( "NS1", `${relative.replace(/\\/g, "/")}: ${e.message}` ); errors++; } } console.log(""); if (errors > 0) { error(`Found ${errors} error(s). Compiled ${compiled} file(s).`); process.exit(1); } else { let msg = `Compiled ${compiled} file(s).`; if (totalWarnings > 0) { msg += ` (${totalWarnings} warning(s))`; } success(msg); } } // src/cli.ts function loadPackageVersion() { const pkg = JSON.parse( (0, import_fs5.readFileSync)(import_path8.default.join(__dirname, "../package.json"), "utf-8") ); return pkg.version; } var version = loadPackageVersion(); var DOCS_URL = "https://noshift.js.org/"; var program = new import_commander.Command(); program.name("nsc").description("NoShift.js compiler").version(version, "-v, --version", "output the version number").option("-w, --watch", "Watch for file changes and recompile").option("--init", "Create a nsjsconfig.json in the current directory").option("--clean", "Delete the output directory (outdir)").option("-r, --run <file>", "Run a .nsjs file directly").option("--create [name]", "Scaffold a new NoShift.js project").option("--no-header", "Suppress the generated header comment in output").addHelpText("after", ` Documentation: ${DOCS_URL}`).action(async (options) => { const noHeader = options.header === false; if (options.watch) { await dev({ noHeader }); } else if (options.init) { await init(); } else if (options.clean) { await clean(); } else if (options.run) { await run(options.run, { noHeader }); } else if (options.create !== void 0) { await create(options.create || void 0); } else { await compile({ noHeader }); } }); program.command("watch").alias("w").description("Watch for file changes and recompile").action(async () => { await dev(); }); program.command("run <file>").description("Run a .nsjs file directly").action(async (file) => { await run(file); }); program.command("create [name]").description("Scaffold a new NoShift.js project").option("--prettier", "Include Prettier (default)").option("--no-prettier", "Skip Prettier setup").action( async (name, options) => { await create(name, options); } ); program.command("init").description("Create a nsjsconfig.json in the current directory").action(async () => { await init(); }); program.command("clean").description("Delete the output directory (outdir)").action(async () => { await clean(); }); program.command("version").description("Display the current version").action(() => { console.log(version); }); program.command("help").description("Show help information").action(() => { program.outputHelp(); }); program.parse(); //# sourceMappingURL=cli.cjs.map