diff --git a/CHANGELOG.md b/CHANGELOG.md index 2917714028..7fc8d8366d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ #### :rocket: New Feature +- Add new Stdlib helpers: `String.capitalize`, `String.isEmpty`, `Dict.size`, `Dict.isEmpty`, `Array.isEmpty`, `Map.isEmpty`, `Set.isEmpty`. https://github.com/rescript-lang/rescript/pull/7516 + #### :bug: Bug fix - Fix formatting of nested records in `.resi` files. https://github.com/rescript-lang/rescript/pull/7741 diff --git a/lib/es6/Stdlib_Array.js b/lib/es6/Stdlib_Array.js index b1bbc32728..f733d81a00 100644 --- a/lib/es6/Stdlib_Array.js +++ b/lib/es6/Stdlib_Array.js @@ -22,6 +22,10 @@ function fromInitializer(length, f) { return arr; } +function isEmpty(arr) { + return arr.length === 0; +} + function equal(a, b, eq) { let len = a.length; if (len === b.length) { @@ -183,6 +187,7 @@ export { fromInitializer, equal, compare, + isEmpty, indexOfOpt, lastIndexOfOpt, reduce, diff --git a/lib/es6/Stdlib_Dict.js b/lib/es6/Stdlib_Dict.js index 51a70b3144..c57b1e1fb0 100644 --- a/lib/es6/Stdlib_Dict.js +++ b/lib/es6/Stdlib_Dict.js @@ -5,25 +5,45 @@ function $$delete$1(dict, string) { delete(dict[string]); } -function forEach(dict, f) { - Object.values(dict).forEach(value => f(value)); -} +let forEach = ((dict, f) => { + for (var i in dict) { + f(dict[i]); + } +}); -function forEachWithKey(dict, f) { - Object.keys(dict).forEach(key => f(dict[key], key)); -} +let forEachWithKey = ((dict, f) => { + for (var i in dict) { + f(dict[i], i); + } +}); -function mapValues(dict, f) { - let target = {}; - Object.keys(dict).forEach(key => { - let value = dict[key]; - target[key] = f(value); - }); +let mapValues = ((dict, f) => { + var target = {}, i; + for (i in dict) { + target[i] = f(dict[i]); + } return target; -} +}); + +let size = ((dict) => { + var size = 0, i; + for (i in dict) { + size++; + } + return size; +}); + +let isEmpty = ((dict) => { + for (var _ in dict) { + return false + } + return true +}); export { $$delete$1 as $$delete, + size, + isEmpty, forEach, forEachWithKey, mapValues, diff --git a/lib/es6/Stdlib_Map.js b/lib/es6/Stdlib_Map.js index ae1b9f17e6..27d7851b1a 100644 --- a/lib/es6/Stdlib_Map.js +++ b/lib/es6/Stdlib_Map.js @@ -1 +1,11 @@ -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ + + + +function isEmpty(map) { + return map.size === 0; +} + +export { + isEmpty, +} +/* No side effect */ diff --git a/lib/es6/Stdlib_Set.js b/lib/es6/Stdlib_Set.js index ae1b9f17e6..469aca57da 100644 --- a/lib/es6/Stdlib_Set.js +++ b/lib/es6/Stdlib_Set.js @@ -1 +1,11 @@ -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ + + + +function isEmpty(set) { + return set.size === 0; +} + +export { + isEmpty, +} +/* No side effect */ diff --git a/lib/es6/Stdlib_String.js b/lib/es6/Stdlib_String.js index d68111d2a3..b6e5069cab 100644 --- a/lib/es6/Stdlib_String.js +++ b/lib/es6/Stdlib_String.js @@ -25,9 +25,23 @@ function searchOpt(s, re) { } +function isEmpty(s) { + return s.length === 0; +} + +function capitalize(s) { + if (s.length === 0) { + return s; + } else { + return s[0].toUpperCase() + s.slice(1); + } +} + export { indexOfOpt, lastIndexOfOpt, searchOpt, + isEmpty, + capitalize, } /* No side effect */ diff --git a/lib/js/Stdlib_Array.js b/lib/js/Stdlib_Array.js index f5313a3e3b..e62a6efb4e 100644 --- a/lib/js/Stdlib_Array.js +++ b/lib/js/Stdlib_Array.js @@ -22,6 +22,10 @@ function fromInitializer(length, f) { return arr; } +function isEmpty(arr) { + return arr.length === 0; +} + function equal(a, b, eq) { let len = a.length; if (len === b.length) { @@ -182,6 +186,7 @@ exports.make = make; exports.fromInitializer = fromInitializer; exports.equal = equal; exports.compare = compare; +exports.isEmpty = isEmpty; exports.indexOfOpt = indexOfOpt; exports.lastIndexOfOpt = lastIndexOfOpt; exports.reduce = reduce; diff --git a/lib/js/Stdlib_Dict.js b/lib/js/Stdlib_Dict.js index ecb9c90844..b38f7a9c40 100644 --- a/lib/js/Stdlib_Dict.js +++ b/lib/js/Stdlib_Dict.js @@ -5,24 +5,44 @@ function $$delete$1(dict, string) { delete(dict[string]); } -function forEach(dict, f) { - Object.values(dict).forEach(value => f(value)); -} +let forEach = ((dict, f) => { + for (var i in dict) { + f(dict[i]); + } +}); -function forEachWithKey(dict, f) { - Object.keys(dict).forEach(key => f(dict[key], key)); -} +let forEachWithKey = ((dict, f) => { + for (var i in dict) { + f(dict[i], i); + } +}); -function mapValues(dict, f) { - let target = {}; - Object.keys(dict).forEach(key => { - let value = dict[key]; - target[key] = f(value); - }); +let mapValues = ((dict, f) => { + var target = {}, i; + for (i in dict) { + target[i] = f(dict[i]); + } return target; -} +}); + +let size = ((dict) => { + var size = 0, i; + for (i in dict) { + size++; + } + return size; +}); + +let isEmpty = ((dict) => { + for (var _ in dict) { + return false + } + return true +}); exports.$$delete = $$delete$1; +exports.size = size; +exports.isEmpty = isEmpty; exports.forEach = forEach; exports.forEachWithKey = forEachWithKey; exports.mapValues = mapValues; diff --git a/lib/js/Stdlib_Map.js b/lib/js/Stdlib_Map.js index ae1b9f17e6..a3844084bd 100644 --- a/lib/js/Stdlib_Map.js +++ b/lib/js/Stdlib_Map.js @@ -1 +1,9 @@ -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ +'use strict'; + + +function isEmpty(map) { + return map.size === 0; +} + +exports.isEmpty = isEmpty; +/* No side effect */ diff --git a/lib/js/Stdlib_Set.js b/lib/js/Stdlib_Set.js index ae1b9f17e6..d74649202f 100644 --- a/lib/js/Stdlib_Set.js +++ b/lib/js/Stdlib_Set.js @@ -1 +1,9 @@ -/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ +'use strict'; + + +function isEmpty(set) { + return set.size === 0; +} + +exports.isEmpty = isEmpty; +/* No side effect */ diff --git a/lib/js/Stdlib_String.js b/lib/js/Stdlib_String.js index b653c67e12..09ffc8b903 100644 --- a/lib/js/Stdlib_String.js +++ b/lib/js/Stdlib_String.js @@ -25,7 +25,21 @@ function searchOpt(s, re) { } +function isEmpty(s) { + return s.length === 0; +} + +function capitalize(s) { + if (s.length === 0) { + return s; + } else { + return s[0].toUpperCase() + s.slice(1); + } +} + exports.indexOfOpt = indexOfOpt; exports.lastIndexOfOpt = lastIndexOfOpt; exports.searchOpt = searchOpt; +exports.isEmpty = isEmpty; +exports.capitalize = capitalize; /* No side effect */ diff --git a/runtime/Stdlib_Array.res b/runtime/Stdlib_Array.res index 534c631c7c..6591ae8928 100644 --- a/runtime/Stdlib_Array.res +++ b/runtime/Stdlib_Array.res @@ -44,6 +44,8 @@ let fromInitializer = (~length, f) => @get external length: array<'a> => int = "length" +let isEmpty = arr => arr->length === 0 + let rec equalFromIndex = (a, b, i, eq, len) => if i === len { true diff --git a/runtime/Stdlib_Array.resi b/runtime/Stdlib_Array.resi index 617060db10..99dc5aecfd 100644 --- a/runtime/Stdlib_Array.resi +++ b/runtime/Stdlib_Array.resi @@ -80,6 +80,24 @@ someArray->Array.length == 2 @get external length: array<'a> => int = "length" +/** +`isEmpty(array)` returns `true` if the array is empty (has length 0), `false` otherwise. + +## Examples + +```rescript +[]->Array.isEmpty->assertEqual(true) +[1, 2, 3]->Array.isEmpty->assertEqual(false) + +let emptyArray = [] +emptyArray->Array.isEmpty->assertEqual(true) + +let nonEmptyArray = ["hello"] +nonEmptyArray->Array.isEmpty->assertEqual(false) +``` +*/ +let isEmpty: array<'a> => bool + // TODO: Docs @deprecated("Use `copyWithin` instead") @send external copyAllWithin: (array<'a>, ~target: int) => array<'a> = "copyWithin" @@ -909,7 +927,7 @@ external mapWithIndex: (array<'a>, ('a, int) => 'b) => array<'b> = "map" /** `reduce(xs, init, fn)` -Applies `fn` to each element of `xs` from beginning to end. Function `fn` has two parameters: the item from the list and an “accumulator”; which starts with a value of `init`. `reduce` returns the final value of the accumulator. +Applies `fn` to each element of `xs` from beginning to end. Function `fn` has two parameters: the item from the list and an "accumulator"; which starts with a value of `init`. `reduce` returns the final value of the accumulator. ## Examples @@ -928,7 +946,7 @@ let reduce: (array<'a>, 'b, ('b, 'a) => 'b) => 'b /** `reduceWithIndex(x, init, fn)` -Applies `fn` to each element of `xs` from beginning to end. Function `fn` has three parameters: the item from the array and an “accumulator”, which starts with a value of `init` and the index of each element. `reduceWithIndex` returns the final value of the accumulator. +Applies `fn` to each element of `xs` from beginning to end. Function `fn` has three parameters: the item from the array and an "accumulator", which starts with a value of `init` and the index of each element. `reduceWithIndex` returns the final value of the accumulator. ## Examples diff --git a/runtime/Stdlib_Dict.res b/runtime/Stdlib_Dict.res index 7604962689..7a146b30ec 100644 --- a/runtime/Stdlib_Dict.res +++ b/runtime/Stdlib_Dict.res @@ -24,22 +24,45 @@ let delete = (dict, string) => { @val external copy: (@as(json`{}`) _, dict<'a>) => dict<'a> = "Object.assign" -let forEach = (dict, f) => { - dict->valuesToArray->Stdlib_Array.forEach(value => f(value)) -} +// Use %raw to support for..in which is a ~10% faster than .forEach +let forEach: (dict<'a>, 'a => unit) => unit = %raw(`(dict, f) => { + for (var i in dict) { + f(dict[i]); + } +}`) -@inline -let forEachWithKey = (dict, f) => { - dict->keysToArray->Stdlib_Array.forEach(key => f(dict->getUnsafe(key), key)) -} +// Use %raw to support for..in which is a ~10% faster than .forEach +let forEachWithKey: (dict<'a>, ('a, string) => unit) => unit = %raw(`(dict, f) => { + for (var i in dict) { + f(dict[i], i); + } +}`) -let mapValues = (dict, f) => { - let target = make() - dict->forEachWithKey((value, key) => { - target->set(key, f(value)) - }) - target -} +// Use %raw to support for..in which is a ~10% faster than .forEach +let mapValues: (dict<'a>, 'a => 'b) => dict<'b> = %raw(`(dict, f) => { + var target = {}, i; + for (i in dict) { + target[i] = f(dict[i]); + } + return target; +}`) + +// Use %raw to support for..in which is a ~10% faster than Object.keys +let size: dict<'a> => int = %raw(`(dict) => { + var size = 0, i; + for (i in dict) { + size++; + } + return size; +}`) + +// Use %raw to support for..in which is a 2x faster than Object.keys +let isEmpty: dict<'a> => bool = %raw(`(dict) => { + for (var _ in dict) { + return false + } + return true +}`) external has: (dict<'a>, string) => bool = "%dict_has" diff --git a/runtime/Stdlib_Dict.resi b/runtime/Stdlib_Dict.resi index e85f05441c..bd8edc864a 100644 --- a/runtime/Stdlib_Dict.resi +++ b/runtime/Stdlib_Dict.resi @@ -144,6 +144,44 @@ Console.log(keys) // Logs `["someKey", "someKey2"]` to the console @val external keysToArray: dict<'a> => array = "Object.keys" +/** +`size(dictionary)` returns the number of key/value pairs in the dictionary. + +## Examples +```rescript +let dict = Dict.make() +dict->Dict.size->assertEqual(0) + +dict->Dict.set("someKey", 1) +dict->Dict.set("someKey2", 2) +dict->Dict.size->assertEqual(2) + +// After deleting a key +dict->Dict.delete("someKey") +dict->Dict.size->assertEqual(1) +``` +*/ +let size: dict<'a> => int + +/** +`isEmpty(dictionary)` returns `true` if the dictionary is empty (has no key/value pairs), `false` otherwise. + +## Examples +```rescript +let emptyDict = Dict.make() +emptyDict->Dict.isEmpty->assertEqual(true) + +let dict = Dict.make() +dict->Dict.set("someKey", 1) +dict->Dict.isEmpty->assertEqual(false) + +// After clearing all keys +dict->Dict.delete("someKey") +dict->Dict.isEmpty->assertEqual(true) +``` +*/ +let isEmpty: dict<'a> => bool + /** `valuesToArray(dictionary)` returns an array of all the values of the dictionary. diff --git a/runtime/Stdlib_Map.res b/runtime/Stdlib_Map.res index 7d534aa7d8..2c821c0021 100644 --- a/runtime/Stdlib_Map.res +++ b/runtime/Stdlib_Map.res @@ -7,6 +7,8 @@ type t<'k, 'v> @get external size: t<'k, 'v> => int = "size" +let isEmpty = map => map->size === 0 + @send external clear: t<'k, 'v> => unit = "clear" @send external forEach: (t<'k, 'v>, 'v => unit) => unit = "forEach" diff --git a/runtime/Stdlib_Map.resi b/runtime/Stdlib_Map.resi index cc4767e90e..8068ab9109 100644 --- a/runtime/Stdlib_Map.resi +++ b/runtime/Stdlib_Map.resi @@ -95,6 +95,25 @@ let size = map->Map.size // 1 @get external size: t<'k, 'v> => int = "size" +/** +`isEmpty(map)` returns `true` if the map has no key/value pairs, `false` otherwise. + +## Examples +```rescript +let emptyMap = Map.make() +emptyMap->Map.isEmpty->assertEqual(true) + +let map = Map.make() +map->Map.set("someKey", "someValue") +map->Map.isEmpty->assertEqual(false) + +// After clearing the map +map->Map.clear +map->Map.isEmpty->assertEqual(true) +``` +*/ +let isEmpty: t<'k, 'v> => bool + /** Clears all entries in the map. diff --git a/runtime/Stdlib_Set.res b/runtime/Stdlib_Set.res index 61040dc863..02a21ad1c9 100644 --- a/runtime/Stdlib_Set.res +++ b/runtime/Stdlib_Set.res @@ -7,6 +7,8 @@ type t<'a> @get external size: t<'a> => int = "size" +let isEmpty = set => set->size === 0 + @send external clear: t<'a> => unit = "clear" @send external add: (t<'a>, 'a) => unit = "add" diff --git a/runtime/Stdlib_Set.resi b/runtime/Stdlib_Set.resi index 36312a84aa..5f9ca18668 100644 --- a/runtime/Stdlib_Set.resi +++ b/runtime/Stdlib_Set.resi @@ -94,6 +94,25 @@ let size = set->Set.size // 2 @get external size: t<'a> => int = "size" +/** +`isEmpty(set)` returns `true` if the set has no values, `false` otherwise. + +## Examples +```rescript +let emptySet = Set.make() +emptySet->Set.isEmpty->assertEqual(true) + +let set = Set.make() +set->Set.add("someValue") +set->Set.isEmpty->assertEqual(false) + +// After clearing the set +set->Set.clear +set->Set.isEmpty->assertEqual(true) +``` +*/ +let isEmpty: t<'a> => bool + /** Clears all entries in the set. diff --git a/runtime/Stdlib_String.res b/runtime/Stdlib_String.res index a1ca7605f8..e0a3f9cbc3 100644 --- a/runtime/Stdlib_String.res +++ b/runtime/Stdlib_String.res @@ -168,6 +168,15 @@ external substringToEnd: (string, ~start: int) => string = "substring" @send external localeCompare: (string, string) => float = "localeCompare" +let isEmpty = s => length(s) == 0 + +let capitalize = s => + if isEmpty(s) { + s + } else { + toUpperCase(getUnsafe(s, 0)) ++ sliceToEnd(s, ~start=1) + } + external ignore: string => unit = "%ignore" @get_index external getSymbolUnsafe: (string, Stdlib_Symbol.t) => 'a = "" diff --git a/runtime/Stdlib_String.resi b/runtime/Stdlib_String.resi index 18a616525b..b0b76dce38 100644 --- a/runtime/Stdlib_String.resi +++ b/runtime/Stdlib_String.resi @@ -808,6 +808,36 @@ String.searchOpt("no numbers", /\d+/) == None */ let searchOpt: (string, Stdlib_RegExp.t) => option +/** +`isEmpty(str)` returns `true` if the string is empty (has zero length), +`false` otherwise. + +## Examples + +```rescript +String.isEmpty("") == true +String.isEmpty("hello") == false +String.isEmpty(" ") == false +``` +*/ +let isEmpty: string => bool + +/** +`capitalize(str)` returns a new string with the first character converted to +uppercase and the remaining characters unchanged. If the string is empty, +returns the empty string. + +## Examples + +```rescript +String.capitalize("hello") == "Hello" +String.capitalize("HELLO") == "HELLO" +String.capitalize("hello world") == "Hello world" +String.capitalize("") == "" +``` +*/ +let capitalize: string => string + /** `slice(str, ~start, ~end)` returns the substring of `str` starting at character `start` up to but not including `end`. diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/package.json b/tests/analysis_tests/tests-reanalyze/deadcode/package.json index a73dcaf9bc..c9ff3996f7 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/package.json +++ b/tests/analysis_tests/tests-reanalyze/deadcode/package.json @@ -3,7 +3,7 @@ "private": true, "scripts": { "build": "rescript legacy build", - "clean": "rescript clean" + "clean": "rescript legacy clean" }, "dependencies": { "@rescript/react": "link:../../../dependencies/rescript-react", diff --git a/tests/analysis_tests/tests/package.json b/tests/analysis_tests/tests/package.json index 601884c23c..01bbe0ecbb 100644 --- a/tests/analysis_tests/tests/package.json +++ b/tests/analysis_tests/tests/package.json @@ -3,7 +3,7 @@ "private": true, "scripts": { "build": "rescript legacy build", - "clean": "rescript clean" + "clean": "rescript legacy clean" }, "dependencies": { "@rescript/react": "link:../../dependencies/rescript-react", diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index 3361871351..802aebe53b 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -405,7 +405,7 @@ Path Array. "kind": 12, "tags": [], "detail": "(array<'a>, 'b, ('b, 'a) => 'b) => 'b", - "documentation": {"kind": "markdown", "value": "\n`reduce(xs, init, fn)`\n\nApplies `fn` to each element of `xs` from beginning to end. Function `fn` has two parameters: the item from the list and an “accumulator”; which starts with a value of `init`. `reduce` returns the final value of the accumulator.\n\n## Examples\n\n```rescript\nArray.reduce([2, 3, 4], 1, (a, b) => a + b) == 10\n\nArray.reduce([\"a\", \"b\", \"c\", \"d\"], \"\", (a, b) => a ++ b) == \"abcd\"\n\n[1, 2, 3]->Array.reduce(list{}, List.add) == list{3, 2, 1}\n\nArray.reduce([], list{}, List.add) == list{}\n```\n"} + "documentation": {"kind": "markdown", "value": "\n`reduce(xs, init, fn)`\n\nApplies `fn` to each element of `xs` from beginning to end. Function `fn` has two parameters: the item from the list and an \"accumulator\"; which starts with a value of `init`. `reduce` returns the final value of the accumulator.\n\n## Examples\n\n```rescript\nArray.reduce([2, 3, 4], 1, (a, b) => a + b) == 10\n\nArray.reduce([\"a\", \"b\", \"c\", \"d\"], \"\", (a, b) => a ++ b) == \"abcd\"\n\n[1, 2, 3]->Array.reduce(list{}, List.add) == list{3, 2, 1}\n\nArray.reduce([], list{}, List.add) == list{}\n```\n"} }, { "label": "sliceToEnd", "kind": 12, @@ -430,6 +430,12 @@ Path Array. "tags": [], "detail": "(array<'a>, int, 'a) => unit", "documentation": {"kind": "markdown", "value": "\n`set(array, index, item)` sets the provided `item` at `index` of `array`.\n\nBeware this will *mutate* the array.\n\n## Examples\n\n```rescript\nlet array = [\"Hello\", \"Hi\", \"Good bye\"]\narray->Array.set(1, \"Hello\")\n\narray[1] == Some(\"Hello\")\n```\n"} + }, { + "label": "isEmpty", + "kind": 12, + "tags": [], + "detail": "array<'a> => bool", + "documentation": {"kind": "markdown", "value": "\n`isEmpty(array)` returns `true` if the array is empty (has length 0), `false` otherwise.\n\n## Examples\n\n```rescript\n[]->Array.isEmpty->assertEqual(true)\n[1, 2, 3]->Array.isEmpty->assertEqual(false)\n\nlet emptyArray = []\nemptyArray->Array.isEmpty->assertEqual(true)\n\nlet nonEmptyArray = [\"hello\"]\nnonEmptyArray->Array.isEmpty->assertEqual(false)\n```\n"} }, { "label": "filterWithIndex", "kind": 12, @@ -507,7 +513,7 @@ Path Array. "kind": 12, "tags": [], "detail": "(array<'a>, 'b, ('b, 'a, int) => 'b) => 'b", - "documentation": {"kind": "markdown", "value": "\n`reduceWithIndex(x, init, fn)`\n\nApplies `fn` to each element of `xs` from beginning to end. Function `fn` has three parameters: the item from the array and an “accumulator”, which starts with a value of `init` and the index of each element. `reduceWithIndex` returns the final value of the accumulator.\n\n## Examples\n\n```rescript\nArray.reduceWithIndex([1, 2, 3, 4], 0, (acc, x, i) => acc + x + i) == 16\n\nArray.reduceWithIndex([1, 2, 3], list{}, (acc, v, i) => list{v + i, ...acc}) == list{5, 3, 1}\n\nArray.reduceWithIndex([], list{}, (acc, v, i) => list{v + i, ...acc}) == list{}\n```\n"} + "documentation": {"kind": "markdown", "value": "\n`reduceWithIndex(x, init, fn)`\n\nApplies `fn` to each element of `xs` from beginning to end. Function `fn` has three parameters: the item from the array and an \"accumulator\", which starts with a value of `init` and the index of each element. `reduceWithIndex` returns the final value of the accumulator.\n\n## Examples\n\n```rescript\nArray.reduceWithIndex([1, 2, 3, 4], 0, (acc, x, i) => acc + x + i) == 16\n\nArray.reduceWithIndex([1, 2, 3], list{}, (acc, v, i) => list{v + i, ...acc}) == list{5, 3, 1}\n\nArray.reduceWithIndex([], list{}, (acc, v, i) => list{v + i, ...acc}) == list{}\n```\n"} }, { "label": "some", "kind": 12,