From dc91be0a89f8673b862d9b0fadea514fb958cd6d Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Wed, 8 Oct 2025 14:23:21 -0700 Subject: [PATCH 1/2] Migrate 13 JS files to TypeScript (#57881) --- ...et-lintable-yml.js => get-lintable-yml.ts} | 18 +++-- ...ferences.js => early-access-references.ts} | 20 ++++-- ...{expired-content.js => expired-content.ts} | 31 +++++---- ....js => frontmatter-versions-whitespace.ts} | 17 +++-- ...ts.js => frontmatter-video-transcripts.ts} | 18 +++-- ...terns.js => multiple-emphasis-patterns.ts} | 19 ++++-- ...rsioning.js => table-liquid-versioning.ts} | 11 ++-- ...nning.js => third-party-action-pinning.ts} | 41 +++++++++--- .../liquid/{data.js => data.ts} | 32 +++++++-- .../middleware/{find-page.js => find-page.ts} | 65 ++++++++++++++----- .../{mini-toc-items.js => mini-toc-items.ts} | 4 +- src/links/lib/validate-docs-urls.ts | 2 +- .../{data-directory.js => data-directory.ts} | 29 ++++++--- ...versions.js => get-applicable-versions.ts} | 18 +++-- 14 files changed, 228 insertions(+), 97 deletions(-) rename src/content-linter/lib/helpers/{get-lintable-yml.js => get-lintable-yml.ts} (82%) rename src/content-linter/lib/linting-rules/{early-access-references.js => early-access-references.ts} (80%) rename src/content-linter/lib/linting-rules/{expired-content.js => expired-content.ts} (76%) rename src/content-linter/lib/linting-rules/{frontmatter-versions-whitespace.js => frontmatter-versions-whitespace.ts} (84%) rename src/content-linter/lib/linting-rules/{frontmatter-video-transcripts.js => frontmatter-video-transcripts.ts} (83%) rename src/content-linter/lib/linting-rules/{multiple-emphasis-patterns.js => multiple-emphasis-patterns.ts} (80%) rename src/content-linter/lib/linting-rules/{table-liquid-versioning.js => table-liquid-versioning.ts} (86%) rename src/content-linter/lib/linting-rules/{third-party-action-pinning.js => third-party-action-pinning.ts} (69%) rename src/content-render/liquid/{data.js => data.ts} (76%) rename src/frame/middleware/{find-page.js => find-page.ts} (63%) rename src/frame/tests/{mini-toc-items.js => mini-toc-items.ts} (95%) rename src/tests/helpers/{data-directory.js => data-directory.ts} (73%) rename src/versions/tests/{get-applicable-versions.js => get-applicable-versions.ts} (88%) diff --git a/src/content-linter/lib/helpers/get-lintable-yml.js b/src/content-linter/lib/helpers/get-lintable-yml.ts similarity index 82% rename from src/content-linter/lib/helpers/get-lintable-yml.js rename to src/content-linter/lib/helpers/get-lintable-yml.ts index 1a53648df11c..f697943349ea 100755 --- a/src/content-linter/lib/helpers/get-lintable-yml.js +++ b/src/content-linter/lib/helpers/get-lintable-yml.ts @@ -24,8 +24,8 @@ import ajv from '@/tests/lib/validate-json-schema' // mdDict will be populated with: // // { '/foo/bar/0': 'item 1', '/foo/bar/1': 'item 2' } -const mdDict = new Map() -const lintableData = Object.keys(dataSchemas) +const mdDict = new Map() +const lintableData: string[] = Object.keys(dataSchemas) // To redefine a custom keyword, you must remove it // then re-add it with the new definition. The default @@ -37,7 +37,8 @@ ajv.addKeyword({ type: 'string', // For docs on defining validate see // https://ajv.js.org/keywords.html#define-keyword-with-validate-function - validate: (compiled, data, schema, parentInfo) => { + // Using any for validate function params because AJV's type definitions for custom keywords are complex + validate: (compiled: any, data: any, schema: any, parentInfo: any): boolean => { mdDict.set(parentInfo.instancePath, data) return true }, @@ -55,13 +56,14 @@ ajv.addKeyword({ // back to the location in the original schema file, // so we also need the parent path of the `lintable` // property in the schema. -export async function getLintableYml(dataFilePath) { +export async function getLintableYml(dataFilePath: string): Promise | null> { const matchingDataPath = lintableData.find( (ref) => dataFilePath === ref || dataFilePath.startsWith(ref), ) if (!matchingDataPath) return null const schemaFilePath = dataSchemas[matchingDataPath] + if (!schemaFilePath) return null const schema = (await import(schemaFilePath)).default if (!schema) return null @@ -78,13 +80,15 @@ export async function getLintableYml(dataFilePath) { // back to a file in the data directory. // The resulting key looks like: // 'data/variables/product.yml /pat_v1_caps' -function addPathToKey(mdDict, dataFilePath) { +function addPathToKey(mdDict: Map, dataFilePath: string): Map { const keys = Array.from(mdDict.keys()) keys.forEach((key) => { const newKey = `${dataFilePath} ${key}` const value = mdDict.get(key) - mdDict.delete(key) - mdDict.set(newKey, value) + if (value !== undefined) { + mdDict.delete(key) + mdDict.set(newKey, value) + } }) return mdDict } diff --git a/src/content-linter/lib/linting-rules/early-access-references.js b/src/content-linter/lib/linting-rules/early-access-references.ts similarity index 80% rename from src/content-linter/lib/linting-rules/early-access-references.js rename to src/content-linter/lib/linting-rules/early-access-references.ts index c9e6f1ca8b54..666ae1e0ae2d 100644 --- a/src/content-linter/lib/linting-rules/early-access-references.js +++ b/src/content-linter/lib/linting-rules/early-access-references.ts @@ -1,7 +1,15 @@ +// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import yaml from 'js-yaml' import { getRange, getFrontmatter } from '../helpers/utils' +import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types' + +interface Frontmatter { + redirect_from?: string | string[] + children?: string[] + [key: string]: any +} const ERROR_MESSAGE = 'An early access reference appears to be used in a non-early access doc. Remove early access references or disable this rule.' @@ -10,20 +18,20 @@ const ERROR_MESSAGE = // There are several existing allowed references to `early access` // as a GitHub feature. This rule focuses on references to early // access pages. -const isEarlyAccessFilepath = (filepath) => filepath.includes('early-access') +const isEarlyAccessFilepath = (filepath: string): boolean => filepath.includes('early-access') const EARLY_ACCESS_REGEX = /early-access/gi // This is a pattern seen in link paths for articles about // early access. This pattern is ok. const EARLY_ACCESS_ARTICLE_REGEX = /-early-access-/ -export const earlyAccessReferences = { +export const earlyAccessReferences: Rule = { names: ['GHD008', 'early-access-references'], description: 'Files that are not early access should not reference early-access or early-access files', tags: ['feature', 'early-access'], severity: 'error', - function: (params, onError) => { + function: (params: RuleParams, onError: RuleErrorCallback) => { if (isEarlyAccessFilepath(params.name)) return // Find errors in content @@ -44,17 +52,17 @@ export const earlyAccessReferences = { }, } -export const frontmatterEarlyAccessReferences = { +export const frontmatterEarlyAccessReferences: Rule = { names: ['GHD009', 'frontmatter-early-access-references'], description: 'Files that are not early access should not have frontmatter that references early-access', tags: ['frontmatter', 'feature', 'early-access'], - function: (params, onError) => { + function: (params: RuleParams, onError: RuleErrorCallback) => { const filepath = params.name if (isEarlyAccessFilepath(filepath)) return // Find errors in frontmatter - const fm = getFrontmatter(params.lines) + const fm = getFrontmatter(params.lines) as Frontmatter | null if (!fm) return // The redirect_from property is allowed to contain early-access paths diff --git a/src/content-linter/lib/linting-rules/expired-content.js b/src/content-linter/lib/linting-rules/expired-content.ts similarity index 76% rename from src/content-linter/lib/linting-rules/expired-content.js rename to src/content-linter/lib/linting-rules/expired-content.ts index ba0bf4cc6f37..152de51cbbe0 100644 --- a/src/content-linter/lib/linting-rules/expired-content.js +++ b/src/content-linter/lib/linting-rules/expired-content.ts @@ -1,5 +1,8 @@ +// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, newLineRe } from 'markdownlint-rule-helpers' +import type { RuleParams, RuleErrorCallback, MarkdownToken, Rule } from '@/content-linter/types' + // This rule looks for opening and closing HTML comment tags that // contain an expiration date in the format: // @@ -8,20 +11,20 @@ import { addError, newLineRe } from 'markdownlint-rule-helpers' // // The `end expires` closing tag closes the content that is expired // and must be removed. -export const expiredContent = { +export const expiredContent: Rule = { names: ['GHD038', 'expired-content'], description: 'Expired content must be remediated.', tags: ['expired'], - function: (params, onError) => { - const tokensToCheck = params.tokens.filter( - (token) => token.type === 'inline' || token.type === 'html_block', + function: (params: RuleParams, onError: RuleErrorCallback) => { + const tokensToCheck = (params.tokens || []).filter( + (token: MarkdownToken) => token.type === 'inline' || token.type === 'html_block', ) - tokensToCheck.forEach((token) => { + tokensToCheck.forEach((token: MarkdownToken) => { // Looking for just opening tag with format: // - const match = token.content.match(//) - if (!match) return + const match = token.content?.match(//) + if (!match || !token.content) return const expireDate = new Date(match.splice(1, 3).join(' ')) const today = new Date() @@ -57,20 +60,20 @@ export const DAYS_TO_WARN_BEFORE_EXPIRED = 14 // // The `end expires` closing tag closes the content that is expired // and must be removed. -export const expiringSoon = { +export const expiringSoon: Rule = { names: ['GHD039', 'expiring-soon'], description: 'Content that expires soon should be proactively addressed.', tags: ['expired'], - function: (params, onError) => { - const tokensToCheck = params.tokens.filter( - (token) => token.type === 'inline' || token.type === 'html_block', + function: (params: RuleParams, onError: RuleErrorCallback) => { + const tokensToCheck = (params.tokens || []).filter( + (token: MarkdownToken) => token.type === 'inline' || token.type === 'html_block', ) - tokensToCheck.forEach((token) => { + tokensToCheck.forEach((token: MarkdownToken) => { // Looking for just opening tag with format: // - const match = token.content.match(//) - if (!match) return + const match = token.content?.match(//) + if (!match || !token.content) return const expireDate = new Date(match.splice(1, 3).join(' ')) const today = new Date() diff --git a/src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.js b/src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.ts similarity index 84% rename from src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.js rename to src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.ts index befc1d5ee882..680873b80dd4 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.js +++ b/src/content-linter/lib/linting-rules/frontmatter-versions-whitespace.ts @@ -1,12 +1,19 @@ +// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getFrontmatter } from '@/content-linter/lib/helpers/utils' +import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types' -export const frontmatterVersionsWhitespace = { +interface Frontmatter { + versions?: Record + [key: string]: any +} + +export const frontmatterVersionsWhitespace: Rule = { names: ['GHD051', 'frontmatter-versions-whitespace'], description: 'Versions frontmatter should not contain unnecessary whitespace', tags: ['frontmatter', 'versions'], - function: (params, onError) => { - const fm = getFrontmatter(params.lines) + function: (params: RuleParams, onError: RuleErrorCallback) => { + const fm = getFrontmatter(params.lines) as Frontmatter | null if (!fm || !fm.versions) return const versionsObj = fm.versions @@ -58,7 +65,7 @@ export const frontmatterVersionsWhitespace = { * Allows whitespace in complex expressions like '<3.6 >3.8' * but disallows leading/trailing whitespace */ -function checkForUnwantedWhitespace(value) { +function checkForUnwantedWhitespace(value: string): boolean { // Don't flag if the value is just whitespace or empty if (!value || value.trim() === '') return false @@ -82,7 +89,7 @@ function checkForUnwantedWhitespace(value) { /** * Get the cleaned version of a value by removing appropriate whitespace */ -function getCleanedValue(value) { +function getCleanedValue(value: string): string { // For values with operators, just trim leading/trailing whitespace const hasOperators = /[<>=]/.test(value) if (hasOperators) { diff --git a/src/content-linter/lib/linting-rules/frontmatter-video-transcripts.js b/src/content-linter/lib/linting-rules/frontmatter-video-transcripts.ts similarity index 83% rename from src/content-linter/lib/linting-rules/frontmatter-video-transcripts.js rename to src/content-linter/lib/linting-rules/frontmatter-video-transcripts.ts index ed736a54cdfe..b4ff762c1548 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-video-transcripts.js +++ b/src/content-linter/lib/linting-rules/frontmatter-video-transcripts.ts @@ -1,16 +1,26 @@ +// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import path from 'path' import { getFrontmatter } from '../helpers/utils' +import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types' -export const frontmatterVideoTranscripts = { +interface Frontmatter { + product_video?: string + product_video_transcript?: string + title?: string + layout?: string + [key: string]: any +} + +export const frontmatterVideoTranscripts: Rule = { names: ['GHD011', 'frontmatter-video-transcripts'], description: 'Video transcript must be configured correctly', tags: ['frontmatter', 'feature', 'video-transcripts'], - function: (params, onError) => { + function: (params: RuleParams, onError: RuleErrorCallback) => { const filepath = params.name - const fm = getFrontmatter(params.lines) + const fm = getFrontmatter(params.lines) as Frontmatter | null if (!fm) return const isTranscriptContent = @@ -29,7 +39,7 @@ export const frontmatterVideoTranscripts = { null, // No fix possible ) } - if (!fm.title.startsWith('Transcript - ')) { + if (fm.title && !fm.title.startsWith('Transcript - ')) { const lineNumber = params.lines.findIndex((line) => line.startsWith('title:')) + 1 const lineContent = params.lines[lineNumber - 1] addError( diff --git a/src/content-linter/lib/linting-rules/multiple-emphasis-patterns.js b/src/content-linter/lib/linting-rules/multiple-emphasis-patterns.ts similarity index 80% rename from src/content-linter/lib/linting-rules/multiple-emphasis-patterns.js rename to src/content-linter/lib/linting-rules/multiple-emphasis-patterns.ts index 5e02edd74b1e..3075ce51ff8d 100644 --- a/src/content-linter/lib/linting-rules/multiple-emphasis-patterns.js +++ b/src/content-linter/lib/linting-rules/multiple-emphasis-patterns.ts @@ -1,16 +1,23 @@ +// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError } from 'markdownlint-rule-helpers' import { getRange } from '../helpers/utils' import frontmatter from '@/frame/lib/read-frontmatter' +import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types' -export const multipleEmphasisPatterns = { +interface Frontmatter { + autogenerated?: boolean + [key: string]: any +} + +export const multipleEmphasisPatterns: Rule = { names: ['GHD050', 'multiple-emphasis-patterns'], description: 'Do not use more than one emphasis/strong, italics, or uppercase for a string', tags: ['formatting', 'emphasis', 'style'], severity: 'warning', - function: (params, onError) => { + function: (params: RuleParams, onError: RuleErrorCallback) => { // Skip autogenerated files const frontmatterString = params.frontMatterLines.join('\n') - const fm = frontmatter(frontmatterString).data + const fm = frontmatter(frontmatterString).data as Frontmatter if (fm && fm.autogenerated) return const lines = params.lines @@ -38,9 +45,9 @@ export const multipleEmphasisPatterns = { /** * Check for multiple emphasis types in a single text segment */ -function checkMultipleEmphasis(line, lineNumber, onError) { +function checkMultipleEmphasis(line: string, lineNumber: number, onError: RuleErrorCallback): void { // Focus on the clearest violations of the style guide - const multipleEmphasisPatterns = [ + const multipleEmphasisPatterns: Array<{ regex: RegExp; types: string[] }> = [ // Bold + italic combinations (***text***) { regex: /\*\*\*([^*]+)\*\*\*/g, types: ['bold', 'italic'] }, { regex: /___([^_]+)___/g, types: ['bold', 'italic'] }, @@ -76,7 +83,7 @@ function checkMultipleEmphasis(line, lineNumber, onError) { /** * Determine if a match should be skipped (likely intentional formatting) */ -function shouldSkipMatch(fullMatch, content) { +function shouldSkipMatch(fullMatch: string, content: string): boolean { // Skip common false positives if (!content) return true diff --git a/src/content-linter/lib/linting-rules/table-liquid-versioning.js b/src/content-linter/lib/linting-rules/table-liquid-versioning.ts similarity index 86% rename from src/content-linter/lib/linting-rules/table-liquid-versioning.js rename to src/content-linter/lib/linting-rules/table-liquid-versioning.ts index e2dea1f4805f..12bc2dc96645 100644 --- a/src/content-linter/lib/linting-rules/table-liquid-versioning.js +++ b/src/content-linter/lib/linting-rules/table-liquid-versioning.ts @@ -1,4 +1,7 @@ -import { addError, filterTokens } from 'markdownlint-rule-helpers' +// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations +import { addError } from 'markdownlint-rule-helpers' + +import type { RuleParams, RuleErrorCallback, Rule } from '@/content-linter/types' // Detects a Markdown table delimiter row const delimiterRegexPure = /(\s)*(:)?(-+)(:)?(\s)*(\|)/ @@ -9,13 +12,13 @@ const liquidRegex = /^{%-?\s*(ifversion|else|endif).*-?%}/ // Detects a Markdown table row with a Liquid versioning tag const liquidAfterRowRegex = /(\|{1}).*(\|{1}).*{%\s*(ifversion|else|endif).*%}$/ -export const tableLiquidVersioning = { +export const tableLiquidVersioning: Rule = { names: ['GHD040', 'table-liquid-versioning'], description: 'Tables must use the correct liquid versioning format', severity: 'error', tags: ['tables'], - function: function GHD040(params, onError) { + function: function GHD040(params: RuleParams, onError: RuleErrorCallback) { const lines = params.lines let inTable = false for (let i = 0; i < lines.length; i++) { @@ -75,7 +78,7 @@ export const tableLiquidVersioning = { }, } -function isPreviousLineIndented(line, previousLine) { +function isPreviousLineIndented(line: string, previousLine: string): boolean { if (!line || !previousLine) return false const numWhitespaceLine = line.length - line.trimLeft().length const numWhitespacePrevLine = previousLine.length - previousLine.trimLeft().length diff --git a/src/content-linter/lib/linting-rules/third-party-action-pinning.js b/src/content-linter/lib/linting-rules/third-party-action-pinning.ts similarity index 69% rename from src/content-linter/lib/linting-rules/third-party-action-pinning.js rename to src/content-linter/lib/linting-rules/third-party-action-pinning.ts index 78cffe7165ea..ed3ffc88fb0f 100644 --- a/src/content-linter/lib/linting-rules/third-party-action-pinning.js +++ b/src/content-linter/lib/linting-rules/third-party-action-pinning.ts @@ -1,8 +1,10 @@ -import yaml from 'js-yaml' +// @ts-ignore - markdownlint-rule-helpers doesn't provide TypeScript declarations import { addError, filterTokens } from 'markdownlint-rule-helpers' +import yaml from 'js-yaml' import { liquid } from '@/content-render/index' import { allVersions } from '@/versions/lib/all-versions' +import type { RuleParams, RuleErrorCallback, MarkdownToken, Rule } from '@/content-linter/types' // Detects third-party actions in the format `owner/repo@ref` const actionRegex = /[\w-]+\/[\w-]+@[\w-]+/ @@ -11,16 +13,33 @@ const shaRegex = /[\w-]+\/[\w-]+@[0-9a-fA-F]{40}/ // Detects first-party actions const firstPartyPrefixes = ['actions/', './.github/actions/', 'github/', 'octo-org/', 'OWNER/'] -export const thirdPartyActionPinning = { +interface WorkflowStep { + uses?: string + [key: string]: any +} + +interface WorkflowJob { + steps?: WorkflowStep[] + [key: string]: any +} + +interface WorkflowYaml { + jobs?: Record + steps?: WorkflowStep[] + [key: string]: any +} + +export const thirdPartyActionPinning: Rule = { names: ['GHD041', 'third-party-action-pinning'], description: 'Code examples that use third-party actions must always pin to a full length commit SHA', tags: ['feature', 'actions'], parser: 'markdownit', asynchronous: true, - function: (params, onError) => { - filterTokens(params, 'fence', async (token) => { - const lang = token.info.trim().split(/\s+/u).shift().toLowerCase() + function: (params: RuleParams, onError: RuleErrorCallback) => { + filterTokens(params, 'fence', async (token: MarkdownToken) => { + if (!token.info || !token.content) return + const lang = token.info.trim().split(/\s+/u).shift()?.toLowerCase() if (lang !== 'yaml' && lang !== 'yml') return if (!token.content.includes('steps:')) return if (!token.content.includes('uses:')) return @@ -32,7 +51,7 @@ export const thirdPartyActionPinning = { // If we don't parse the Liquid first, yaml loading chokes on {% raw %} tags const renderedYaml = await liquid.parseAndRender(token.content, context) try { - const yamlObj = yaml.load(renderedYaml) + const yamlObj = yaml.load(renderedYaml) as WorkflowYaml const steps = getWorkflowSteps(yamlObj) if (!steps.some((step) => step.uses)) return @@ -40,11 +59,13 @@ export const thirdPartyActionPinning = { if (step.uses) { const actionMatch = step.uses.match(actionRegex) if (actionMatch) { - const isFirstParty = firstPartyPrefixes.some((prefix) => step.uses.startsWith(prefix)) + const isFirstParty = firstPartyPrefixes.some((prefix) => + step.uses!.startsWith(prefix), + ) if (!isFirstParty && !shaRegex.test(step.uses)) { addError( onError, - getLineNumber(token.content, step.uses) + token.lineNumber, + getLineNumber(token.content!, step.uses) + token.lineNumber, 'Code examples that use third-party actions must always pin to a full length commit SHA', step.uses, ) @@ -64,7 +85,7 @@ export const thirdPartyActionPinning = { }, } -function getWorkflowSteps(yamlObj) { +function getWorkflowSteps(yamlObj: WorkflowYaml): WorkflowStep[] { if (yamlObj?.jobs) { const jobs = Object.values(yamlObj.jobs) return jobs.flatMap((job) => job.steps || []) @@ -74,7 +95,7 @@ function getWorkflowSteps(yamlObj) { return [] } -function getLineNumber(tokenContent, step) { +function getLineNumber(tokenContent: string, step: string): number { const contentLines = tokenContent.split('\n') return contentLines.findIndex((line) => line.includes(step)) + 1 } diff --git a/src/content-render/liquid/data.js b/src/content-render/liquid/data.ts similarity index 76% rename from src/content-render/liquid/data.js rename to src/content-render/liquid/data.ts index 732dbc8eaaf8..fce51925c73f 100644 --- a/src/content-render/liquid/data.js +++ b/src/content-render/liquid/data.ts @@ -1,4 +1,5 @@ import { TokenizationError } from 'liquidjs' +import type { TagToken, Liquid, Template } from 'liquidjs' import { THROW_ON_EMPTY, DataReferenceError } from './error-handling' import { getDataByLanguage } from '@/data-directory/lib/get-data' @@ -6,8 +7,22 @@ import { getDataByLanguage } from '@/data-directory/lib/get-data' const Syntax = /([a-z0-9/\\_.\-[\]]+)/i const SyntaxHelp = "Syntax Error in 'data' - Valid syntax: data [path]" +// Using any for scope because it has custom environments property not in Liquid's Scope type +interface CustomScope { + environments: any + [key: string]: any +} + +interface DataTag { + path: string + tagToken: TagToken + liquid: Liquid + parse(tagToken: TagToken): void + render(scope: CustomScope): Promise