diff --git a/src/console-script.js b/src/console-script.js
index 58ce4b2..66774ef 100644
--- a/src/console-script.js
+++ b/src/console-script.js
@@ -20,44 +20,112 @@ export const generateConsoleScript = ({ html, css }) => {
pushToConsole({line:fixedLine, column, message}, "error")
}
+ const encodeFunction = (fn) => ({
+ type: 'function',
+ name: fn.name || '(anonymous)',
+ async: fn.constructor?.name === 'AsyncFunction',
+ generator: fn.constructor?.name === 'GeneratorFunction',
+ content: fn.toString()
+ })
+
+ const encode = (value, seen) => {
+ if (typeof value === 'function') return encodeFunction(value)
+
+ // primitives
+ if (value === null || typeof value !== 'object') return value
+
+ // circular detection
+ if (seen.has(value)) return { type: 'circular' }
+ seen.add(value)
+
+ if (Array.isArray(value)) {
+ return value.map(item => encode(item, seen))
+ }
+
+ if (value instanceof RegExp) {
+ return { type: 'regexp', value: value.toString() }
+ }
+
+ if (value instanceof Date) {
+ return { type: 'date', value: value.toISOString() }
+ }
+
+ if (value instanceof Set) {
+ const values = []
+ for (const v of value.values()) {
+ values.push(encode(v, seen))
+ }
+ return { type: 'set', size: value.size, values }
+ }
+
+ if (value instanceof Map) {
+ const entries = []
+ for (const [k, v] of value.entries()) {
+ entries.push([encode(k, seen), encode(v, seen)])
+ }
+ return { type: 'map', size: value.size, entries }
+ }
+
+ if (value.constructor === Object) {
+ const out = {}
+ const keys = Reflect.ownKeys(value) // includes symbols
+
+ for (const key of keys) {
+ const outKey = typeof key === 'symbol' ? key.toString() : key
+ out[outKey] = encode(value[key], seen)
+ }
+
+ return out
+ }
+
+ // Fallback: try to serialize toString (safe)
+ try {
+ return { type: 'unknown', value: value.toString() }
+ } catch (e) {
+ return { type: 'unknown', value: Object.prototype.toString.call(value) }
+ }
+ }
+
+ const serialize = (args) => args.map(arg => encode(arg, new WeakSet()))
+
const counts = {}
const timers = {}
const console = {
log: function(...args){
- pushToConsole(args, "log:log")
+ pushToConsole(serialize(args), "log:log")
},
error: function(...args){
- pushToConsole(args, "log:error")
+ pushToConsole(serialize(args), "log:error")
},
warn: function(...args){
- pushToConsole(args, "log:warn")
+ pushToConsole(serialize(args), "log:warn")
},
info: function(...args){
- pushToConsole(args, "log:info")
+ pushToConsole(serialize(args), "log:info")
},
debug: function(...args){
- pushToConsole(args, "log:debug")
+ pushToConsole(serialize(args), "log:debug")
},
table: function(data){
- pushToConsole([data], "log:table")
+ pushToConsole(serialize([data]), "log:table")
},
count: function(label = 'default'){
counts[label] = (counts[label] || 0) + 1
- pushToConsole([label + ": " + counts[label]], "log:count")
+ pushToConsole(serialize([label + ": " + counts[label]]), "log:count")
},
countReset: function(label = 'default'){
counts[label] = 0
},
trace: function(...args){
const stack = new Error().stack
- pushToConsole([...args, stack], "log:trace")
+ pushToConsole(serialize([...args, stack]), "log:trace")
},
dir: function(obj){
- pushToConsole([obj], "log:dir")
+ pushToConsole(serialize([obj]), "log:dir")
},
dirxml: function(obj){
- pushToConsole([obj], "log:dirxml")
+ pushToConsole(serialize([obj]), "log:dirxml")
},
time: function(label = 'default'){
timers[label] = performance.now()
@@ -65,19 +133,19 @@ export const generateConsoleScript = ({ html, css }) => {
timeEnd: function(label = 'default'){
if (timers[label]) {
const duration = performance.now() - timers[label]
- pushToConsole([label + ": " + duration.toFixed(2) + "ms"], "log:time")
+ pushToConsole(serialize([label + ": " + duration.toFixed(2) + "ms"]), "log:time")
delete timers[label]
}
},
timeLog: function(label = 'default', ...args){
if (timers[label]) {
const duration = performance.now() - timers[label]
- pushToConsole([label + ": " + duration.toFixed(2) + "ms"].concat(args), "log:time")
+ pushToConsole(serialize([label + ": " + duration.toFixed(2) + "ms"].concat(args)), "log:time")
}
},
assert: function(condition, ...args){
if (!condition) {
- pushToConsole(["Assertion failed:", ...args], "log:assert")
+ pushToConsole(serialize(["Assertion failed:", ...args]), "log:assert")
}
},
clear: function(){
diff --git a/src/console.js b/src/console.js
index b79f502..2f253f6 100644
--- a/src/console.js
+++ b/src/console.js
@@ -134,15 +134,121 @@ const formatValue = (value, indentLevel = 0) => {
}
if (typeof value === 'object') {
+ if (value && value.type === 'function') {
+ const fnContent = escapeHtml(value.content)
+ const startOfBody = fnContent.indexOf('{')
+
+ const isAsync = value.async
+ const isGenerator = value.generator
+
+ let className = 'console-fn'
+ if (isAsync) {
+ className += ' console-async-fn'
+ }
+ if (isGenerator) {
+ className += ' console-generator-fn'
+ }
+
+ // Function signature logic
+ let signature
+ if (startOfBody === -1) {
+ signature = fnContent.trim()
+ } else {
+ signature = fnContent.substring(0, startOfBody).trim()
+ }
+
+ signature = signature
+ .replace(/^async\s+/, '')
+ .replace(/^function\s*\*\s*/, '')
+ .replace(/^function\s*/, '')
+ .trim()
+
+ // Function body logic
+ let functionBody
+ if (startOfBody === -1) {
+ functionBody = ''
+ } else {
+ const bodyContent = fnContent.substring(startOfBody + 1, fnContent.lastIndexOf('}'))
+ const compressedBody = bodyContent.trim().length > 0 ? '...' : ''
+ functionBody = ` {${compressedBody}}`
+ }
+
+ return `${signature}${functionBody}`
+ }
+
+ if (value && value.type === 'circular') {
+ return '[Circular]'
+ }
+
+ if (value && value.type === 'regexp') {
+ return `${escapeHtml(value.value)}`
+ }
+
+ if (value && value.type === 'unknown') {
+ return `${escapeHtml(String(value.value))}`
+ }
+
+ if (value && value.type === 'date') {
+ const isoString = value.value
+ const cleanedString = isoString
+ .replace('T', ' ')
+ .replace(/\.\d{3}Z$/, '')
+
+ return `${escapeHtml(cleanedString)}`
+ }
+
+ if (value && value.type === 'set') {
+ const short = `Set(${value.size})`
+
+ if (value.size === 0) return `${short} {}`
+
+ let result = `${short} {\n`
+
+ ;(value.values || []).forEach((v, index) => {
+ result += `${indent} ${formatValue(v, indentLevel + 1)}`
+ if (index < value.values.length - 1) result += ','
+ result += '\n'
+ })
+
+ result += `${indent}}`
+ return result
+ }
+
+ const isSymbolKey = (k) => typeof k === 'string' && k.startsWith('Symbol(') && k.endsWith(')')
+
+ const formatKey = (key) => {
+ return (isValidIdentifier(key) || isSymbolKey(key))
+ ? `${escapeHtml(key)}`
+ : `"${escapeHtml(key)}"`
+ }
+
+ if (value && value.type === 'map') {
+ const short = `Map(${value.size})`
+ if (value.size === 0) return `${short} {}`
+
+ let result = `${short} {\n`
+
+ ;(value.entries || []).forEach(([k, v], index) => {
+ const keyFormatted = formatKey(k)
+ const valueFormatted = formatValue(v, indentLevel + 1)
+
+ result += `${indent} ${keyFormatted} => ${valueFormatted}`
+
+ if (index < value.entries.length - 1) result += ','
+ result += '\n'
+ })
+
+ result += `${indent}}`
+ return result
+ }
+
const keys = Object.keys(value)
if (keys.length === 0) return '{}'
let result = '{\n'
keys.forEach((key, index) => {
- const renderedKey = isValidIdentifier(key)
- ? `${escapeHtml(key)}`
- : `"${escapeHtml(key)}"`
+ const renderedKey = formatKey(key)
result += `${indent} ${renderedKey}: ${formatValue(value[key], indentLevel + 1)}`
diff --git a/src/css/console.css b/src/css/console.css
index c3f9871..8c9c642 100644
--- a/src/css/console.css
+++ b/src/css/console.css
@@ -128,6 +128,28 @@
color: #9cdcfe;
}
+ .console-regexp {
+ color: #b46695;
+ }
+
+ .console-date {
+ color: #9cdcfe;
+ }
+
+ .console-fn::before {
+ content: "ƒ ";
+ font-style: italic;
+ color: #ce9178;
+ }
+
+ .console-async-fn::before {
+ content: "async ƒ ";
+ }
+
+ .console-generator-fn::before {
+ content: "ƒ* ";
+ }
+
.console-badge {
display: inline-block;
padding: 2px 6px;