diff --git a/static_core/plugins/ets/sdk/api/@ohos.util.ets b/static_core/plugins/ets/sdk/api/@ohos.util.ets index 98c5f824ecc30b21adc720b78ea80e2ee133fccf..ccefd5278b86e27b1b6ba4b703268fc084d3acb8 100644 --- a/static_core/plugins/ets/sdk/api/@ohos.util.ets +++ b/static_core/plugins/ets/sdk/api/@ohos.util.ets @@ -455,7 +455,7 @@ export namespace util { * %O: Object. A string representation of an object with generic JavaScript object formatting. * Similar to util.inspect() without options. This will show the full object not including non-enumerable * properties and proxies. - * %c: CSS. This specifier is ignored and will skip any CSS passed in. + * %c: [DEPRECATED] - this specifier is ignored with they argument * %%: single percent sign ('%'). This does not consume an argument. * * @param s: A printf-like format string. diff --git a/static_core/plugins/ets/stdlib/std/core/ConsoleHelper.ets b/static_core/plugins/ets/stdlib/std/core/ConsoleHelper.ets index 0ceec9ab1d3cac7baafbf01acedeff1413fbee14..32ddde2608e990507ef7fb6251b8a652aa0dfee7 100644 --- a/static_core/plugins/ets/stdlib/std/core/ConsoleHelper.ets +++ b/static_core/plugins/ets/stdlib/std/core/ConsoleHelper.ets @@ -135,7 +135,16 @@ export class Formatter { } private static arrayToString(delimiter: string, arr: Array, depth: number, full: boolean = false): string { - return ('[ ' + Formatter.arrayToStringImpl(delimiter, arr, depth, full) + (full ? ('[length]: ' + arr.length) : '') + ' ]') + let stringifiedArray = Formatter.arrayToStringImpl(delimiter, arr, depth, full); + let postfix = ''; + if (full) { + if (arr.length == 0) { + postfix = '[length]: ' + arr.length; + } else { + postfix = ', [length]: ' + arr.length; + } + } + return ('[ ' + stringifiedArray + postfix + ' ]') } private static functionDescription(obj: Object, delimiter: string = '\n', isSub: boolean = false, full: boolean = false, inArray: boolean = false): string { @@ -228,48 +237,148 @@ export class Formatter { .trim() } - private static processArg(flag: string, arg: Object): string { - // %c: CSS. This specifier is ignored and will skip any CSS passed in. - if (flag == "c" && (arg instanceof String)) { - return Formatter.extractContents(arg.toString()) - } else if (flag == "s" && !(arg instanceof BigInt)) { - return arg.toString() - } else if (flag == "d") { - if (arg instanceof BigInt) { - throw new Error("invalid flag " + flag + " for arg " + arg) + private static formatAsLittleObject(item: Any): string + { + if (item === undefined) { + return "undefined" + } + if (item === null) { + return "null" + } + return Formatter.objectToString(__narrowAny(item), true) + } + + private static formatAsBigObject(item: Any): string + { + if (item === undefined) { + return "undefined" + } + if (item === null) { + return "null" + } + return Formatter.objectToString(__narrowAny(item)) + } + + private static formatAsNumeric(item: Any, policy: "nan-int" | "nan-num" | "zero-num"): string + { + if (item === null || item === undefined) { + return "NaN" + } + if (item instanceof BigInt) + { + return item.toString() + "n" + } + if (item instanceof String && item == "") { + if (policy == "nan-int" || policy == "nan-num") { + return "NaN" + } else { + return "0" } - return "" + new Number(arg.toString()) - } else if (flag == "i") { - if (arg instanceof BigInt) { - throw new Error("invalid flag " + flag + " for arg " + arg) + } + if (item instanceof String && isNaN(new Number(item as String))) { + return "NaN" + } + if (item instanceof String) { + if (policy == "nan-int") { + return parseInt(item, 10).toString(); + } else if (policy == "nan-num") { + return parseFloat(item).toString() + } else { + return (new Number(item)).toString() } - return "" + parseInt(arg.toString(), 10) - } - else if (flag == "f") { - return "" + parseFloat(arg.toString()) - } else if (flag == "j") { - let res = "" - try { - res = JSON.stringify(arg) + } + if (item instanceof Array && item.length > 0) { + return Formatter.formatAsNumeric(item[0], policy) + } + if (item instanceof Array) { + return "NaN" + } + if (item instanceof Object) { + let itemstr = item.toString(); + if (policy == "nan-int") { + return parseInt(itemstr, 10).toString(); + } else if (policy == "nan-num") { + return parseFloat(itemstr).toString() + } else { + return (new Number(itemstr)).toString() } - catch(error) { - if (typeof arg === "object") { - return Formatter.objectToString(arg) - } else { - throw new Error("error parse json") - } + } + return "NaN"; + } + + private static formatAsJson(item: Any): string + { + if (item === null) { + return "null" + } + if (item === undefined) { + return "undefined" + } + let res = "" + try { + res = JSON.stringify(item) + } + catch(error) { + if (item instanceof Object) { + return Formatter.objectToString(item) + } else { + throw new Error("error parse json") } - return res - } else if (flag == "o") { - // Object. A string representation of an object - // Similar to util.inspect() with options { showHidden: true, showProxy: true } - return Formatter.objectToString(arg, true) - } else if (flag == "O") { - // Object. A string representation of an object - // Similar to util.inspect() without options - return Formatter.objectToString(arg) } + return res + } + + private static formatAsString(item: Any): string + { + if (item instanceof BigInt) { + return item.toString() + "n" + } + if (item === null) { + return "null" + } + if (item === undefined) { + return "undefined" + } + return __narrowAny(item).toString() + } + + private static formatAsC(item: Any): string + { + if (item === null) { + return "null" + } + if (item === undefined) { + return "undefined" + } + return __narrowAny(item).toString() + } + private static processArg(flag: string, item: Any): string + { + if (flag == "o") { + return Formatter.formatAsLittleObject(item) + } + if (flag == "O") { + return Formatter.formatAsBigObject(item) + } + if (flag == "i") { + return Formatter.formatAsNumeric(item, "nan-int") + } + if (flag == "d") { + return Formatter.formatAsNumeric(item, "zero-num") + } + if (flag == "f") { + return Formatter.formatAsNumeric(item, "nan-num") + } + if (flag == "j") { + return Formatter.formatAsJson(item) + } + if (flag == "s") { + return Formatter.formatAsString(item) + } + if (flag == "c") { + return Formatter.formatAsC(item) + } return "%" + flag } @@ -309,13 +418,10 @@ export class Formatter { res.append(s[j]); } const flagIndex = preI + i + 1; - const flag = s[flagIndex]; + const flag: string = s[flagIndex]; if (flags.includes(flag)) { let d = args[argCounter]; - if (d == undefined || d == null) { - d = ""; - } - res.append(Formatter.processArg(flag, __narrowAny(d))); + res.append(Formatter.processArg(flag, d)); ++argCounter; } else if (flag == "%") { res.append("%"); diff --git a/static_core/plugins/ets/tests/ets_func_tests/std/core/ConsoleFormatSpecTest.ets b/static_core/plugins/ets/tests/ets_func_tests/std/core/ConsoleFormatSpecTest.ets new file mode 100644 index 0000000000000000000000000000000000000000..658cb6e5933577e4379eee87d7a2dc936ca4a1b7 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_func_tests/std/core/ConsoleFormatSpecTest.ets @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + function main() { + console.log("%i", 10); + console.log("%i", Int.toByte(128)); + console.log("%i", 3.14); + console.log("%i", "1234.567"); + console.log("%i", undefined); + console.log("%i", true); + console.log("%i", [[123], [456]]); + console.log("%i", [] as Any[]); + console.log("%i", 15n); + console.log("%i", ""); + console.log("%i", "meow"); + + console.log("---") + console.log("%d", 10); + console.log("%d", Int.toByte(128)); + console.log("%d", 3.14); + console.log("%d", "1234.567"); + console.log("%d", undefined); + console.log("%d", true); + console.log("%d", [[123], [456]]); + console.log("%d", [] as Any[]); + console.log("%d", ""); + console.log("%d", "meow"); + + console.log("---") + console.log("%f", 10); + console.log("%f", Int.toByte(128)); + console.log("%f", 3.14); + console.log("%f", "1234.567"); + console.log("%f", undefined); + console.log("%f", true); + console.log("%f", [[123], [456]]); + console.log("%f", [] as Any[]); + console.log("%f", ""); + console.log("%f", "meow"); + + console.log("---") + console.log("%o", [[1, 2]]); + console.log("%O", [[1, 2]]); + + console.log("---") + console.log("%s", "hello, world"); + console.log("%s", [1, 2, 3]); + console.log("%s", Int.toByte(128)); + console.log("%s", undefined); + + console.log("---") + console.log("%j", "hello, world"); + console.log("%j", 123); + console.log("%j", [1, 2, 3]); + console.log("%j", true); + console.log("%j", undefined); + console.log("%j", null); +} diff --git a/static_core/plugins/ets/tests/ets_func_tests/std/core/ConsoleFormatSpecTest.ets.expected b/static_core/plugins/ets/tests/ets_func_tests/std/core/ConsoleFormatSpecTest.ets.expected new file mode 100644 index 0000000000000000000000000000000000000000..c6298a432700bcf4d768078641c1dd8de97364d0 --- /dev/null +++ b/static_core/plugins/ets/tests/ets_func_tests/std/core/ConsoleFormatSpecTest.ets.expected @@ -0,0 +1,61 @@ +# Copyright (c) 2025 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +10 +-128 +3 +1234 +NaN +NaN +123 +NaN +15n +NaN +NaN +--- +10 +-128 +3.14 +1234.567 +NaN +NaN +123 +NaN +0 +NaN +--- +10 +-128 +3.14 +1234.567 +NaN +NaN +123 +NaN +NaN +NaN +--- +[ [ 1, 2, [length]: 2 ], [length]: 1 ] +[ [ 1, 2 ] ] +--- +hello, world +1,2,3 +-128 +undefined +--- +"hello, world" +123 +[1,2,3] +true +undefined +null diff --git a/static_core/plugins/ets/tests/ets_sdk/api/@ohos/util/FormatTest.ets b/static_core/plugins/ets/tests/ets_sdk/api/@ohos/util/FormatTest.ets index 2c34753dc4a5751479de7b4e2866f12a9a4a71ba..43e8bae1fdf984df840f8e09cf321924d2250798 100644 --- a/static_core/plugins/ets/tests/ets_sdk/api/@ohos/util/FormatTest.ets +++ b/static_core/plugins/ets/tests/ets_sdk/api/@ohos/util/FormatTest.ets @@ -34,7 +34,6 @@ function main(): int { failures += check(testFormatNoMatchArgs(), "Test util.format() with no matching flags & arguments") failures += check(testFormatMoreArgsThanInUtilFormat(), "Test util.format() with more args than in format string") failures += check(testFormatJSON(), "Test util.format() JSON") - failures += check(testFormatRemoveCSS(), "Test util.format() remove CSS from string") failures += check(testFormatEscapePercent(), "Test util.format() for %%") failures += check(testEmptyFormatWithArgs(), "Test util.format() with args & empty format string") failures += check(testNonEmptyFormatWithArgs(), "Test util.format() with args & non-empty format string") @@ -106,25 +105,6 @@ function testFormatMoreArgsThanInUtilFormat(): int { return success } -function testFormatRemoveCSS(): int { - let text = '\ - \ - \ - \ - \ - Content' - - let res = util.format("without css: %c", text) - arktest.assertEQ(res, "without css: Content") - return success -} - class TestObject1 { x: int = 5 y: int = 6 @@ -154,7 +134,7 @@ class TestObject2 { } } function testFormatObject(): int { - + let res = util.format("Object: %o", new TestObject2("k28", 14, 0.1f)) let expected = "Object: { strField: 'k28',\nintField: 14,\nfloatField: 0.1,\nboolField: false }" arktest.assertEQ(res, expected) @@ -198,7 +178,7 @@ function testArrayOfCircularObjects() : int { arr[0] = circularObject arr[1] = circularObject - let expected = "[ { prop: 'Circular Reference Example',\n reference: [Circular]\n }, \n{ prop: 'Circular Reference Example',\n reference: [Circular]\n }[length]: 2 ]" + let expected = "[ { prop: 'Circular Reference Example',\n reference: [Circular]\n }, \n{ prop: 'Circular Reference Example',\n reference: [Circular]\n }, [length]: 2 ]" let expected1 = "[ { prop: 'Circular Reference Example',\n reference: [Circular]\n }, \n{ prop: 'Circular Reference Example',\n reference: [Circular]\n } ]" let res = util.format("%o", arr) @@ -251,7 +231,7 @@ function testFormatArrayObect(): int { let arr = new Array(1) arr[0] = new TestObject4() - let expected = "Array: [ { f1: '',\n k1: 8,\n t: false }[length]: 1 ]" + let expected = "Array: [ { f1: '',\n k1: 8,\n t: false }, [length]: 1 ]" let expected1 = "Array: [ { f1: '',\n k1: 8,\n t: false } ]" arktest.assertEQ(util.format('Array: %o', arr), expected) @@ -280,7 +260,7 @@ function testFunctionsArrayObject() : int { let arr = new Array<(arg: string)=>string>(1) arr[0] = lambda - let expected = "Array of functions: [ { [Function: null] [length]: 0, [name] :'null', [prototype]: null { [constructor]: [Circular] } }[length]: 1 ]" + let expected = "Array of functions: [ { [Function: null] [length]: 0, [name] :'null', [prototype]: null { [constructor]: [Circular] } }, [length]: 1 ]" let expected1 = "Array of functions: [ [Function: null] ]" arktest.assertEQ(util.format('Array of functions: %o', arr), expected)