diff --git a/.ameba.yml b/.ameba.yml index 47647e8e1..0e5043707 100644 --- a/.ameba.yml +++ b/.ameba.yml @@ -3,3 +3,9 @@ Metrics/CyclomaticComplexity: Lint/NotNil: Enabled: false + +Documentation/DocumentationAdmonition: + Enabled: false + +Lint/UselessAssign: + ExcludeTypeDeclarations: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56ec93981..2e69dfeb3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-13, macos-latest] runs-on: ${{ matrix.os }} steps: @@ -31,11 +31,11 @@ jobs: mkdir build docker-compose run --rm app crystal build src/mint.cr -o build/mint-${GITHUB_REF_SLUG}-linux --static --no-debug --release - - if: matrix.os == 'macos-latest' + - if: startsWith(matrix.os, 'macos') name: Build binary (macOS) run: | mkdir build - crystal build src/mint.cr -o build/mint-${GITHUB_REF_SLUG}-osx --release + crystal build src/mint.cr -o build/mint-${GITHUB_REF_SLUG}-${{ matrix.os }} --release - name: Upload artifacts uses: actions/upload-artifact@v3 @@ -50,8 +50,8 @@ jobs: aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws_bucket: ${{ secrets.AWS_BUCKET }} aws_key_id: ${{ secrets.AWS_KEY }} - source_dir: build destination_dir: "" + source_dir: build - if: startsWith(github.ref, 'refs/tags/') name: Upload to GitHub Releases @@ -59,6 +59,6 @@ jobs: with: repo_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ github.ref }} - file: build/* overwrite: true file_glob: true + file: build/* diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 2cc00a9da..38ed9b0b9 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -39,9 +39,9 @@ jobs: - name: Log into ${{ env.REGISTRY }} registry uses: docker/login-action@v3 with: + password: ${{ secrets.GITHUB_TOKEN }} registry: ${{ env.REGISTRY }} username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} # Extract metadata (tags, labels) for Docker # https://github.com/docker/metadata-action @@ -52,12 +52,12 @@ jobs: images: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | - type=ref,event=tag + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}} type=ref,event=branch - type=ref,event=pr type=sha,format=long - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} + type=ref,event=tag + type=ref,event=pr # Build and push Docker image with Buildx # https://github.com/docker/build-push-action @@ -67,10 +67,10 @@ jobs: with: context: . push: true - cache-from: type=gha cache-to: type=gha,mode=max + cache-from: type=gha + labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.meta.outputs.tags }} platforms: | linux/amd64 linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/ci-base.yml b/.github/workflows/ci-base.yml new file mode 100644 index 000000000..706d49497 --- /dev/null +++ b/.github/workflows/ci-base.yml @@ -0,0 +1,57 @@ +name: CI + +on: + workflow_call: + inputs: + crystal-version: + required: true + type: string + +jobs: + test: + name: Test + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-13] + runs-on: ${{ matrix.os }} + + steps: + - name: Install Crystal + uses: crystal-lang/install-crystal@v1 + with: + crystal: ${{ inputs.crystal-version }} + + - name: Download source + uses: actions/checkout@v4 + + - name: Install dependencies + run: shards install + + - name: Run specs + run: crystal spec --error-on-warnings --error-trace + + - name: Build the binary + run: shards build --error-on-warnings --error-trace + + - name: Run core specs (Firefox) + working-directory: ./core/tests + run: ../../bin/mint test -b firefox + + - name: Run core specs (Google Chrome) + working-directory: ./core/tests + run: ../../bin/mint test -b chrome + + - name: Check formatting (Mint) + working-directory: ./core + run: ../bin/mint format --check + + - name: Check formatting (Mint tests) + working-directory: ./core/tests + run: ../../bin/mint format --check + + - name: Check formatting (Crystal) + run: crystal tool format --check + + - name: Run ameba + run: bin/ameba diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml index 99dc0555c..a477e2d50 100644 --- a/.github/workflows/ci-nightly.yml +++ b/.github/workflows/ci-nightly.yml @@ -7,49 +7,6 @@ on: jobs: test: - name: Test - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} - - steps: - - name: Install Crystal - uses: crystal-lang/install-crystal@v1 - with: - crystal: nightly - - - name: Download source - uses: actions/checkout@v4 - - - name: Install dependencies - run: shards install - - - name: Run specs - run: crystal spec --error-on-warnings --error-trace - - - name: Build the binary - run: shards build --error-on-warnings --error-trace - - - name: Run core specs (Firefox) - working-directory: ./core/tests - run: ../../bin/mint test -b firefox - - - name: Run core specs (Google Chrome) - working-directory: ./core/tests - run: ../../bin/mint test -b chrome - - - name: Check formatting (Mint) - working-directory: ./core - run: ../bin/mint format --check - - - name: Check formatting (Mint tests) - working-directory: ./core/tests - run: ../../bin/mint format --check - - - name: Check formatting (Crystal) - run: crystal tool format --check - - - name: Run ameba - run: bin/ameba + uses: ./.github/workflows/ci-base.yml + with: + crystal-version: nightly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0cb766df..04ac421b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,49 +11,6 @@ on: jobs: test: - name: Test - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} - - steps: - - name: Install Crystal - uses: crystal-lang/install-crystal@v1 - with: - crystal: latest - - - name: Download source - uses: actions/checkout@v4 - - - name: Install dependencies - run: shards install - - - name: Run specs - run: crystal spec --error-on-warnings --error-trace - - - name: Build the binary - run: shards build --error-on-warnings --error-trace - - - name: Run core specs (Firefox) - working-directory: ./core/tests - run: ../../bin/mint test -b firefox - - - name: Run core specs (Google Chrome) - working-directory: ./core/tests - run: ../../bin/mint test -b chrome - - - name: Check formatting (Mint) - working-directory: ./core - run: ../bin/mint format --check - - - name: Check formatting (Mint tests) - working-directory: ./core/tests - run: ../../bin/mint format --check - - - name: Check formatting (Crystal) - run: crystal tool format --check - - - name: Run ameba - run: bin/ameba + uses: ./.github/workflows/ci-base.yml + with: + crystal-version: latest diff --git a/.gitignore b/.gitignore index c284ca24b..6f8bafc5f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ /lib/ /bin/ /.shards/ -.vscode \ No newline at end of file +.vscode +node_modules +coverage diff --git a/.tool-versions b/.tool-versions index 1868f63d6..7b43fac36 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,4 @@ -crystal 1.9.1 +crystal 1.10.1 mint 0.19.0 +nodejs 20.10.0 +yarn 1.22.19 diff --git a/Makefile b/Makefile index e04ddb5ef..29f8ae433 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ test: spec ameba .PHONY: test-core test-core: build - cd core/tests && ../../bin/mint test -b firefox + cd core/tests && ../../bin/mint test -b chrome .PHONY: development development: build @@ -37,6 +37,17 @@ local: build documentation: rm -rf docs && crystal docs -# This builds the binary and depends on files in "src" and "core" directories. -bin/mint: $(shell find src -type f) $(shell find core/source -type f) +src/assets/runtime.js: $(shell find runtime/src -type f) + cd runtime && make index + +src/assets/runtime_test.js: $(shell find runtime/src -type f) + cd runtime && make index_testing + +# This builds the binary and depends on files in some directories. +bin/mint: \ + $(shell find core/source -type f) \ + $(shell find runtime/src -type f) \ + $(shell find src -type f) \ + src/assets/runtime_test.js \ + src/assets/runtime.js shards build --error-on-warnings --error-trace --progress diff --git a/core/source/Array.mint b/core/source/Array.mint index 3d44fbd7d..c5c5807db 100644 --- a/core/source/Array.mint +++ b/core/source/Array.mint @@ -8,10 +8,7 @@ module Array { Array.any([1, 3], (number : Number) : Bool { number % 2 == 0 }) == false */ fun any (array : Array(item), function : Function(item, Bool)) : Bool { - case Array.find(array, function) { - Maybe.Nothing => false - Maybe.Just => true - } + Array.find(array, function) != Maybe.Nothing } /* @@ -20,14 +17,14 @@ module Array { Array.append([1, 1, 2] [3, 5, 8]) == [1, 1, 2, 3, 5, 8] */ fun append (array1 : Array(item), array2 : Array(item)) : Array(item) { - `[].concat(#{array1}).concat(#{array2})` + `[...#{array1}, ...#{array2}]` } /* Returns the element at the given index as a `Maybe(item)`. - Array.at([0], 0) == Maybe::Just(0) - Array.at([0], 1) == Maybe::Nothing() + Array.at([0], 0) == Maybe.Just(0) + Array.at([0], 1) == Maybe.Nothing() */ fun at (array : Array(item), index : Number) : Maybe(item) { array[index] @@ -35,9 +32,9 @@ module Array { /* Flattens an `Array(Maybe(item))` into an `Array(item)`, by unwrapping the - items and skipping all elements of `Maybe::Nothing`. + items and skipping all elements of `Maybe.Nothing`. - Array.compact([Maybe::Just("A"), Maybe::Nothing()]) == ["A"] + Array.compact([Maybe.Just("A"), Maybe.Nothing()]) == ["A"] */ fun compact (array : Array(Maybe(item))) : Array(item) { Array.reduce( @@ -45,8 +42,8 @@ module Array { [], (memo : Array(item), item : Maybe(item)) : Array(item) { case item { - Maybe.Just(value) => Array.push(memo, value) - Maybe.Nothing => memo + Just(value) => Array.push(memo, value) + Nothing => memo } }) } @@ -70,7 +67,7 @@ module Array { ` (() => { for (let item of #{array}) { - if (_compare(#{other}, item)) { + if (#{%compare%}(#{other}, item)) { return true } } @@ -98,7 +95,7 @@ module Array { ` (() => { if (#{index} < 0 || #{index} >= #{array}.length) { return #{array} } - const result = Array.from(#{array}) + const result = [...#{array}] result.splice(#{index}, 1) return result })() @@ -131,15 +128,14 @@ module Array { /* Finds the first element in the array that matches the predicate function. - Array.find([1, 2, 3, 4], (number : Number) { number % 2 == 0 }) == Maybe::Just(2) + Array.find([1, 2, 3, 4], (number : Number) { number % 2 == 0 }) == Maybe.Just(2) */ fun find (array : Array(item), function : Function(item, Bool)) : Maybe(item) { - Array.first( - for item of array { - item - } when { - function(item) - }) + for item of array { + item + } when { + function(item) + }[0] } /* @@ -149,7 +145,7 @@ module Array { Array.findByAndMap( [1, 2, 3, 4], (number : Number) : (Bool, value) { {number % 2 == 0, "Two"} } - ) == Maybe::Just("Two") + ) == Maybe.Just("Two") */ fun findByAndMap ( array : Array(item), @@ -161,21 +157,21 @@ module Array { const [found, value] = #{function}(item) if (found) { - return #{Maybe::Just(`value`)} + return #{Maybe.Just(`value`)} } } - return #{Maybe::Nothing} + return #{Maybe.Nothing} })() ` } /* - Returns the first element of the array as `Maybe::Just(item)` or - `Maybe::Nothing`. + Returns the first element of the array as `Maybe.Just(item)` or + `Maybe.Nothing`. - Array.first(["a", "x"]) == Maybe::Just("a") - Array.first([]) == Maybe::Nothing + Array.first(["a", "x"]) == Maybe.Just("a") + Array.first([]) == Maybe.Nothing */ fun first (array : Array(item)) : Maybe(item) { array[0] @@ -247,7 +243,7 @@ module Array { let lowerLimit = 0 #{array} = - Array.from(#{array}).reverse() + [...#{array}].reverse() for (var $0 = 0; $0 < groups; $0++) { lowerLimit = $0 * #{size}; @@ -273,7 +269,7 @@ module Array { ` (() => { for (let index = 0; index < #{array}.length; index++) { - if (_compare(#{value}, #{method}(#{array}[index]))) { + if (#{%compare%}(#{value}, #{method}(#{array}[index]))) { return index } } @@ -306,7 +302,7 @@ module Array { fun insertAt (array : Array(item), item : item, position : Number) : Array(item) { ` (() => { - const result = Array.from(#{array}) + const result = [...#{array}] if (#{position} <= 0) { result.unshift(#{item}) @@ -339,19 +335,20 @@ module Array { } /* - Returns the last element of the array as `Maybe::Just(a)` or `Maybe::Nothing`. + Returns the last element of the array as `Maybe.Just(a)` or `Maybe.Nothing`. - Array.last(["x", "a"]) == Maybe::Just("a") - Array.last([]) == Maybe::Nothing + Array.last(["x", "a"]) == Maybe.Just("a") + Array.last([]) == Maybe.Nothing */ fun last (array : Array(item)) : Maybe(item) { ` (() => { - let last = #{array}[#{array}.length - 1] + let last = #{array}[#{array}.length - 1]; + if (last !== undefined) { - return #{Maybe::Just(`last`)} + return #{Maybe.Just(`last`)} } else { - return #{Maybe::Nothing} + return #{Maybe.Nothing} } })() ` @@ -401,8 +398,8 @@ module Array { Returns the maximum value of an array of numbers. It's a maybe because the array might not have items in it. - Array.max([0, 1, 2, 3, 4]) == Maybe::Just(4) - Array.max([]) == Maybe::Nothing + Array.max([0, 1, 2, 3, 4]) == Maybe.Just(4) + Array.max([]) == Maybe.Nothing */ fun max (array : Array(Number)) : Maybe(Number) { if Array.size(array) > 0 { @@ -416,8 +413,8 @@ module Array { Returns the minimum value of an array of numbers. It's a maybe because the array might not have items in it. - Array.min([0, 1, 2, 3, 4]) == Maybe::Just(0) - Array.min([]) == Maybe::Nothing + Array.min([0, 1, 2, 3, 4]) == Maybe.Just(0) + Array.min([]) == Maybe.Nothing */ fun min (array : Array(Number)) : Maybe(Number) { if Array.size(array) > 0 { @@ -452,7 +449,7 @@ module Array { fun move (array : Array(item), from : Number, to : Number) : Array(item) { ` (() => { - const result = Array.from(#{array}) + const result = [...#{array}] if (#{from} == #{to} || #{from} < 0 || #{from} >= result.length) { return result @@ -562,8 +559,8 @@ module Array { /* Returns a random element from the array. - Array.sample(["a"]) == Maybe::Just("a") - Array.sample() == Maybe::Nothing() + Array.sample(["a"]) == Maybe.Just("a") + Array.sample() == Maybe.Nothing() */ fun sample (array : Array(item)) : Maybe(item) { ` @@ -571,9 +568,9 @@ module Array { if (#{array}.length) { const item = #{array}[Math.floor(Math.random() * #{array}.length)] - return #{Maybe::Just(`item`)} + return #{Maybe.Just(`item`)} } else { - return #{Maybe::Nothing} + return #{Maybe.Nothing} } })() ` @@ -598,7 +595,7 @@ module Array { ` (() => { if (#{index} < 0 || #{index} >= #{array}.length) { return #{array} } - const result = Array.from(#{array}) + const result = [...#{array}] result[#{index}] = #{item} return result })() @@ -712,7 +709,7 @@ module Array { return #{array} } - const result = Array.from(#{array}) + const result = [...#{array}] const saved = result[#{index1}] result[#{index1}] = result[#{index2}] result[#{index2}] = saved; @@ -760,7 +757,7 @@ module Array { fun unshift (array : Array(item), item : item) : Array(item) { ` (() => { - const result = Array.from(#{array}) + const result = [...#{array}] result.unshift(#{item}) return result })() diff --git a/core/source/Base64.mint b/core/source/Base64.mint index f2610b137..f72157608 100644 --- a/core/source/Base64.mint +++ b/core/source/Base64.mint @@ -4,9 +4,9 @@ module Base64 { ` (() => { try { - return #{Result::Ok(`atob(#{value})`)}; + return #{Result.Ok(`atob(#{value})`)}; } catch (error) { - return #{Result::Err(`error.toString()`)}; + return #{Result.Err(`error.toString()`)}; } })() ` diff --git a/core/source/Clipboard.mint b/core/source/Clipboard.mint index f038becf4..659ec28fb 100644 --- a/core/source/Clipboard.mint +++ b/core/source/Clipboard.mint @@ -29,12 +29,17 @@ module Clipboard { // Get selection and replace current selection const selection = window.getSelection() - const lastRanges = Array.from({length: selection.rangeCount}, (_, i) => selection.getRangeAt(i)) + + const lastRanges = + Array.from( + { length: selection.rangeCount }, + (_, i) => selection.getRangeAt(i)) + selection.removeAllRanges() selection.addRange(range) // Select all the text - textarea.setSelectionRange(0, 999999) + textarea.setSelectionRange(0, Number.MAX_SAFE_INTEGER) // Copy to clipboard document.execCommand("copy") diff --git a/core/source/Console.mint b/core/source/Console.mint index 11194cfb8..e8b8fe389 100644 --- a/core/source/Console.mint +++ b/core/source/Console.mint @@ -30,8 +30,7 @@ module Console { */ fun assert (assertion : Bool, value : a, values : Array(b) = []) : Tuple(Bool, a, Array(b)) { `console.assert(#{assertion}, #{value}, ...#{values})` - - #(assertion, value, values) + {assertion, value, values} } /* @@ -53,7 +52,7 @@ module Console { `console.count(#{label})` Console.Counter.increment(label) - #(label, Console.Counter.get(label)) + {label, Console.Counter.get(label)} } /* @@ -66,7 +65,7 @@ module Console { `console.countReset(#{label})` Console.Counter.clear(label) - #(label, Console.Counter.get(label)) + {label, Console.Counter.get(label)} } /* @@ -78,8 +77,7 @@ module Console { */ fun debug (value : a, values : Array(b) = []) : Tuple(a, Array(b)) { `console.debug(#{value}, ...#{values})` - - #(value, values) + {value, values} } /* @@ -89,7 +87,6 @@ module Console { */ fun dir (value : a) : a { `console.dir(#{value})` - value } @@ -101,7 +98,6 @@ module Console { */ fun dirxml (value : a) : a { `console.dirxml(#{value})` - value } @@ -112,8 +108,7 @@ module Console { */ fun error (value : a, values : Array(b) = []) : Tuple(a, Array(b)) { `console.error(#{value}, ...#{values})` - - #(value, values) + {value, values} } /* @@ -123,7 +118,6 @@ module Console { */ fun group (label : String = "Default") : String { `console.group(#{label})` - label } @@ -134,7 +128,6 @@ module Console { */ fun groupCollapsed (label : String = "Default") : String { `console.groupCollapsed(#{label})` - label } @@ -145,7 +138,6 @@ module Console { */ fun groupEnd (label : String = "Default") : String { `console.groupEnd(#{label})` - label } @@ -156,8 +148,7 @@ module Console { */ fun info (value : a, values : Array(b) = []) : Tuple(a, Array(b)) { `console.info(#{value}, ...#{values})` - - #(value, values) + {value, values} } /* @@ -169,8 +160,7 @@ module Console { */ fun log (value : a, values : Array(b) = []) : Tuple(a, Array(b)) { `console.log(#{value}, ...#{values})` - - #(value, values) + {value, values} } /* @@ -219,7 +209,7 @@ module Console { `console.table(#{data})` } - #(data, columns) + {data, columns} } /* @@ -229,7 +219,6 @@ module Console { */ fun time (label : String = "Default") : String { `console.time(#{label})` - label } @@ -240,7 +229,6 @@ module Console { */ fun timeEnd (label : String = "Default") : String { `console.timeEnd(#{label})` - label } @@ -251,8 +239,7 @@ module Console { */ fun timeLog (label : String = "Default", values : Array(a) = []) : Tuple(String, Array(a)) { `console.timeLog(#{label}, ...#{values})` - - #(label, values) + {label, values} } /* @@ -279,8 +266,7 @@ module Console { */ fun trace (value : a, values : Array(b)) : Tuple(a, Array(b)) { `console.trace(#{value}, ...#{values})` - - #(value, values) + {value, values} } /* @@ -290,7 +276,6 @@ module Console { */ fun warn (value : a, values : Array(b) = []) : Tuple(a, Array(b)) { `console.warn(#{value}, ...#{values})` - - #(value, values) + {value, values} } } diff --git a/core/source/Dom.mint b/core/source/Dom.mint index 5bfb7aaab..bad3ffaac 100644 --- a/core/source/Dom.mint +++ b/core/source/Dom.mint @@ -56,14 +56,9 @@ module Dom { |> Dom.getElementById() */ fun focus (maybeElement : Maybe(Dom.Element)) : Promise(Void) { - case maybeElement { - Maybe.Just(element) => - { - focusWhenVisible(element) - Promise.resolve(void) - } - - Maybe.Nothing => Promise.resolve(void) + if let Maybe.Just(element) = maybeElement { + focusWhenVisible(element) + Promise.resolve(void) } } @@ -89,7 +84,7 @@ module Dom { let focus = () => { if (counter > 15) { - resolve(#{Result::Err("Could not focus the element in 150ms. Is it visible?")}) + resolve(#{Result.Err("Could not focus the element in 150ms. Is it visible?")}) } #{element}.focus() @@ -98,7 +93,7 @@ module Dom { counter++ setTimeout(focus, 10) } else { - resolve(#{Result::Ok(void)}) + resolve(#{Result.Ok(void)}) } } @@ -116,9 +111,9 @@ module Dom { ` (() => { if (document.activeElement) { - return #{Maybe::Just(`document.activeElement`)} + return #{Maybe.Just(`document.activeElement`)} } else { - return #{Maybe::Nothing} + return #{Maybe.Nothing} } })() ` @@ -131,8 +126,8 @@ module Dom { Dom.getElementById("my-div") case (outcome) { - Maybe::Just(element) => Dom.getAttribute(element, "id") == "my-div" - Maybe::Nothing => false + Maybe.Just(element) => Dom.getAttribute(element, "id") == "my-div" + Maybe.Nothing => false } */ fun getAttribute (element : Dom.Element, name : String) : Maybe(String) { @@ -141,9 +136,9 @@ module Dom { const value = #{element}.getAttribute(#{name}) if (value === "") { - return #{Maybe::Nothing} + return #{Maybe.Nothing} } else { - return #{Maybe::Just(`value`)} + return #{Maybe.Just(`value`)} } })() ` @@ -155,7 +150,7 @@ module Dom { Dom.getChildren()) */ fun getChildren (element : Dom.Element) : Array(Dom.Element) { - `Array.from(#{element}.children)` + `[...#{element}.children]` } /* @@ -220,9 +215,9 @@ module Dom { let element = document.getElementById(#{id}) if (element) { - return #{Maybe::Just(`element`)} + return #{Maybe.Just(`element`)} } else { - return #{Maybe::Nothing} + return #{Maybe.Nothing} } })() ` @@ -240,12 +235,12 @@ module Dom { let element = document.querySelector(#{selector}) if (element) { - return #{Maybe::Just(`element`)} + return #{Maybe.Just(`element`)} } else { - return #{Maybe::Nothing} + return #{Maybe.Nothing} } } catch (error) { - return #{Maybe::Nothing} + return #{Maybe.Nothing} } })() ` @@ -262,9 +257,9 @@ module Dom { const element = document.elementFromPoint(#{left}, #{top}) if (element) { - return #{Maybe::Just(`element`)} + return #{Maybe.Just(`element`)} } else { - return #{Maybe::Nothing} + return #{Maybe.Nothing} } })() ` @@ -284,16 +279,16 @@ module Dom { fun getFocusableElements (element : Dom.Element) : Array(Dom.Element) { ` (() => { - /* Save focused element. */ + // Save focused element. const focused = document.activeElement - /* Save scroll position. */ + // Save scroll position. const scrollX = window.scrollX const scrollY = window.scrollY - /* Save the scroll position of each element. */ + // Save the scroll position of each element. const scrollPositions = - Array.from(document.querySelectorAll("*")).reduce((memo, element) => { + [...document.querySelectorAll("*")].reduce((memo, element) => { if (element.scrollHeight > 0 || element.scrollWidth > 0) { memo.set(element, [element.scrollLeft, element.scrollTop]) } @@ -301,11 +296,12 @@ module Dom { return memo }, new Map) - /* Gather the focusable elements by focusing them and comparing it - with the focused element. */ + // Gather the focusable elements by focusing them and comparing it + // with the focused element. const foundElements = - Array.from(#{element}.querySelectorAll("*")).reduce((memo ,element) => { + [...#{element}.querySelectorAll("*")].reduce((memo ,element) => { element.focus() + if (document.activeElement == element && element.tabIndex !== -1) { memo.push(element) } @@ -313,7 +309,7 @@ module Dom { return memo }, []) - /* Restore scroll positions and focus. */ + // Restore scroll positions and focus. for (let element in scrollPositions) { const [x, y] = scrollPositions[element] element.scrollTo(x, y) @@ -388,7 +384,7 @@ module Dom { let hash = String.parameterize(text) - #(tag, text, hash) + {tag, text, hash} }) } @@ -477,10 +473,7 @@ module Dom { Dom.scrollTo(element, 10, 10) */ fun scrollTo (element : Dom.Element, left : Number, top : Number) : Promise(Void) { - `#{element}.scrollTo({ - left: #{left}, - top: #{top} - })` + `#{element}.scrollTo({ left: #{left}, top: #{top} })` } /* @@ -521,8 +514,13 @@ module Dom { It is used to set the value of `input` fields programmatically. */ - fun setValue (dom : Dom.Element, value : String) : Dom.Element { - `(#{dom}.value = #{value}) && #{dom}` + fun setValue (element : Dom.Element, value : String) : Dom.Element { + ` + (() => { + #{element}.value = #{value} + return #{element} + })() + ` } /* @@ -531,10 +529,6 @@ module Dom { Dom.smoothScrollTo(element, 10, 10) */ fun smoothScrollTo (element : Dom.Element, left : Number, top : Number) : Promise(Void) { - `#{element}.scrollTo({ - behavior: 'smooth', - left: #{left}, - top: #{top} - })` + `#{element}.scrollTo({ behavior: 'smooth', left: #{left}, top: #{top} })` } } diff --git a/core/source/File.mint b/core/source/File.mint index 337068622..da6e6727a 100644 --- a/core/source/File.mint +++ b/core/source/File.mint @@ -3,7 +3,8 @@ module File { /* Prompts a dialog for the saving the given file. - file = await File.select(*) + let file = + await File.select("*") File.download(file) */ @@ -55,7 +56,7 @@ module File { /* Reads the contents of the given file as a String. - file = + let file = File.create("Some content...", "test.txt", "text/plain") File.readAsArrayBuffer(file) @@ -76,11 +77,11 @@ module File { /* Reads the contents of the given file as a Data URL. - files = + let files = await File.select("text/plain") - url = - File.readAsDataURL(file) + let url = + await File.readAsDataURL(file) url == "data:text/plain;...." */ @@ -100,10 +101,10 @@ module File { /* Reads the contents of the given file as a String. - file = + let file = File.create("Some content...", "test.txt", "text/plain") - url = + let url = await File.readAsString(file) url == "Some content..." @@ -127,7 +128,7 @@ module File { * The mime type can be restricted to the given one. * It might not resolve if the user cancels the dialog. - file = + let file = await File.select("application/json") Debug.log(file) @@ -165,7 +166,7 @@ module File { * The mime type can be restricted to the given one. * It might not resolve if the user cancels the dialog. - files = + let files = await File.selectMultiple("application/json") Debug.log(files) diff --git a/core/source/FileSize.mint b/core/source/FileSize.mint index b9af09faa..566a8fa89 100644 --- a/core/source/FileSize.mint +++ b/core/source/FileSize.mint @@ -8,16 +8,23 @@ module FileSize { FileSize.format(1073741824) == "1 GB" */ fun format (size : Number) : String { - ` - (() => { - if (#{size} == 0){ - return "0 B" - } else { - const index = Math.floor(Math.log(#{size}) / Math.log(1024)); - const affix = ['B', 'kB', 'MB', 'GB', 'TB'][index] - return (#{size} / Math.pow(1024, index)).toFixed(2) * 1 + ' ' + affix; - } - })() - ` + if size == 0 { + "0 B" + } else { + let index = + Math.floor(Math.log(size) / Math.log(1024)) + + let affix = + ["B", "kB", "MB", "GB", "TB"][index] or "" + + // We calculate the number, then convert it to String + // with 2 decimals and then parse it again. + let number = + (size / Math.pow(1024, index) + |> Number.toFixed(2) + |> Number.fromString()) or 0 + + "#{number} #{affix}" + } } } diff --git a/core/source/Html.Event.mint b/core/source/Html.Event.mint index df154af1b..4b4974f6c 100644 --- a/core/source/Html.Event.mint +++ b/core/source/Html.Event.mint @@ -1,46 +1,54 @@ /* Represents an HTML event. */ type Html.Event { - bubbles : Bool, - cancelable : Bool, + event : Html.NativeEvent, + + clipboardData : Html.DataTransfer, + dataTransfer : Html.DataTransfer, + currentTarget : Dom.Element, + target : Dom.Element, + defaultPrevented : Bool, - dataTransfer : Html.DataTransfer, - clipboardData : Html.DataTransfer, - eventPhase : Number, + cancelable : Bool, isTrusted : Bool, - target : Dom.Element, - timeStamp : Number, + bubbles : Bool, + repeat : Bool, + + shiftKey : Bool, + metaKey : Bool, + ctrlKey : Bool, + altKey : Bool, + + animationName : String, + pseudoElement : String, + propertyName : String, + locale : String, type : String, data : String, - altKey : Bool, - charCode : Number, - ctrlKey : Bool, key : String, - keyCode : Number, - locale : String, + + elapsedTime : Number, + eventPhase : Number, + timeStamp : Number, + charCode : Number, location : Number, - metaKey : Bool, - repeat : Bool, - shiftKey : Bool, - which : Number, - button : Number, + keyCode : Number, buttons : Number, + button : Number, + detail : Number, + which : Number, + clientX : Number, clientY : Number, - pageX : Number, - pageY : Number, screenX : Number, screenY : Number, - detail : Number, + pageX : Number, + pageY : Number, + deltaMode : Number, deltaX : Number, deltaY : Number, - deltaZ : Number, - animationName : String, - pseudoElement : String, - propertyName : String, - elapsedTime : Number, - event : Html.NativeEvent + deltaZ : Number } /* Utility functions for `Html.Event` */ @@ -145,53 +153,6 @@ module Html.Event { const Y = 89 const Z = 90 - /* Converts a native event into a Mint one. */ - fun fromEvent (event : Html.NativeEvent) : Html.Event { - { - bubbles: `#{event}.bubbles`, - cancelable: `#{event}.cancelable`, - currentTarget: `#{event}.currentTarget`, - defaultPrevented: `#{event}.defaultPrevented`, - dataTransfer: `#{event}.dataTransfer`, - clipboardData: `#{event}.clipboardData`, - eventPhase: `#{event}.eventPhase`, - isTrusted: `#{event}.isTrusted`, - target: `#{event}.target`, - timeStamp: `#{event}.timeStamp`, - type: `#{event}.type`, - data: `#{event}.data`, - altKey: `#{event}.altKey`, - charCode: `#{event}.charCode`, - ctrlKey: `#{event}.ctrlKey`, - key: `#{event}.key`, - keyCode: `#{event}.keyCode`, - locale: `#{event}.locale`, - location: `#{event}.location`, - metaKey: `#{event}.metaKey`, - repeat: `#{event}.repeat`, - shiftKey: `#{event}.shiftKey`, - which: `#{event}.which`, - button: `#{event}.button`, - buttons: `#{event}.buttons`, - clientX: `#{event}.clientX`, - clientY: `#{event}.clientY`, - pageX: `#{event}.pageX`, - pageY: `#{event}.pageY`, - screenX: `#{event}.screenX`, - screenY: `#{event}.screenY`, - detail: `#{event}.detail`, - deltaMode: `#{event}.deltaMode`, - deltaX: `#{event}.deltaX`, - deltaY: `#{event}.deltaY`, - deltaZ: `#{event}.deltaZ`, - animationName: `#{event}.animationName`, - pseudoElement: `#{event}.pseudoElement`, - propertyName: `#{event}.propertyName`, - elapsedTime: `#{event}.elapsedTime`, - event: event - } - } - /* Returns whether or not the events propagation is stopped or not. diff --git a/core/source/Html.Portals.Body.mint b/core/source/Html.Portals.Body.mint index 0ce197ad6..65399eaa2 100644 --- a/core/source/Html.Portals.Body.mint +++ b/core/source/Html.Portals.Body.mint @@ -5,6 +5,6 @@ component Html.Portals.Body { /* Renders the children into the document's body. */ fun render : Html { - `_createPortal(#{children}, document.body)` + `#{%createPortal%}(#{children}, document.body)` } } diff --git a/core/source/Html.Portals.Head.mint b/core/source/Html.Portals.Head.mint index 478bfdedb..c3e427a05 100644 --- a/core/source/Html.Portals.Head.mint +++ b/core/source/Html.Portals.Head.mint @@ -5,6 +5,6 @@ component Html.Portals.Head { /* Renders the children into the document's head. */ fun render : Html { - `_createPortal(#{children}, document.head)` + `#{%createPortal%}(#{children}, document.head)` } } diff --git a/core/source/Html.mint b/core/source/Html.mint index fce170fb7..5745b12d8 100644 --- a/core/source/Html.mint +++ b/core/source/Html.mint @@ -7,7 +7,7 @@ module Html { Html.empty() } else {
- <{ "It should work" }> + "It should work"
} } diff --git a/spec/compilers/module_access_subscriptions b/spec/compilers/module_access_subscriptions index bb89442eb..58ec66eaa 100644 --- a/spec/compilers/module_access_subscriptions +++ b/spec/compilers/module_access_subscriptions @@ -1,4 +1,4 @@ -record Test { +type Test { test : String } diff --git a/spec/compilers/operation_or b/spec/compilers/operation_or index 0deb365aa..105357e47 100644 --- a/spec/compilers/operation_or +++ b/spec/compilers/operation_or @@ -1,11 +1,11 @@ -enum Maybe(a) { +type Maybe(a) { Nothing Just(a) } component Main { fun test : String { - Maybe::Nothing or "Hello" + Maybe.Nothing or "Hello" } fun render : String { diff --git a/spec/compilers/pipe b/spec/compilers/pipe index 986c157a9..080c08a50 100644 --- a/spec/compilers/pipe +++ b/spec/compilers/pipe @@ -1,8 +1,8 @@ component Main { fun render : Html {+ "It should work" +
+ } +} + +component Main { + fun render : Html { + Html.Testing.renderAll() + } +} +-------------------------------------------------------------------------------- +import { createElement as A } from "./runtime.js"; + +export const + a = () => { + return A(`p`, {}, [`It should work`]) + }, + B = () => { + return a() + }; diff --git a/spec/compilers2/module_access b/spec/compilers2/module_access new file mode 100644 index 000000000..25926594f --- /dev/null +++ b/spec/compilers2/module_access @@ -0,0 +1,30 @@ +module Test { + fun a : String { + "Hello" + } + + fun b : Function(String) { + Test.a + } +} + +component Main { + fun render : String { + let x = + Test.b() + + x() + } +} +-------------------------------------------------------------------------------- +export const + a = () => { + return `Hello` + }, + b = () => { + return a + }, + A = () => { + const c = b(); + return c() + }; diff --git a/spec/compilers2/module_access_get b/spec/compilers2/module_access_get new file mode 100644 index 000000000..43bb133ee --- /dev/null +++ b/spec/compilers2/module_access_get @@ -0,0 +1,26 @@ +store Test { + get a : String { + "Hello" + } + + fun b : String { + a + } +} + +component Main { + fun render : String { + Test.b() + } +} +-------------------------------------------------------------------------------- +export const + a = () => { + return `Hello` + }, + b = () => { + return a() + }, + A = () => { + return b() + }; diff --git a/spec/compilers2/module_access_subscriptions b/spec/compilers2/module_access_subscriptions new file mode 100644 index 000000000..fe14e8097 --- /dev/null +++ b/spec/compilers2/module_access_subscriptions @@ -0,0 +1,51 @@ +type Test { + test : String +} + +provider Test : Test { + fun update : Promise(Void) { + subscriptions + await void + } + + fun print (a : String) : String { + a + } +} + +component Main { + use Test { + test: "" + } + + fun render : String { + Test.subscriptions + Test.print("a") + } +} +-------------------------------------------------------------------------------- +import { + createProvider as B, + subscriptions as C, + useId as E +} from "./runtime.js"; + +export const + a = new Map(), + b = (c) => { + return c + }, + A = B(a, async () => { + C(a); + return await null + }), + D = () => { + const d = E(); + A(d, () => { + return { + test: `` + } + }); + C(a); + return b(`a`) + }; diff --git a/spec/compilers2/module_call b/spec/compilers2/module_call new file mode 100644 index 000000000..e8690f524 --- /dev/null +++ b/spec/compilers2/module_call @@ -0,0 +1,26 @@ +module Test { + fun a (value : String) : String { + value + } + + fun b : String { + Test.a("Lorem ipsum dolor sit amet") + } +} + +component Main { + fun render : String { + Test.b() + } +} +-------------------------------------------------------------------------------- +export const + a = (b) => { + return b + }, + c = () => { + return a(`Lorem ipsum dolor sit amet`) + }, + A = () => { + return c() + }; diff --git a/spec/compilers2/module_call_piped b/spec/compilers2/module_call_piped new file mode 100644 index 000000000..21ba67372 --- /dev/null +++ b/spec/compilers2/module_call_piped @@ -0,0 +1,27 @@ +module Test { + fun a (x : Bool, value : String) : String { + value + } + + fun b : String { + true + |> Test.a("Lorem ipsum dolor sit amet") + } +} + +component Main { + fun render : String { + Test.b() + } +} +-------------------------------------------------------------------------------- +export const + a = (b, c) => { + return c + }, + d = () => { + return a(true, `Lorem ipsum dolor sit amet`) + }, + A = () => { + return d() + }; diff --git a/spec/compilers2/next_call b/spec/compilers2/next_call new file mode 100644 index 000000000..3e8b9774b --- /dev/null +++ b/spec/compilers2/next_call @@ -0,0 +1,37 @@ +component Main { + state name : String = "Joe" + state age : Number = 24 + + fun test : Promise(Void) { + next + { + name: "Hello", + age: 30 + } + } + + fun render : String { + test() + + "" + } +} +-------------------------------------------------------------------------------- +import { + useSignal as B, + batch as C +} from "./runtime.js"; + +export const A = () => { + const + a = B(`Joe`), + b = B(24), + c = () => { + return C(() => { + a.value = `Hello`; + b.value = 30 + }) + }; + c(); + return `` +}; diff --git a/spec/compilers2/next_call_empty b/spec/compilers2/next_call_empty new file mode 100644 index 000000000..dc991181f --- /dev/null +++ b/spec/compilers2/next_call_empty @@ -0,0 +1,11 @@ +component Main { + fun render : String { + next {} + "" + } +} +-------------------------------------------------------------------------------- +export const A = () => { + null; + return `` +}; diff --git a/spec/compilers2/number_literal_negative b/spec/compilers2/number_literal_negative new file mode 100644 index 000000000..a348075c2 --- /dev/null +++ b/spec/compilers2/number_literal_negative @@ -0,0 +1,11 @@ +component Main { + fun render : String { + -42 + "" + } +} +-------------------------------------------------------------------------------- +export const A = () => { + -42; + return `` +}; diff --git a/spec/compilers2/number_literal_simple b/spec/compilers2/number_literal_simple new file mode 100644 index 000000000..efcc15473 --- /dev/null +++ b/spec/compilers2/number_literal_simple @@ -0,0 +1,11 @@ +component Main { + fun render : String { + 10 + "" + } +} +-------------------------------------------------------------------------------- +export const A = () => { + 10; + return `` +}; diff --git a/spec/compilers2/number_literal_with_decimal b/spec/compilers2/number_literal_with_decimal new file mode 100644 index 000000000..70c37398a --- /dev/null +++ b/spec/compilers2/number_literal_with_decimal @@ -0,0 +1,11 @@ +component Main { + fun render : String { + 10.120 + "" + } +} +-------------------------------------------------------------------------------- +export const A = () => { + 10.120; + return `` +}; diff --git a/spec/compilers2/operation_chained b/spec/compilers2/operation_chained new file mode 100644 index 000000000..636b05463 --- /dev/null +++ b/spec/compilers2/operation_chained @@ -0,0 +1,13 @@ +component Main { + fun render : String { + "a" == "b" && true != false + "" + } +} +-------------------------------------------------------------------------------- +import { compare as B } from "./runtime.js"; + +export const A = () => { + B(`a`, `b`) && !B(true, false); + return `` +}; diff --git a/spec/compilers2/operation_or b/spec/compilers2/operation_or new file mode 100644 index 000000000..8bc4b5158 --- /dev/null +++ b/spec/compilers2/operation_or @@ -0,0 +1,29 @@ +type Result(error, value) { + Err(error) + Ok(value) +} + +type Maybe(value) { + Nothing + Just(value) +} + +component Main { + fun render : String { + Maybe.Nothing or "Hello" + } +} +-------------------------------------------------------------------------------- +import { + variant as B, + or as G +} from "./runtime.js"; + +export const + A = B(0), + C = B(1), + D = B(1), + E = B(1), + F = () => { + return G(A, D, new A(), `Hello`) + }; diff --git a/spec/compilers2/operation_simple b/spec/compilers2/operation_simple new file mode 100644 index 000000000..c779827db --- /dev/null +++ b/spec/compilers2/operation_simple @@ -0,0 +1,13 @@ +component Main { + fun render : String { + "a" == "b" + "" + } +} +-------------------------------------------------------------------------------- +import { compare as B } from "./runtime.js"; + +export const A = () => { + B(`a`, `b`); + return `` +}; diff --git a/spec/compilers2/parenthesized_expression b/spec/compilers2/parenthesized_expression new file mode 100644 index 000000000..b9352f3c8 --- /dev/null +++ b/spec/compilers2/parenthesized_expression @@ -0,0 +1,11 @@ +component Main { + fun render : String { + (true) + "" + } +} +-------------------------------------------------------------------------------- +export const A = () => { + (true); + return `` +}; diff --git a/spec/compilers2/pipe b/spec/compilers2/pipe new file mode 100644 index 000000000..f5887061f --- /dev/null +++ b/spec/compilers2/pipe @@ -0,0 +1,21 @@ +component Main { + fun render : Html { ++ hi + | +