From 26bd1e55671338eee6597e3bcbd0fc749ee93880 Mon Sep 17 00:00:00 2001 From: ExE Boss <3889017+ExE-Boss@users.noreply.github.com> Date: Wed, 27 Nov 2019 01:03:48 +0100 Subject: [PATCH] =?UTF-8?q?feat(ejs):=20Update=C2=A0to=C2=A0v2.7=20(#40531?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ejs): Update to v2.7 * test(ejs): Fix tests on TS 2.8 --- types/ejs/index.d.ts | 490 +++++++++++++++--- .../{ejs-tests.ts => test/ejs.cjs.test.ts} | 31 +- types/ejs/test/ejs.umd.test.ts | 36 ++ types/ejs/tsconfig.json | 5 +- 4 files changed, 461 insertions(+), 101 deletions(-) rename types/ejs/{ejs-tests.ts => test/ejs.cjs.test.ts} (79%) create mode 100644 types/ejs/test/ejs.umd.test.ts diff --git a/types/ejs/index.d.ts b/types/ejs/index.d.ts index a82e634367..7d5e73ad8f 100644 --- a/types/ejs/index.d.ts +++ b/types/ejs/index.d.ts @@ -1,37 +1,36 @@ -// Type definitions for ejs.js 2.6 +// Type definitions for ejs 2.7 // Project: http://ejs.co/, https://github.com/mde/ejs // Definitions by: Ben Liddicott +// ExE Boss // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.4 -export interface Data { - [name: string]: any; -} +export as namespace ejs; + /** - * EJS template function cache. This can be a LRU object from lru-cache NPM - * module. By default, it is {@link module:utils.cache}, a simple in-process - * cache that grows continuously. + * Name for detection of EJS. */ -export let cache: Cache; -/** - * Name of the object containing the locals. - * - * This variable is overridden by {@link Options}`.localsName` if it is not - * `undefined`. - */ -export let localsName: string; +export const name: 'ejs'; + /** * Get the path to the included file from the parent file path and the * specified path. + * + * @param name specified path + * @param filename parent file path + * @param isDir whether the parent file path is a directory */ -export function resolveInclude(name: string, filename: string, isDir: boolean): string; +export function resolveInclude(name: string, filename: string, isDir?: boolean): string; + /** * Compile the given `str` of ejs into a template function. */ -export function compile(template: string, opts?: Options & { async: false }): TemplateFunction; -export function compile(template: string, opts: Options & { async: true }): AsyncTemplateFunction; -export function compile(template: string, opts: Options & { async?: never }): TemplateFunction; +export function compile(template: string, opts: Options & { async: true; client?: false }): AsyncTemplateFunction; +export function compile(template: string, opts: Options & { async: true; client: true }): AsyncClientFunction; +export function compile(template: string, opts?: Options & { async?: false; client?: false }): TemplateFunction; +export function compile(template: string, opts?: Options & { async?: false; client: true }): ClientFunction; export function compile(template: string, opts?: Options): TemplateFunction | AsyncTemplateFunction; + /** * Render the given `template` of ejs. * @@ -43,7 +42,13 @@ export function render(template: string, data: Data | undefined, opts: Options & export function render(template: string, data: Data | undefined, opts: Options & { async?: never }): string; export function render(template: string, data?: Data, opts?: Options): string | Promise; -export type RenderFileCallback = (err: Error, str?: string) => T; +/** + * Callback for receiving data from `renderFile`. + * + * @param err error, if any resulted from the rendering process + * @param str output string, is `undefined` if there is an error + */ +export type RenderFileCallback = (err: Error | null, str: string) => T; /** * Render an EJS file at the given `path` and callback `cb(err, str)`. @@ -54,82 +59,401 @@ export type RenderFileCallback = (err: Error, str?: string) => T; export function renderFile(path: string, cb: RenderFileCallback): T; export function renderFile(path: string, data: Data, cb: RenderFileCallback): T; export function renderFile(path: string, data: Data, opts: Options, cb: RenderFileCallback): T; -// tslint:disable-next-line no-unnecessary-generics -export function renderFile(path: string, data?: Data, opts?: Options): Promise; +export function renderFile(path: string, data?: Data, opts?: Options): Promise; /** * Clear intermediate JavaScript cache. Calls {@link Cache#reset}. */ export function clearCache(): void; -export type TemplateFunction = (data?: Data) => string; -export type AsyncTemplateFunction = (data?: Data) => Promise; -export interface Options { - /** Compiled functions are cached, requires `filename` */ - cache?: boolean; - /** - * The name of the file being rendered. Not required if you are using `renderFile()`. - * Used by `cache` to key caches, and for includes. - */ - filename?: string; - /** Set project root for includes with an absolute path (/file.ejs). */ - root?: string; - /** Function execution context */ - context?: any; - /** When `false` no debug instrumentation is compiled */ - compileDebug?: boolean; - /** When `true`, compiles a function that can be rendered in the browser without needing to load the EJS Runtime (ejs.min.js). */ - client?: boolean; - /** Character to use with angle brackets for open/close */ - delimiter?: string; - /** Output generated function body */ - debug?: boolean; - /** When set to `true`, generated function is in strict mode */ - strict?: boolean; - /** - * Whether or not to use `with() {}` constructs. - * If `false` then the locals will be stored in the `locals` object. Set to `false` in strict mode. - */ - _with?: boolean; - /** Name to use for the object storing local variables when not using `with` Defaults to `locals` */ - localsName?: string; - /** - * Remove all safe-to-remove whitespace, including leading and trailing whitespace. - * It also enables a safer version of `-%>` line slurping for all scriptlet tags (it does not strip new lines of tags in the middle of a line). - */ - rmWhitespace?: boolean; - /** - * The escaping function used with `<%=` construct. - * It is used in rendering and is `.toString()`ed in the generation of client functions. - * (By default escapes XML). - */ - escape?(str: string): string; - /** Whether or not to use a Promise */ - async?: boolean; -} - -export function escapeRegexChars(s: string): string; /** - * Escape characters reserved in XML. - * - * This is simply an export of {@link module:utils.escapeXML}. - * - * If `markup` is `undefined` or `null`, the empty string is returned. + * EJS template function cache. This can be a LRU object from lru-cache + * NPM module. By default, it is `utils.cache`, a simple in-process + * cache that grows continuously. */ -export function escapeXML(markup: string): string; -export interface Cache { - set(key: string, val: TemplateFunction): void; - get(key: string): TemplateFunction | undefined; -} -export let delimiter: string; +export let cache: Cache; /** * Custom file loader. Useful for template preprocessing or restricting access * to a certain part of the filesystem. + * + * @param path the path of the file to be read + * @return the contents of the file as a string or object that implements the toString() method + * + * @default fs.readFileSync */ -export function fileLoader(filePath: string): string; +export type fileLoader = (path: string) => string | { toString(): string }; +export let fileLoader: fileLoader; /** - * Name for detection of EJS. + * Name of the object containing the locals. + * + * This variable is overridden by {@link Options}`.localsName` if it is not + * `undefined`. + * + * @default 'locals' */ -export const name = "ejs"; +export let localsName: string; + +/** + * The opening delimiter for all statements. This allows you to clearly delinate + * the difference between template code and existing delimiters. (It is recommended + * to synchronize this with the closeDelimiter property.) + * + * @default '<' + */ +export let openDelimiter: string; + +/** + * The closing delimiter for all statements. This allows to to clearly delinate + * the difference between template code and existing delimiters. (It is recommended + * to synchronize this with the openDelimiter property.) + * + * @default '>' + */ +export let closeDelimiter: string; + +/** + * The delimiter used in template compilation. + * + * @default '%' + */ +export let delimiter: string; + +/** + * Promise implementation -- defaults to the native implementation if available + * This is mostly just for testability + * + * @default Promise + */ +export let promiseImpl: PromiseConstructorLike | undefined; + +/** + * Escape characters reserved in XML. + * + * This is simply an export of `utils.escapeXML`. + * + * If `markup` is `undefined` or `null`, the empty string is returned. + */ +export function escapeXML(markup?: any): string; + +export class Template { + /** + * The EJS template source text. + */ + readonly templateText: string; + + /** + * The compiled JavaScript function source, or the empty string + * if the template hasn't been compiled yet. + */ + readonly source: string; + + /** + * The compiled dependencies of this template. + * + * Always empty if legacy include directives are disabled. + */ + readonly dependencies: ReadonlyArray; + + constructor(text: string, opts?: Options); + + /** + * Compiles the EJS template. + */ + compile(): TemplateFunction | AsyncTemplateFunction | ClientFunction | AsyncClientFunction; +} + +export namespace Template { + enum modes { + EVAL = 'eval', + ESCAPED = 'escaped', + RAW = 'raw', + COMMENT = 'comment', + LITERAL = 'literal' + } +} + +export interface Data { + [name: string]: any; +} + +/** + * This type of function is returned from `compile`, when + * `Options.client` is false. + * + * @param data an object of data to be passed into the template. + * @return Return type depends on `Options.async`. + */ +export type TemplateFunction = (data?: Data) => string; + +/** + * This type of function is returned from `compile`, when + * `Options.client` is false. + * + * @param data an object of data to be passed into the template. + * @return Return type depends on `Options.async`. + */ +export type AsyncTemplateFunction = (data?: Data) => Promise; + +/** + * This type of function is returned from `compile`, when + * `Options.client` is true. + * + * + * This is also used internally to generate a `TemplateFunction`. + * + * @param locals an object of data to be passed into the template. + * The name of this variable is adjustable through `localsName`. + * + * @param escape callback used to escape variables + * @param include callback used to include files at runtime with `include()` + * @param rethrow callback used to handle and rethrow errors + * + * @return Return type depends on `Options.async`. + */ +export type ClientFunction = ( + locals?: Data, + escape?: EscapeCallback, + include?: IncludeCallback, + rethrow?: RethrowCallback, +) => string; + +/** + * This type of function is returned from `compile`, when + * `Options.client` is true. + * + * + * This is also used internally to generate a `TemplateFunction`. + * + * @param locals an object of data to be passed into the template. + * The name of this variable is adjustable through `localsName`. + * + * @param escape callback used to escape variables + * @param include callback used to include files at runtime with `include()` + * @param rethrow callback used to handle and rethrow errors + * + * @return Return type depends on `Options.async`. + */ +export type AsyncClientFunction = ( + locals?: Data, + escape?: EscapeCallback, + include?: IncludeCallback, + rethrow?: RethrowCallback, +) => Promise; + +/** + * Escapes a string using HTML/XML escaping rules. + * + * Returns the empty string for `null` or `undefined`. + * + * @param markup Input string + * @return Escaped string + */ +export type EscapeCallback = (markup?: any) => string; + +/** + * This type of callback is used when `Options.compileDebug` + * is `true`, and an error in the template is thrown. + * + * By default it is used to rethrow an error in a better-formatted way. + * + * @param err Error object + * @param str full EJS source + * @param filename file name of the EJS source + * @param lineno line number of the error + */ +export type RethrowCallback = ( + err: Error, + str: string, + filename: string | null | undefined, + lineno: number, + esc: EscapeCallback, +) => never; + +/** + * The callback called by `ClientFunction` to include files at runtime with `include()` + * + * @param path Path to be included + * @param data Data passed to the template + * @return Contents of the file requested + */ +export type IncludeCallback = (path: string, data?: Data) => string; + +export interface Options { + /** + * Log the generated JavaScript source for the EJS template to the console. + * + * @default false + */ + debug?: boolean; + + /** + * Include additional runtime debugging information in generated template + * functions. + * + * @default true + */ + compileDebug?: boolean; + + /** + * Whether or not to use `with () {}` construct in the generated template + * functions. If set to `false`, data is still accessible through the object + * whose name is specified by `ejs.localsName` (defaults to `locals`). + * + * @default true + */ + _with?: boolean; + + /** + * Whether to run in strict mode or not. + * Enforces `_with=false`. + * + * @default false + */ + strict?: boolean; + + /** + * An array of local variables that are always destructured from `localsName`, + * available even in strict mode. + * + * @default [] + */ + destructuredLocals?: string[]; + + /** + * Remove all safe-to-remove whitespace, including leading and trailing + * whitespace. It also enables a safer version of `-%>` line slurping for all + * scriptlet tags (it does not strip new lines of tags in the middle of a + * line). + * + * @default false + */ + rmWhitespace?: boolean; + + /** + * Whether or not to compile a `ClientFunction` that can be rendered + * in the browser without depending on ejs.js. Otherwise, a `TemplateFunction` + * will be compiled. + * + * @default false + */ + client?: boolean; + + /** + * The escaping function used with `<%=` construct. It is used in rendering + * and is `.toString()`ed in the generation of client functions. + * + * @default ejs.escapeXML + */ + escape?: EscapeCallback; + + /** + * The filename of the template. Required for inclusion and caching unless + * you are using `renderFile`. Also used for error reporting. + * + * @default undefined + */ + filename?: string; + + /** + * The path to the project root. When this is set, absolute paths for includes + * (/filename.ejs) will be relative to the project root. + * + * @default undefined + */ + root?: string; + + /** + * The opening delimiter for all statements. This allows you to clearly delinate + * the difference between template code and existing delimiters. (It is recommended + * to synchronize this with the closeDelimiter property.) + * + * @default ejs.openDelimiter + */ + openDelimiter?: string; + + /** + * The closing delimiter for all statements. This allows to to clearly delinate + * the difference between template code and existing delimiters. (It is recommended + * to synchronize this with the openDelimiter property.) + * + * @default ejs.closeDelimiter + */ + closeDelimiter?: string; + + /** + * The delimiter used in template compilation. + * + * @default ejs.delimiter + */ + delimiter?: string; + + /** + * Whether or not to enable caching of template functions. Beware that + * the options of compilation are not checked as being the same, so + * special handling is required if, for example, you want to cache client + * and regular functions of the same file. + * + * Requires `filename` to be set. Only works with rendering function. + * + * @default false + */ + cache?: boolean; + + /** + * The Object to which `this` is set during rendering. + * + * @default this + */ + context?: any; + + /** + * Whether or not to create an async function instead of a regular function. + * This requires language support. + * + * @default false + */ + async?: boolean; + + /** + * Whether to enable legacy preprocessor include directives. + * + * @default true + * + * @example + * ```ejs + * <%- include foo %> + * ``` + */ + legacyInclude?: boolean; + + /** + * Name to use for the object storing local variables when not using `with` or destructuring. + * + * @default ejs.localsName + */ + localsName?: string; +} + +export interface Cache { + /** + * Cache the intermediate JavaScript function for a template. + * + * @param key key for caching + * @param val cached function + */ + set(key: string, val: TemplateFunction): void; + + /** + * Get the cached intermediate JavaScript function for a template. + * + * @param key key for caching + */ + get(key: string): TemplateFunction | undefined; + + /** + * Clear the entire cache. + */ + reset(): void; +} diff --git a/types/ejs/ejs-tests.ts b/types/ejs/test/ejs.cjs.test.ts similarity index 79% rename from types/ejs/ejs-tests.ts rename to types/ejs/test/ejs.cjs.test.ts index 881a3fe103..e7ea26bdc4 100644 --- a/types/ejs/ejs-tests.ts +++ b/types/ejs/test/ejs.cjs.test.ts @@ -1,9 +1,8 @@ /// -import ejs = require("ejs"); +import ejs = require('ejs'); import { readFileSync as read } from 'fs'; -import LRU = require("lru-cache"); -import { TemplateFunction, AsyncTemplateFunction, Options } from "ejs"; +import LRU = require('lru-cache'); const fileName = 'test.ejs'; const people = ['geddy', 'neil', 'alex']; @@ -11,15 +10,15 @@ const data = { people }; const template = '<%= people.join(", "); %>'; const options = { filename: fileName }; let result: string; -let ejsFunction: TemplateFunction; +let ejsFunction: ejs.TemplateFunction; let asyncResult: Promise; -let ejsAsyncFunction: AsyncTemplateFunction; +let ejsAsyncFunction: ejs.AsyncTemplateFunction; -const SimpleCallback = (err: any, html?: string) => { - if (err) { - return null; - } - return html; +const SimpleCallback = (err: any, html: string) => { + if (err) { + return null!; + } + return html; }; result = ejs.render(template); @@ -34,20 +33,20 @@ asyncResult = ejs.renderFile(fileName, data); asyncResult = ejs.renderFile(fileName, data, options); ejsFunction = ejs.compile(''); -ejsFunction = ejs.compile(read(fileName, "utf8")); +ejsFunction = ejs.compile(read(fileName, 'utf8')); ejsFunction = ejs.compile(template); ejsFunction = ejs.compile(template, options); ejsFunction = ejs.compile(template, { cache: true, filename: fileName }); -ejsFunction = ejs.compile(template, { cache: true, filename: fileName, root: "./" }); +ejsFunction = ejs.compile(template, { cache: true, filename: fileName, root: './' }); ejsFunction = ejs.compile(template, { context: { foo: 'FOO' } }); ejsFunction = ejs.compile(template, { compileDebug: false }); ejsFunction = ejs.compile(template, { client: true }); ejsFunction = ejs.compile('<$= people.join(", "); $>', { delimiter: '$' }); ejsFunction = ejs.compile('<%= locals.people.join(", "); %>', { _with: false }); ejsFunction = ejs.compile('<%= locals.people.join(", "); %>', { strict: true }); -ejsFunction = ejs.compile('<%= it.people.join(", "); %>', { _with: false, localsName: "it" }); +ejsFunction = ejs.compile('<%= it.people.join(", "); %>', { _with: false, localsName: 'it' }); ejsFunction = ejs.compile(template, { rmWhitespace: true }); -const customEscape = (str: string) => !str ? '' : str.toUpperCase(); +const customEscape = (str: string) => (!str ? '' : str.toUpperCase()); ejsFunction = ejs.compile(template, { escape: customEscape }); ejsFunction = ejs.compile(template, { async: false }); @@ -63,12 +62,12 @@ asyncResult = ejsAsyncFunction({}); asyncResult = ejsAsyncFunction(data); /** @see https://github.com/mde/ejs/tree/v2.5.7#custom-fileloader */ -ejs.fileLoader = (path: string) => ""; +ejs.fileLoader = (path: string) => ''; /** @see https://github.com/mde/ejs/tree/v2.5.7#caching */ ejs.clearCache(); ejs.cache = LRU(100); /** @see https://github.com/mde/ejs/tree/v2.5.7#custom-delimiters */ -ejs.delimiter = "%"; +ejs.delimiter = '%'; delete ejs.delimiter; diff --git a/types/ejs/test/ejs.umd.test.ts b/types/ejs/test/ejs.umd.test.ts new file mode 100644 index 0000000000..74791c24a9 --- /dev/null +++ b/types/ejs/test/ejs.umd.test.ts @@ -0,0 +1,36 @@ +/** + * The `expectType` function from https://www.npmjs.com/package/tsd, + * except instead of returning `void`, it returns `T`. + */ +declare function expectType(value: T): T; + +// $ExpectType PromiseConstructorLike | undefined +ejs.promiseImpl; + +// $ExpectType () => void +ejs.clearCache; + +// $ExpectType Cache +ejs.cache; + +expectType<{ + (template: string, opts: ejs.Options & { async: true; client?: false }): ejs.AsyncTemplateFunction; + (template: string, opts: ejs.Options & { async: true; client: true }): ejs.AsyncClientFunction; + (template: string, opts?: ejs.Options & { async?: false; client?: false }): ejs.TemplateFunction; + (template: string, opts?: ejs.Options & { async?: false; client: true }): ejs.ClientFunction; + (template: string, opts?: ejs.Options): ejs.AsyncTemplateFunction | ejs.TemplateFunction; +}>(ejs.compile); + +expectType<{ + (template: string, data?: ejs.Data, opts?: ejs.Options & { async: false }): string; + (template: string, data: ejs.Data, opts: ejs.Options & { async: true }): Promise; + (template: string, data: ejs.Data, opts: ejs.Options & { async?: undefined }): string; + (template: string, data?: ejs.Data, opts?: ejs.Options): string | Promise; +}>(ejs.render); + +expectType<{ + (path: string, cb: ejs.RenderFileCallback): T; + (path: string, data: ejs.Data, cb: ejs.RenderFileCallback): T; + (path: string, data: ejs.Data, opts: ejs.Options, cb: ejs.RenderFileCallback): T; + (path: string, data?: ejs.Data, opts?: ejs.Options): Promise; +}>(ejs.renderFile); diff --git a/types/ejs/tsconfig.json b/types/ejs/tsconfig.json index 8724ff1613..e555830d26 100644 --- a/types/ejs/tsconfig.json +++ b/types/ejs/tsconfig.json @@ -6,7 +6,7 @@ ], "noImplicitAny": true, "noImplicitThis": true, - "strictNullChecks": false, + "strictNullChecks": true, "strictFunctionTypes": true, "baseUrl": "../", "typeRoots": [ @@ -21,6 +21,7 @@ }, "files": [ "index.d.ts", - "ejs-tests.ts" + "test/ejs.cjs.test.ts", + "test/ejs.umd.test.ts" ] }