noshift.js
Version:
Joke language.
1,565 lines (1,543 loc) • 47.3 kB
JavaScript
;
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