Skip to content

Commit

Permalink
feat: string defaults, chained index access, format subscope (#1024)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssalbdivad committed Jun 23, 2024
1 parent 1aa6064 commit 5284b60
Show file tree
Hide file tree
Showing 104 changed files with 2,603 additions and 1,047 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-ears-heal2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@arktype/schema": patch
---

### Add support for index access, less indeterminate morph union errors (see full release notes in [ArkType's CHANGELOG](../type/CHANGELOG.md))
12 changes: 12 additions & 0 deletions .changeset/dirty-ears-heal3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@arktype/attest": patch
---

### Add .satisfies as an attest assertion to compare the value to an ArkType definition.

```ts
attest({ foo: "bar" }).satisfies({ foo: "string" })

// Error: foo must be a number (was string)
attest({ foo: "bar" }).satisfies({ foo: "number" })
```
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"typescript"
],
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
"typescript.tsdk": "./node_modules/typescript/lib",
// IF YOU UPDATE THE MOCHA CONFIG HERE, PLEASE ALSO UPDATE package.json/mocha AND ark/repo/mocha.jsonc
"mochaExplorer.nodeArgv": ["--import=tsx"],
// ignore attest since it requires type information
Expand Down
2 changes: 1 addition & 1 deletion ark/attest/__tests__/assertions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as assert from "node:assert/strict"

const o = { ark: "type" }

specify("type assertions", () => {
describe("type assertions", () => {
it("type parameter", () => {
attest<{ ark: string }>(o)
assert.throws(
Expand Down
2 changes: 1 addition & 1 deletion ark/attest/__tests__/instantiations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ contextualize(() => {
.or({
kind: "'pleb'"
})
attest.instantiations([7574, "instantiations"])
// attest.instantiations([7574, "instantiations"])
})
it("fails on instantiations above threshold", () => {
attest(() => {
Expand Down
12 changes: 12 additions & 0 deletions ark/attest/__tests__/satisfies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { attest } from "../assert/attest.js"
import { contextualize } from "../utils.js"

contextualize(() => {
it("can assert types", () => {
attest({ foo: "bar" }).satisfies({ foo: "string" })

attest(() => {
attest({ foo: "bar" }).satisfies({ foo: "number" })
}).throws("foo must be a number (was string)")
})
})
2 changes: 1 addition & 1 deletion ark/attest/__tests__/snap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ contextualize(() => {
})

it("snap", () => {
attest(o).snap({ re: `do` })
attest<{ re: string }>(o).snap({ re: `do` })
attest(o).equals({ re: "do" }).type.toString.snap(`{ re: string; }`)
assert.throws(
() => attest(o).snap({ re: `dorf` }),
Expand Down
8 changes: 1 addition & 7 deletions ark/attest/assert/attest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { caller, getCallStack, type SourcePosition } from "@arktype/fs"
import type { inferTypeRoot, validateTypeRoot } from "arktype"
import { getBenchCtx } from "../bench/bench.js"
import type { Measure } from "../bench/measure.js"
import { instantiationDataHandler } from "../bench/type.js"
Expand All @@ -20,11 +19,6 @@ export type AttestFn = {
...args: [actual] extends [never] ? [value: expected] : []
): [expected] extends [never] ? rootAssertions<unknown, AssertionKind>
: rootAssertions<expected, AssertionKind>
<actual, def>(
actual: actual,
def: validateTypeRoot<def>
): asserts actual is unknown extends actual ? inferTypeRoot<def> & actual
: Extract<actual, inferTypeRoot<def>>

instantiations: (count?: Measure<"instantiations"> | undefined) => void
}
Expand Down Expand Up @@ -83,4 +77,4 @@ export const attest: AttestFn = Object.assign(attestInternal, {
ctx.lastSnapCallPosition = calledFrom
instantiationDataHandler({ ...ctx, kind: "instantiations" }, args, false)
}
})
}) as never
8 changes: 8 additions & 0 deletions ark/attest/assert/chainableAssertions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { caller } from "@arktype/fs"
import { printable, snapshot, type Constructor } from "@arktype/util"
import { type, type validateTypeRoot } from "arktype"
import * as assert from "node:assert/strict"
import { isDeepStrictEqual } from "node:util"
import {
Expand Down Expand Up @@ -71,6 +72,11 @@ export class ChainableAssertions implements AssertionRecord {
return this
}

satisfies(def: unknown): this {
type(def as never).assert(def)
return this
}

instanceOf(expected: Constructor): this {
if (!(this.ctx.actual instanceof expected)) {
throwAssertionError({
Expand Down Expand Up @@ -218,6 +224,7 @@ export class ChainableAssertions implements AssertionRecord {
}
}
}

const checkCompletionsForErrors = (completions?: Completions) => {
if (typeof completions === "string") throw new Error(completions)
}
Expand Down Expand Up @@ -278,6 +285,7 @@ export type comparableValueAssertion<expected, kind extends AssertionKind> = {
instanceOf: (constructor: Constructor) => nextAssertions<kind>
is: (value: expected) => nextAssertions<kind>
completions: (value?: Completions) => void
satisfies: <def>(def: validateTypeRoot<def>) => nextAssertions<kind>
// This can be used to assert values without type constraints
unknown: Omit<comparableValueAssertion<unknown, kind>, "unknown">
}
Expand Down
2 changes: 1 addition & 1 deletion ark/attest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@typescript/analyze-trace": "0.10.1"
},
"devDependencies": {
"typescript": "5.4.5"
"typescript": "5.5.2"
},
"peerDependencies": {
"typescript": "*"
Expand Down
22 changes: 13 additions & 9 deletions ark/attest/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,18 @@ export type ContextualizeBlock = {
}

export const contextualize: ContextualizeBlock = (...args: any[]) => {
if (globalThis.describe as unknown) {
const fileName = basename(caller().file)
if (typeof args[0] === "function") globalThis.describe(fileName, args[0])
else {
globalThis.describe(fileName, () => {
for (let i = 0; i < args.length; i = i + 2)
globalThis.describe(args[i], args[i + 1])
})
}
const describe = globalThis.describe
if (!describe) {
throw new Error(
`contextualize cannot be used without a global 'describe' function.`
)
}

const fileName = basename(caller().file)
if (typeof args[0] === "function") describe(fileName, args[0])
else {
describe(fileName, () => {
for (let i = 0; i < args.length; i = i + 2) describe(args[i], args[i + 1])
})
}
}
14 changes: 12 additions & 2 deletions ark/dark/arktype.scratch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,18 @@ or("foo|bar")
// [optional(s)]: "number"
// })

const lOrR = types.l.or(types.r)

// THIS SHOULD NOT BE HIGHLIGHTED
// attest(t.internal.indexableExpressions).snap()

const ff = type("string").or("foobar|baz")

const types = scope({ notASpace: { a: type("string") } }).export()
attest<Type<{ a: string }, Ark>>(types.notASpace)

test("type definition", () => {
const types = scope({ a: type("string") }).export()
const types = scope({ a: type("string | number") }).export()
attest<string>(types.a.infer)
attest(() =>
// @ts-expect-error
Expand All @@ -77,6 +82,10 @@ scope({
]
})

$.type({
foo: "string[]"
})

{
const type = (arg?: any) => {}
type({
Expand Down Expand Up @@ -130,5 +139,6 @@ class F {
const highlighted = type({
literals: "'foo' | 'bar' | true",
expressions: "boolean[] | 5 < number <= 10 | number % 2",
pattern: "/^(?:4[0-9]{12}(?:[0-9]{3,6}))$/"
pattern: "/^(?:4[0-9]{12}(?:[0-9]{3,6}))$/",
bar: "(string | number)[]"
})
36 changes: 34 additions & 2 deletions ark/dark/injected.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,49 @@
"patterns": [
{
"include": "#arkDefinition"
},
{
"include": "#arkChained"
}
],
"repository": {
"arkDefinition": {
"contentName": "meta.embedded.arktype.definition",
"begin": "(([^\\)\\(\\s]+)?(?:type|scope|define|match|fn|\\.morph|\\.and|\\.or|\\.when|\\.extends|\\.intersect))(\\()",
"begin": "([^\\)\\(\\s]+)?(\\.)?(type|scope|define|match|fn)(\\()",
"beginCaptures": {
"1": {
"2": {
"name": "punctuation.accessor.ts"
},
"3": {
"name": "entity.name.function.ts"
},
"4": {
"name": "meta.brace.round.ts"
}
},
"end": "\\)",
"endCaptures": {
"0": {
"name": "meta.brace.round.ts"
}
},
"patterns": [
{
"include": "#arkAll"
}
]
},
"arkChained": {
"contentName": "meta.embedded.arktype.definition",
"begin": "([^\\)\\(\\s]+)?(\\.)(and|or|when|extends|intersect|exclude|extract|overlaps|subsumes)(\\()",
"beginCaptures": {
"2": {
"name": "punctuation.accessor.ts"
},
"3": {
"name": "entity.name.function.ts"
},
"4": {
"name": "meta.brace.round.ts"
}
},
Expand Down
2 changes: 1 addition & 1 deletion ark/dark/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "arkdark",
"displayName": "ArkDark",
"description": "Syntax highlighting, inline errors and theme for ArkType⛵",
"version": "5.3.0",
"version": "5.4.2",
"publisher": "arktypeio",
"type": "module",
"scripts": {
Expand Down
33 changes: 31 additions & 2 deletions ark/dark/tsWithArkType.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,41 @@
"repository": {
"arkDefinition": {
"contentName": "meta.embedded.arktype.definition",
"begin": "(([^\\)\\(\\s]+)?(?:type|scope|define|match|fn|\\.morph|\\.and|\\.or|\\.when|\\.extends|\\.intersect))(\\()",
"begin": "([^\\)\\(\\s]+)?(\\.)?(type|scope|define|match|fn)(\\()",
"beginCaptures": {
"1": {
"2": {
"name": "punctuation.accessor.ts"
},
"3": {
"name": "entity.name.function.ts"
},
"4": {
"name": "meta.brace.round.ts"
}
},
"end": "\\)",
"endCaptures": {
"0": {
"name": "meta.brace.round.ts"
}
},
"patterns": [
{
"include": "#arkAll"
}
]
},
"arkChained": {
"contentName": "meta.embedded.arktype.definition",
"begin": "([^\\)\\(\\s]+)?(\\.)(and|or|when|extends|intersect|exclude|extract|overlaps|subsumes)(\\()",
"beginCaptures": {
"2": {
"name": "punctuation.accessor.ts"
},
"3": {
"name": "entity.name.function.ts"
},
"4": {
"name": "meta.brace.round.ts"
}
},
Expand Down
6 changes: 6 additions & 0 deletions ark/docs/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@
background-color: var(--sl-color-gray-6);
}

@media (prefers-reduced-motion: no-preference) {
* {
scroll-behavior: smooth;
}
}

pre.astro-code,
pre.shiki,
.twoslash-popup-container,
Expand Down
30 changes: 2 additions & 28 deletions ark/repo/scratch.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
import { ArkErrors, type } from "arktype"

interface RuntimeErrors extends ArkErrors {
/**
```
email must be a palindrome (was "david@arktype.io")
score (133.7) must be...
• an integer
• less than 100
```*/
summary: string
}

const narrowMessage = (e: ArkErrors): e is RuntimeErrors => true

// ---cut---
const palindromicEmail = type("email").narrow((address, ctx) => {
if (address === [...address].reverse().join("")) {
// congratulations! your email is somehow a palindrome
return true
}
// add a customizable error and return false
return ctx.mustBe("a palindrome")
})
import { type } from "../type/index.js"

const palindromicContact = type({
email: palindromicEmail,
email: "email",
score: "integer < 100"
})

Expand All @@ -34,9 +11,6 @@ const out = palindromicContact({
})

if (out instanceof type.errors) {
// ---cut-start---
if (!narrowMessage(out)) throw new Error()
// ---cut-end---
console.error(out.summary)
} else {
console.log(out.email)
Expand Down
26 changes: 26 additions & 0 deletions ark/repo/testV8.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { type } from "arktype"
import { fromHere } from "@arktype/fs"

console.log(
"⏱️ Checking for V8 fast properties (https://v8.dev/blog/fast-properties) on Type...\n"
)

const t = type({
a: "string",
b: "number"
})

let hasFastProperties

try {
hasFastProperties = eval("%HasFastProperties(t)")
} catch {
throw new Error(`This test must be run in a V8-based runtime with the --allow-natives-syntax flag, e.g.:
node --allow-natives-syntax ${fromHere()}`)
}

if (!hasFastProperties) {
throw new Error("⚠️ Type instance has been deoptimized.")
}

console.log("🏎️ Type instance has fast properties!")
Loading

0 comments on commit 5284b60

Please sign in to comment.