-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #260 from vim-denops/eval
👍 add `eval` module and mark deprecated `helper/expr_string`
- Loading branch information
Showing
17 changed files
with
2,601 additions
and
186 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/** | ||
* This module provides utilities for creating Vim expressions in TypeScript. | ||
* | ||
* @module | ||
*/ | ||
|
||
import type { Predicate } from "@core/unknownutil/type"; | ||
import { isIntersectionOf } from "@core/unknownutil/is/intersection-of"; | ||
import { isLiteralOf } from "@core/unknownutil/is/literal-of"; | ||
import { isObjectOf } from "@core/unknownutil/is/object-of"; | ||
import { | ||
isVimEvaluatable, | ||
type VimEvaluatable, | ||
vimExpressionOf, | ||
} from "./vim_evaluatable.ts"; | ||
import { stringify } from "./stringify.ts"; | ||
|
||
/** | ||
* An object that defines a Vim's expression. | ||
* | ||
* Note that although it inherits from primitive `string` for convenience, it | ||
* actually inherits from the `String` wrapper object. | ||
* | ||
* ```typescript | ||
* import { assertEquals } from "jsr:@std/assert/equals"; | ||
* import { expr } from "jsr:@denops/std/eval/expression"; | ||
* | ||
* const s: string = expr`foo`; | ||
* assertEquals(typeof s, "object"); // is not "string" | ||
* ``` | ||
*/ | ||
export type Expression = string & ExpressionProps; | ||
|
||
interface ExpressionProps extends VimEvaluatable { | ||
/** | ||
* Used by the `JSON.stringify` to enable the transformation of an object's data to JSON serialization. | ||
*/ | ||
toJSON(): string; | ||
|
||
readonly [Symbol.toStringTag]: "Expression"; | ||
} | ||
|
||
/** | ||
* Create a {@linkcode Expression} that evaluates as a Vim expression. | ||
* | ||
* `raw_vim_expression` is a string text representing a raw Vim expression. | ||
* Backslashes are treated as Vim syntax rather than JavaScript string escape | ||
* sequences. Note that the raw Vim expression has not been verified and is | ||
* therefore **UNSAFE**. | ||
* | ||
* `${value}` is the expression to be inserted at the current position, whose | ||
* value is converted to the corresponding Vim expression. The conversion is | ||
* safe and an error is thrown if it cannot be converted. Note that if the | ||
* value contains `Expression` it will be inserted as-is, this is **UNSAFE**. | ||
* | ||
* ```typescript | ||
* import { assertEquals } from "jsr:@std/assert/equals"; | ||
* import { expr } from "jsr:@denops/std/eval/expression"; | ||
* | ||
* assertEquals( | ||
* expr`raw_vim_expression`.toString(), | ||
* "raw_vim_expression", | ||
* ); | ||
* | ||
* const value = ["foo", 123, null]; | ||
* assertEquals( | ||
* expr`raw_vim_expression + ${value}`.toString(), | ||
* "raw_vim_expression + ['foo',123,v:null]" | ||
* ); | ||
* ``` | ||
*/ | ||
export function expr( | ||
template: TemplateStringsArray, | ||
...substitutions: TemplateSubstitutions | ||
): Expression { | ||
const values = substitutions.map(stringify); | ||
const raw = String.raw(template, ...values); | ||
return new ExpressionImpl(raw) as unknown as Expression; | ||
} | ||
|
||
/** | ||
* Returns `true` if the value is a {@linkcode Expression}. | ||
* | ||
* ```typescript | ||
* import { assert, assertFalse } from "jsr:@std/assert"; | ||
* import { isExpression, expr } from "jsr:@denops/std/eval/expression"; | ||
* | ||
* assert(isExpression(expr`123`)); | ||
* | ||
* assertFalse(isExpression("456")); | ||
* ``` | ||
*/ | ||
export const isExpression: Predicate<Expression> = isIntersectionOf([ | ||
// NOTE: Do not check `isString` here, because `Expression` has a different type in definition (primitive `string`) and implementation (`String`). | ||
isObjectOf({ | ||
[Symbol.toStringTag]: isLiteralOf("Expression"), | ||
}), | ||
isVimEvaluatable, | ||
]) as unknown as Predicate<Expression>; | ||
|
||
// deno-lint-ignore no-explicit-any | ||
type TemplateSubstitutions = any[]; | ||
|
||
class ExpressionImpl extends String implements ExpressionProps { | ||
get [Symbol.toStringTag]() { | ||
return "Expression" as const; | ||
} | ||
|
||
[vimExpressionOf](): string { | ||
return this.valueOf(); | ||
} | ||
|
||
toJSON(): string { | ||
return this[vimExpressionOf](); | ||
} | ||
|
||
[Symbol.for("Deno.customInspect")]() { | ||
return `[${this[Symbol.toStringTag]}: ${Deno.inspect(this.valueOf())}]`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { assertEquals } from "@std/assert"; | ||
import { test } from "@denops/test"; | ||
import { vimExpressionOf } from "./vim_evaluatable.ts"; | ||
import { rawString } from "./string.ts"; | ||
|
||
import { expr, isExpression } from "./expression.ts"; | ||
|
||
Deno.test("expr()", async (t) => { | ||
await t.step("returns an `Expression`", () => { | ||
const actual = expr`foo`; | ||
assertEquals(actual[Symbol.toStringTag], "Expression"); | ||
}); | ||
}); | ||
|
||
test({ | ||
mode: "all", | ||
name: "Expression", | ||
fn: async (denops, t) => { | ||
const expression = expr`[42 + ${123}, ${"foo"}, ${null}]`; | ||
await t.step(".@@vimExpressionOf() returns a string", async (t) => { | ||
const actual = expression[vimExpressionOf](); | ||
assertEquals(typeof actual, "string"); | ||
|
||
await t.step("that evaluates as a Vim expression", async () => { | ||
assertEquals(await denops.eval(actual), [165, "foo", null]); | ||
}); | ||
}); | ||
await t.step(".toJSON() returns same as @@vimExpressionOf()", () => { | ||
const actual = expression.toJSON(); | ||
const expected = expression[vimExpressionOf](); | ||
assertEquals(actual, expected); | ||
}); | ||
await t.step(".toString() returns same as @@vimExpressionOf()", () => { | ||
const actual = expression.toString(); | ||
const expected = expression[vimExpressionOf](); | ||
assertEquals(actual, expected); | ||
}); | ||
await t.step('is inspected as `[Expression: "..."]`', () => { | ||
assertEquals( | ||
Deno.inspect(expression), | ||
`[Expression: "[42 + 123, 'foo', v:null]"]`, | ||
); | ||
}); | ||
}, | ||
}); | ||
|
||
Deno.test("isExpression()", async (t) => { | ||
await t.step("returns true if the value is Expression", () => { | ||
const actual = isExpression(expr`foo`); | ||
assertEquals(actual, true); | ||
}); | ||
await t.step("returns false if the value is", async (t) => { | ||
const tests: readonly [name: string, value: unknown][] = [ | ||
["string", "foo"], | ||
["number", 123], | ||
["undefined", undefined], | ||
["null", null], | ||
["true", true], | ||
["false", false], | ||
["Function", () => 0], | ||
["symbol", Symbol("bar")], | ||
["Array", [0, 1]], | ||
["Object", { key: "a" }], | ||
["RawString", rawString`baz`], | ||
]; | ||
for (const [name, value] of tests) { | ||
await t.step(name, () => { | ||
const actual = isExpression(value); | ||
assertEquals(actual, false); | ||
}); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/** | ||
* A module to provide values that can be evaluated in Vim. | ||
* | ||
* ```typescript | ||
* import type { Denops } from "jsr:@denops/std"; | ||
* import * as fn from "jsr:@denops/std/function"; | ||
* import type { Expression, RawString } from "jsr:@denops/std/eval"; | ||
* import { expr, rawString, stringify, useEval } from "jsr:@denops/std/eval"; | ||
* | ||
* export async function main(denops: Denops): Promise<void> { | ||
* // Create `Expression` with `expr`. | ||
* const vimExpression: Expression = expr`expand('<cword>')`; | ||
* | ||
* // Create `RawString` with `rawString`. | ||
* const vimKeySequence: RawString = rawString`\<Cmd>echo 'foo'\<CR>`; | ||
* | ||
* // Use values in `useEval` block. | ||
* await useEval(denops, async (denops) => { | ||
* await denops.cmd('echo expr', { expr: vimExpression }); | ||
* await fn.feedkeys(denops, vimKeySequence); | ||
* }); | ||
* | ||
* // Convert values to a string that can be parsed Vim's `eval()`. | ||
* const vimEvaluable: string = stringify({ | ||
* expr: vimExpression, | ||
* keys: vimKeySequence, | ||
* }); | ||
* await denops.cmd('echo eval(value)', { value: vimEvaluable }); | ||
* } | ||
* ``` | ||
* | ||
* @module | ||
*/ | ||
|
||
export * from "./expression.ts"; | ||
export * from "./string.ts"; | ||
export * from "./stringify.ts"; | ||
export * from "./use_eval.ts"; |
Oops, something went wrong.