Merge feature/nation-turn-audit
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import ts from 'typescript';
|
||||
import ts from 'typescript-legacy';
|
||||
|
||||
const ROOT_DIR = process.cwd();
|
||||
const PHP_ROOT = path.join(ROOT_DIR, 'legacy', 'hwe', 'sammo', 'Command');
|
||||
@@ -25,6 +25,7 @@ Options:
|
||||
--json Print JSON report.
|
||||
--show-matches Print matched command keys.
|
||||
--show-compat Print compatibility-matched pairs.
|
||||
--check Exit non-zero when a command is missing or mismatched.
|
||||
--no-compat Disable compatibility alias rules (default: enabled).
|
||||
--compat-file <path> Compatibility rules JSON file (default: tools/compare-command-constraints.compat.json).
|
||||
--similarity <0..1> Near-match threshold for non-strict mode (default: 0.6).
|
||||
@@ -32,6 +33,7 @@ Options:
|
||||
`;
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const check = args.includes('--check');
|
||||
if (args.includes('--help')) {
|
||||
console.log(HELP_TEXT.trim());
|
||||
process.exit(0);
|
||||
@@ -114,7 +116,7 @@ const maskPhpComments = (text) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === '\'' || ch === '"') {
|
||||
if (ch === "'" || ch === '"') {
|
||||
quote = ch;
|
||||
i += 1;
|
||||
continue;
|
||||
@@ -205,7 +207,7 @@ const scanToDelimiter = (text, startIndex, delimiter) => {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (ch === '\'' || ch === '"') {
|
||||
if (ch === "'" || ch === '"') {
|
||||
quote = ch;
|
||||
i += 1;
|
||||
continue;
|
||||
@@ -242,7 +244,7 @@ const scanToParenEnd = (text, startIndex) => {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (ch === '\'' || ch === '"') {
|
||||
if (ch === "'" || ch === '"') {
|
||||
quote = ch;
|
||||
i += 1;
|
||||
continue;
|
||||
@@ -280,7 +282,7 @@ const splitTopLevel = (text, delimiter) => {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (ch === '\'' || ch === '"') {
|
||||
if (ch === "'" || ch === '"') {
|
||||
quote = ch;
|
||||
current += ch;
|
||||
continue;
|
||||
@@ -389,7 +391,7 @@ const stripWrappingQuotes = (value) => {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
(text.startsWith('\'') && text.endsWith('\'')) ||
|
||||
(text.startsWith("'") && text.endsWith("'")) ||
|
||||
(text.startsWith('"') && text.endsWith('"')) ||
|
||||
(text.startsWith('`') && text.endsWith('`'))
|
||||
) {
|
||||
@@ -830,8 +832,7 @@ const extractPhpFileConstraints = (file, text) => {
|
||||
regex.lastIndex = endIndex;
|
||||
}
|
||||
|
||||
const classMatch =
|
||||
/class\s+([\\\p{L}_][\\\p{L}\p{N}_]*)\s+extends\s+([\\\p{L}_][\\\p{L}\p{N}_]*)/mu.exec(text);
|
||||
const classMatch = /class\s+([\\\p{L}_][\\\p{L}\p{N}_]*)\s+extends\s+([\\\p{L}_][\\\p{L}\p{N}_]*)/mu.exec(text);
|
||||
const parentClass = classMatch?.[2]?.split('\\').pop() ?? null;
|
||||
|
||||
return {
|
||||
@@ -902,11 +903,7 @@ const readStringLikeProperty = (objLiteral, keyName) => {
|
||||
if (!ts.isPropertyAssignment(prop)) {
|
||||
continue;
|
||||
}
|
||||
const key = ts.isIdentifier(prop.name)
|
||||
? prop.name.text
|
||||
: ts.isStringLiteral(prop.name)
|
||||
? prop.name.text
|
||||
: null;
|
||||
const key = ts.isIdentifier(prop.name) ? prop.name.text : ts.isStringLiteral(prop.name) ? prop.name.text : null;
|
||||
if (key !== keyName) {
|
||||
continue;
|
||||
}
|
||||
@@ -1337,7 +1334,12 @@ const extractTsFileConstraints = (file, text) => {
|
||||
let parentFile = null;
|
||||
|
||||
const visit = (node) => {
|
||||
if (!parentFile && ts.isVariableDeclaration(node) && node.initializer && ts.isCallExpression(node.initializer)) {
|
||||
if (
|
||||
!parentFile &&
|
||||
ts.isVariableDeclaration(node) &&
|
||||
node.initializer &&
|
||||
ts.isCallExpression(node.initializer)
|
||||
) {
|
||||
const callee = node.initializer.expression;
|
||||
if (ts.isIdentifier(callee)) {
|
||||
const moduleText = imports.get(callee.text);
|
||||
@@ -1373,15 +1375,11 @@ const extractTsFileConstraints = (file, text) => {
|
||||
const name = node.name.text;
|
||||
if (name === 'buildConstraints') {
|
||||
assigned.full = true;
|
||||
byKind.full.push(
|
||||
...extractTsMethodConstraints(node, sourceFile, 'ts', 'full', relFile, factorySet)
|
||||
);
|
||||
byKind.full.push(...extractTsMethodConstraints(node, sourceFile, 'ts', 'full', relFile, factorySet));
|
||||
}
|
||||
if (name === 'buildMinConstraints') {
|
||||
assigned.min = true;
|
||||
byKind.min.push(
|
||||
...extractTsMethodConstraints(node, sourceFile, 'ts', 'min', relFile, factorySet)
|
||||
);
|
||||
byKind.min.push(...extractTsMethodConstraints(node, sourceFile, 'ts', 'min', relFile, factorySet));
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
@@ -1834,7 +1832,9 @@ const printReport = (report) => {
|
||||
if (result.near.length > 0) {
|
||||
console.log(` Near match (${result.near.length}):`);
|
||||
for (const pair of result.near) {
|
||||
console.log(` - PHP "${pair.phpName}" ~ TS "${pair.tsName}" (score ${pair.score.toFixed(2)})`);
|
||||
console.log(
|
||||
` - PHP "${pair.phpName}" ~ TS "${pair.tsName}" (score ${pair.score.toFixed(2)})`
|
||||
);
|
||||
}
|
||||
}
|
||||
if (result.argDiffs.length > 0) {
|
||||
@@ -1904,9 +1904,18 @@ const main = async () => {
|
||||
})),
|
||||
};
|
||||
console.log(JSON.stringify(jsonFriendly, null, 2));
|
||||
return;
|
||||
} else {
|
||||
printReport(report);
|
||||
}
|
||||
if (
|
||||
check &&
|
||||
(report.totals.mismatch > 0 ||
|
||||
report.totals.nearMatch > 0 ||
|
||||
report.totals.missingCommandInTs > 0 ||
|
||||
report.totals.missingCommandInPhp > 0)
|
||||
) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
printReport(report);
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import ts from 'typescript';
|
||||
import ts from 'typescript-legacy';
|
||||
|
||||
const ROOT_DIR = process.cwd();
|
||||
const PHP_ROOT = path.join(ROOT_DIR, 'legacy', 'hwe', 'sammo', 'Command');
|
||||
@@ -24,6 +24,7 @@ Options:
|
||||
--keep-date Keep <1>...</> date markers in normalized output.
|
||||
--ignore-file <path> JSON ignore list file (default: tools/compare-command-logs.ignore.json).
|
||||
--checklist Output a markdown checklist for mismatches.
|
||||
--check Exit non-zero when a command is missing or mismatched.
|
||||
--json Output JSON report.
|
||||
--help Show this help.
|
||||
`;
|
||||
@@ -40,6 +41,7 @@ const includeGuards = args.includes('--include-guards');
|
||||
const includeTarget = args.includes('--include-target');
|
||||
const countSensitive = args.includes('--count-sensitive');
|
||||
const checklist = args.includes('--checklist');
|
||||
const check = args.includes('--check');
|
||||
const ignoreFileIndex = args.indexOf('--ignore-file');
|
||||
const ignoreFile = ignoreFileIndex >= 0 ? args[ignoreFileIndex + 1] : DEFAULT_IGNORE_FILE;
|
||||
|
||||
@@ -62,6 +64,7 @@ const guardPatterns = [
|
||||
/병종 정보를 확인할 수 없어/,
|
||||
/현재 선택할 수 없는 병종입니다/,
|
||||
/도시 정보가 없어/,
|
||||
/도달할 방법이 없습니다/,
|
||||
];
|
||||
|
||||
const excludeGuards = DEFAULT_EXCLUDE_GUARDS && !includeGuards;
|
||||
@@ -127,7 +130,7 @@ const scanToDelimiter = (text, startIndex, delimiter) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === '\'' || ch === '"') {
|
||||
if (ch === "'" || ch === '"') {
|
||||
quote = ch;
|
||||
i += 1;
|
||||
continue;
|
||||
@@ -166,7 +169,7 @@ const scanToFirstArgumentEnd = (text, startIndex) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === '\'' || ch === '"') {
|
||||
if (ch === "'" || ch === '"') {
|
||||
quote = ch;
|
||||
i += 1;
|
||||
continue;
|
||||
@@ -208,7 +211,7 @@ const scanToParenEnd = (text, startIndex) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === '\'' || ch === '"') {
|
||||
if (ch === "'" || ch === '"') {
|
||||
quote = ch;
|
||||
i += 1;
|
||||
continue;
|
||||
@@ -249,7 +252,7 @@ const splitTopLevel = (text, delimiter) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === '\'' || ch === '"') {
|
||||
if (ch === "'" || ch === '"') {
|
||||
quote = ch;
|
||||
current += ch;
|
||||
continue;
|
||||
@@ -280,7 +283,7 @@ const splitTopLevel = (text, delimiter) => {
|
||||
const parsePhpStringLiteral = (segment) => {
|
||||
const trimmed = segment.trim();
|
||||
const quote = trimmed[0];
|
||||
if (quote !== '\'' && quote !== '"') {
|
||||
if (quote !== "'" && quote !== '"') {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -505,7 +508,8 @@ const extractPhpLogCalls = (text, assignments) => {
|
||||
}
|
||||
|
||||
const parsed = parsePhpExprToTemplate(value, assignments, match.index);
|
||||
const hasGeneralId = methodMeta.generalMethod && !isPhpActorLoggerExpr(calleeExpr, actorGeneralVars, actorLoggerVars);
|
||||
const hasGeneralId =
|
||||
methodMeta.generalMethod && !isPhpActorLoggerExpr(calleeExpr, actorGeneralVars, actorLoggerVars);
|
||||
|
||||
results.push({
|
||||
pos: match.index,
|
||||
@@ -615,10 +619,7 @@ const extractTsLogs = (filePath, text) => {
|
||||
if (!ts.isIdentifier(decl.name) || !decl.initializer) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
ts.isStringLiteral(decl.initializer) ||
|
||||
ts.isNoSubstitutionTemplateLiteral(decl.initializer)
|
||||
) {
|
||||
if (ts.isStringLiteral(decl.initializer) || ts.isNoSubstitutionTemplateLiteral(decl.initializer)) {
|
||||
constants.set(decl.name.text, decl.initializer.text);
|
||||
}
|
||||
}
|
||||
@@ -684,8 +685,7 @@ const extractTsLogs = (filePath, text) => {
|
||||
return;
|
||||
}
|
||||
const nameProp = arg.properties.find(
|
||||
(prop) =>
|
||||
ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'name'
|
||||
(prop) => ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'name'
|
||||
);
|
||||
if (!nameProp || !ts.isPropertyAssignment(nameProp)) {
|
||||
ts.forEachChild(node, visitFactory);
|
||||
@@ -780,8 +780,7 @@ const loadIgnoreConfig = async () => {
|
||||
const compileIgnoreRules = (config) => {
|
||||
const global = config.Global ?? {};
|
||||
const normalizeList = (list) => (Array.isArray(list) ? list.map((item) => normalizeTemplate(String(item))) : []);
|
||||
const compileRegex = (list) =>
|
||||
Array.isArray(list) ? list.map((item) => new RegExp(String(item))) : [];
|
||||
const compileRegex = (list) => (Array.isArray(list) ? list.map((item) => new RegExp(String(item))) : []);
|
||||
|
||||
return {
|
||||
globalTemplates: new Set(normalizeList(global.templates)),
|
||||
@@ -1127,69 +1126,71 @@ const main = async () => {
|
||||
|
||||
if (asJson) {
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
`Compare command logs (mode: ${mode}, strict: ${strict ? 'on' : 'off'}, keepDate: ${keepDate ? 'on' : 'off'}, excludeGuards: ${excludeGuards ? 'on' : 'off'}, excludeTarget: ${excludeTarget ? 'on' : 'off'}, countSensitive: ${countSensitive ? 'on' : 'off'})`
|
||||
);
|
||||
console.log(`PHP commands: ${report.totals.phpCommands}`);
|
||||
console.log(`TS commands: ${report.totals.tsCommands}`);
|
||||
console.log(`Matched commands: ${report.totals.matches}`);
|
||||
console.log(`Mismatched commands: ${report.totals.mismatches}`);
|
||||
console.log(`Missing in TS: ${report.missingInTs.length}`);
|
||||
console.log(`Missing in PHP: ${report.missingInPhp.length}`);
|
||||
console.log(`Ignored mismatches: ${report.totals.ignored}`);
|
||||
|
||||
console.log(
|
||||
`Compare command logs (mode: ${mode}, strict: ${strict ? 'on' : 'off'}, keepDate: ${keepDate ? 'on' : 'off'}, excludeGuards: ${excludeGuards ? 'on' : 'off'}, excludeTarget: ${excludeTarget ? 'on' : 'off'}, countSensitive: ${countSensitive ? 'on' : 'off'})`
|
||||
);
|
||||
console.log(`PHP commands: ${report.totals.phpCommands}`);
|
||||
console.log(`TS commands: ${report.totals.tsCommands}`);
|
||||
console.log(`Matched commands: ${report.totals.matches}`);
|
||||
console.log(`Mismatched commands: ${report.totals.mismatches}`);
|
||||
console.log(`Missing in TS: ${report.missingInTs.length}`);
|
||||
console.log(`Missing in PHP: ${report.missingInPhp.length}`);
|
||||
console.log(`Ignored mismatches: ${report.totals.ignored}`);
|
||||
|
||||
if (report.missingInTs.length > 0) {
|
||||
console.log('\nMissing in TS:');
|
||||
for (const key of report.missingInTs) {
|
||||
console.log(`- ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (report.missingInPhp.length > 0) {
|
||||
console.log('\nMissing in PHP:');
|
||||
for (const key of report.missingInPhp) {
|
||||
console.log(`- ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (report.mismatches.length > 0) {
|
||||
console.log('\nMismatch Details:');
|
||||
for (const mismatch of report.mismatches) {
|
||||
console.log(`\n== ${mismatch.key} ==`);
|
||||
if (mismatch.missingDetails.length > 0) {
|
||||
console.log(
|
||||
`PHP only: ${mismatch.missingDetails
|
||||
.map((item) => (item.count > 1 ? `${item.template} x${item.count}` : item.template))
|
||||
.join(' | ')}`
|
||||
);
|
||||
}
|
||||
if (mismatch.extraDetails.length > 0) {
|
||||
console.log(
|
||||
`TS only: ${mismatch.extraDetails
|
||||
.map((item) => (item.count > 1 ? `${item.template} x${item.count}` : item.template))
|
||||
.join(' | ')}`
|
||||
);
|
||||
}
|
||||
const phpLines = formatEntries(mismatch.phpEntries);
|
||||
const tsLines = formatEntries(mismatch.tsEntries);
|
||||
|
||||
console.log('PHP:');
|
||||
for (const line of phpLines) {
|
||||
console.log(line);
|
||||
}
|
||||
console.log('TS:');
|
||||
for (const line of tsLines) {
|
||||
console.log(line);
|
||||
if (report.missingInTs.length > 0) {
|
||||
console.log('\nMissing in TS:');
|
||||
for (const key of report.missingInTs) {
|
||||
console.log(`- ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checklist) {
|
||||
console.log('\nChecklist:');
|
||||
console.log(renderChecklist(report));
|
||||
if (report.missingInPhp.length > 0) {
|
||||
console.log('\nMissing in PHP:');
|
||||
for (const key of report.missingInPhp) {
|
||||
console.log(`- ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (report.mismatches.length > 0) {
|
||||
console.log('\nMismatch Details:');
|
||||
for (const mismatch of report.mismatches) {
|
||||
console.log(`\n== ${mismatch.key} ==`);
|
||||
if (mismatch.missingDetails.length > 0) {
|
||||
console.log(
|
||||
`PHP only: ${mismatch.missingDetails
|
||||
.map((item) => (item.count > 1 ? `${item.template} x${item.count}` : item.template))
|
||||
.join(' | ')}`
|
||||
);
|
||||
}
|
||||
if (mismatch.extraDetails.length > 0) {
|
||||
console.log(
|
||||
`TS only: ${mismatch.extraDetails
|
||||
.map((item) => (item.count > 1 ? `${item.template} x${item.count}` : item.template))
|
||||
.join(' | ')}`
|
||||
);
|
||||
}
|
||||
const phpLines = formatEntries(mismatch.phpEntries);
|
||||
const tsLines = formatEntries(mismatch.tsEntries);
|
||||
|
||||
console.log('PHP:');
|
||||
for (const line of phpLines) {
|
||||
console.log(line);
|
||||
}
|
||||
console.log('TS:');
|
||||
for (const line of tsLines) {
|
||||
console.log(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checklist) {
|
||||
console.log('\nChecklist:');
|
||||
console.log(renderChecklist(report));
|
||||
}
|
||||
}
|
||||
if (check && (report.totals.mismatches > 0 || report.missingInTs.length > 0 || report.missingInPhp.length > 0)) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user