Compare commits

...

2 Commits

Author SHA1 Message Date
RunDevelopment 2c4e9e50a6 Added CJS output and source maps 2023-05-31 13:43:51 +02:00
Michael Schmidt 55943eb17e
V2 TypeScript (#3687) 2023-05-31 11:31:05 +02:00
413 changed files with 5404 additions and 12727 deletions

View File

@ -148,7 +148,7 @@ module.exports = {
overrides: [
{
files: ['*.d.ts'],
files: ['*.ts'],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
@ -161,12 +161,39 @@ module.exports = {
'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
rules: {
'no-use-before-define': 'off'
'no-use-before-define': 'off',
// I turned this rule off because we use `hasOwnProperty` in a lot of places
// TODO: Think about re-enabling this rule
'no-prototype-builtins': 'off',
// turning off some regex rules
// these are supposed to protect against accidental use but we need those quite often
'no-control-regex': 'off',
'no-empty-character-class': 'off',
'no-useless-escape': 'off',
// type rules
'@typescript-eslint/ban-types': [
'error',
{
'types': {
// un-ban a type that's banned by default
'{}': false
},
'extendDefaults': true
}
],
'@typescript-eslint/consistent-type-imports': [
'warn',
{ disallowTypeAnnotations: true }
]
}
},
{
// Core
files: ['src/core/**/*.js'],
files: ['src/core/**/*.ts'],
env: {
browser: true,
node: true,
@ -174,14 +201,14 @@ module.exports = {
},
{
// Browser-specific parts
files: ['src/auto-start.js'],
files: ['src/auto-start.ts'],
env: {
browser: true
},
},
{
// Plugins
files: ['src/plugins/**/*.js'],
files: ['src/plugins/**/*.ts'],
env: {
browser: true,
},
@ -214,13 +241,42 @@ module.exports = {
mocha: true,
node: true
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tests/tsconfig.json'],
},
},
{
// Benchmark
files: [
'benchmark/**',
],
env: {
node: true
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./benchmark/tsconfig.json'],
},
},
{
// Scripts
files: [
'scripts/**',
],
env: {
node: true
},
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./scripts/tsconfig.json'],
},
},
{
// Gulp, Danger, and benchmark
files: [
'gulpfile.js/**',
'dangerfile.js',
'benchmark/**',
],
env: {
node: true

View File

@ -1,19 +1,18 @@
// @ts-check
const Benchmark = require('benchmark');
const crypto = require('crypto');
const fs = require('fs');
const fetch = require('node-fetch').default;
const path = require('path');
const simpleGit = require('simple-git');
const { argv } = require('yargs');
const { parseLanguageNames } = require('../tests/helper/test-case');
import Benchmark from 'benchmark';
import fetch from 'cross-fetch';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import { gitP } from 'simple-git';
import { argv } from 'yargs';
import { parseLanguageNames } from '../tests/helper/test-case';
import { config as baseConfig } from './config';
import type { Prism } from '../src/core';
import type { Config, ConfigOptions } from './config';
import type { Options, Stats } from 'benchmark';
/**
* @param {import("./config").Config} config
*/
async function runBenchmark(config) {
async function runBenchmark(config: Config) {
const cases = await getCases(config);
const candidates = await getCandidates(config);
const maxCandidateNameLength = candidates.reduce((a, c) => Math.max(a, c.name.length), 0);
@ -24,12 +23,13 @@ async function runBenchmark(config) {
const estimate = candidates.length * totalNumberOfCaseFiles * config.options.maxTime;
console.log(`Estimated duration: ${Math.floor(estimate / 60)}m ${Math.floor(estimate % 60)}s`);
/**
* @type {Summary[]}
*
* @typedef {{ best: number; worst: number, relative: number[], avgRelative?: number }} Summary
*/
const totalSummary = Array.from({ length: candidates.length }, () => ({ best: 0, worst: 0, relative: [] }));
interface Summary {
best: number
worst: number
relative: number[]
avgRelative?: number
}
const totalSummary: Summary[] = Array.from({ length: candidates.length }, () => ({ best: 0, worst: 0, relative: [] }));
for (const $case of cases) {
@ -45,12 +45,11 @@ async function runBenchmark(config) {
// prepare candidates
const warmupCode = await fs.promises.readFile($case.files[0].path, 'utf8');
/** @type {[string, (code: string) => void][]} */
const candidateFunctions = candidates.map(({ name, setup }) => {
const fn = setup($case.language, $case.languages);
const candidateFunctions = await Promise.all(candidates.map(async ({ name, setup }) => {
const fn = await setup($case.language, $case.languages);
fn(warmupCode); // warmup
return [name, fn];
});
return [name, fn] as const;
}));
// bench all files
@ -109,55 +108,53 @@ async function runBenchmark(config) {
const name = candidates[i].name.padEnd(maxCandidateNameLength, ' ');
const best = String(s.best).padStart('best'.length);
const worst = String(s.worst).padStart('worst'.length);
const relative = ((s.avgRelative / minAvgRelative).toFixed(2) + 'x').padStart('relative'.length);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const relative = ((s.avgRelative! / minAvgRelative).toFixed(2) + 'x').padStart('relative'.length);
console.log(` ${name} ${best} ${worst} ${relative}`);
});
}
function getConfig() {
const base = require('./config');
function getConfig(): Config {
const base = baseConfig;
const args = /** @type {Record<string, unknown>} */(argv);
const args = argv as Record<string, unknown>;
if (typeof args.testFunction === 'string') {
// @ts-ignore
base.options.testFunction = args.testFunction;
baseConfig.options.testFunction = args.testFunction as ConfigOptions['testFunction'];
}
if (typeof args.maxTime === 'number') {
base.options.maxTime = args.maxTime;
baseConfig.options.maxTime = args.maxTime;
}
if (typeof args.language === 'string') {
base.options.language = args.language;
baseConfig.options.language = args.language;
}
if (typeof args.remotesOnly === 'boolean') {
base.options.remotesOnly = args.remotesOnly;
baseConfig.options.remotesOnly = args.remotesOnly;
}
return base;
}
/**
* @param {import("./config").Config} config
* @returns {Promise<Case[]>}
*
* @typedef Case
* @property {string} id
* @property {string} language The main language.
* @property {string[]} languages All languages that have to be loaded.
* @property {FileInfo[]} files
*/
async function getCases(config) {
/** @type {Map<string, ReadonlySet<FileInfo>>} */
const caseFileCache = new Map();
interface Case {
id: string
/**
* The main language.
*/
language: string
/**
* All languages that have to be loaded.
*/
languages: string[]
files: FileInfo[]
}
async function getCases(config: Config) {
const caseFileCache = new Map<string, ReadonlySet<FileInfo>>();
/**
* Returns all files of the test case with the given id.
*
* @param {string} id
* @returns {Promise<ReadonlySet<FileInfo>>}
*/
async function getCaseFiles(id) {
async function getCaseFiles(id: string) {
const cached = caseFileCache.get(id);
if (cached) {
return cached;
@ -168,8 +165,7 @@ async function getCases(config) {
throw new Error(`Unknown case "${id}"`);
}
/** @type {Set<FileInfo>} */
const files = new Set();
const files = new Set<FileInfo>();
caseFileCache.set(id, files);
await Promise.all(toArray(caseEntry.files).map(async (uri) => {
@ -184,22 +180,18 @@ async function getCases(config) {
/**
* Returns whether the case is enabled by the options provided by the user.
*
* @param {string[]} languages
* @returns {boolean}
*/
function isEnabled(languages) {
function isEnabled(languages: string[]) {
if (config.options.language) {
// test whether the given languages contain any of the required languages
const required = new Set(config.options.language.split(/,/g).filter(Boolean));
const required = new Set(config.options.language.split(/,/).filter(Boolean));
return languages.some((l) => required.has(l));
}
return true;
}
/** @type {Case[]} */
const cases = [];
const cases: Case[] = [];
for (const id of Object.keys(config.cases)) {
const parsed = parseLanguageNames(id);
@ -220,17 +212,16 @@ async function getCases(config) {
return cases;
}
/** @type {Map<string, Promise<FileInfo>>} */
const fileInfoCache = new Map();
interface FileInfo {
uri: string
path: string
size: number
}
const fileInfoCache = new Map<string, Promise<FileInfo>>();
/**
* Returns the path and other information for the given file identifier.
*
* @param {string} uri
* @returns {Promise<FileInfo>}
*
* @typedef {{ uri: string, path: string, size: number }} FileInfo
*/
function getFileInfo(uri) {
function getFileInfo(uri: string) {
let info = fileInfoCache.get(uri);
if (info === undefined) {
info = getFileInfoUncached(uri);
@ -238,11 +229,7 @@ function getFileInfo(uri) {
}
return info;
}
/**
* @param {string} uri
* @returns {Promise<FileInfo>}
*/
async function getFileInfoUncached(uri) {
async function getFileInfoUncached(uri: string): Promise<FileInfo> {
const p = await getFilePath(uri);
const stat = await fs.promises.stat(p);
if (stat.isFile()) {
@ -257,11 +244,8 @@ async function getFileInfoUncached(uri) {
}
/**
* Returns the local path of the given file identifier.
*
* @param {string} uri
* @returns {Promise<string>}
*/
async function getFilePath(uri) {
async function getFilePath(uri: string) {
if (/^https:\/\//.test(uri)) {
// it's a URL, so let's download the file (if not downloaded already)
const downloadDir = path.join(__dirname, 'downloads');
@ -269,7 +253,8 @@ async function getFilePath(uri) {
// file path
const hash = crypto.createHash('md5').update(uri).digest('hex');
const localPath = path.resolve(downloadDir, hash + '-' + /[-\w\.]*$/.exec(uri)[0]);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const localPath = path.resolve(downloadDir, hash + '-' + /[-\w\.]*$/.exec(uri)![0]);
if (!fs.existsSync(localPath)) {
// download file
@ -284,24 +269,20 @@ async function getFilePath(uri) {
return path.resolve(__dirname, uri);
}
/**
* @param {Iterable<[string, () => void]>} candidates
* @param {import("benchmark").Options} [options]
* @returns {Result[]}
*
* @typedef {{ name: string, stats: import("benchmark").Stats }} Result
*/
function measureCandidates(candidates, options) {
interface Result {
name: string
stats: Stats
}
function measureCandidates(candidates: Iterable<[string, () => void]>, options: Options): Result[] {
const suite = new Benchmark.Suite('temp name');
for (const [name, fn] of candidates) {
suite.add(name, fn, options);
}
/** @type {Result[]} */
const results = [];
const results: Result[] = [];
suite.on('cycle', (event) => {
suite.on('cycle', (event: { target: Result }) => {
results.push({
name: event.target.name,
stats: event.target.stats
@ -311,11 +292,7 @@ function measureCandidates(candidates, options) {
return results;
}
/**
* @param {Result[]} results
* @returns {Result | null}
*/
function getBest(results) {
function getBest(results: Result[]): Result | null {
if (results.length >= 2) {
const sorted = [...results].sort((a, b) => a.stats.mean - b.stats.mean);
const best = sorted[0].stats;
@ -329,11 +306,7 @@ function getBest(results) {
return null;
}
/**
* @param {Result[]} results
* @returns {Result | null}
*/
function getWorst(results) {
function getWorst(results: Result[]): Result | null {
if (results.length >= 2) {
const sorted = [...results].sort((a, b) => b.stats.mean - a.stats.mean);
const worst = sorted[0].stats;
@ -351,20 +324,17 @@ function getWorst(results) {
/**
* Create a new test function from the given Prism instance.
*
* @param {any} Prism
* @param {string} mainLanguage
* @param {string} testFunction
* @returns {(code: string) => void}
*/
function createTestFunction(Prism, mainLanguage, testFunction) {
function createTestFunction(Prism: Prism, mainLanguage: string, testFunction: string): (code: string) => void {
if (testFunction === 'tokenize') {
return (code) => {
Prism.tokenize(code, Prism.languages[mainLanguage]);
const grammar = Prism.components.getLanguage(mainLanguage);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Prism.tokenize(code, grammar!);
};
} else if (testFunction === 'highlight') {
return (code) => {
Prism.highlight(code, Prism.languages[mainLanguage], mainLanguage);
Prism.highlight(code, mainLanguage);
};
} else {
throw new Error(`Unknown test function "${testFunction}"`);
@ -372,25 +342,20 @@ function createTestFunction(Prism, mainLanguage, testFunction) {
}
/**
* @param {import("./config").Config} config
* @returns {Promise<Candidate[]>}
*
* @typedef Candidate
* @property {string} name
* @property {(mainLanguage: string, languages: string[]) => (code: string) => void} setup
*/
async function getCandidates(config) {
/** @type {Candidate[]} */
const candidates = [];
interface Candidate {
name: string
setup(mainLanguage: string, languages: string[]): Promise<(code: string) => void>
}
async function getCandidates(config: Config): Promise<Candidate[]> {
const candidates: Candidate[] = [];
// local
if (!config.options.remotesOnly) {
const localPrismLoader = require('../tests/helper/prism-loader');
const localPrismLoader = await import('../tests/helper/prism-loader');
candidates.push({
name: 'local',
setup(mainLanguage, languages) {
const Prism = localPrismLoader.createInstance(languages);
async setup(mainLanguage, languages) {
const Prism = await localPrismLoader.createInstance(languages);
return createTestFunction(Prism, mainLanguage, config.options.testFunction);
}
});
@ -402,10 +367,11 @@ async function getCandidates(config) {
const remoteBaseDir = path.join(__dirname, 'remotes');
await fs.promises.mkdir(remoteBaseDir, { recursive: true });
const baseGit = simpleGit.gitP(remoteBaseDir);
const baseGit = gitP(remoteBaseDir);
for (const remote of config.remotes) {
const user = /[^/]+(?=\/prism.git)/.exec(remote.repo);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const user = /[^/]+(?=\/prism.git)/.exec(remote.repo)![0];
const branch = remote.branch || 'master';
const remoteName = `${user}@${branch}`;
const remoteDir = path.join(remoteBaseDir, `${user}@${branch}`);
@ -414,18 +380,19 @@ async function getCandidates(config) {
if (!fs.existsSync(remoteDir)) {
console.log(`Cloning ${remote.repo}`);
await baseGit.clone(remote.repo, remoteName);
remoteGit = simpleGit.gitP(remoteDir);
remoteGit = gitP(remoteDir);
} else {
remoteGit = simpleGit.gitP(remoteDir);
remoteGit = gitP(remoteDir);
await remoteGit.fetch('origin', branch); // get latest version of branch
}
await remoteGit.checkout(branch); // switch to branch
const remotePrismLoader = require(path.join(remoteDir, 'tests/helper/prism-loader'));
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const remotePrismLoader = (await import(path.join(remoteDir, 'tests/helper/prism-loader'))) as typeof import('../tests/helper/prism-loader');
candidates.push({
name: remoteName,
setup(mainLanguage, languages) {
const Prism = remotePrismLoader.createInstance(languages);
async setup(mainLanguage, languages) {
const Prism = await remotePrismLoader.createInstance(languages);
return createTestFunction(Prism, mainLanguage, config.options.testFunction);
}
});
@ -436,12 +403,8 @@ async function getCandidates(config) {
/**
* A utility function that converts the given optional array-like value into an array.
*
* @param {T[] | T | undefined | null} value
* @returns {readonly T[]}
* @template T
*/
function toArray(value) {
function toArray<T extends {}>(value: T[] | T | undefined | null): readonly T[] {
if (Array.isArray(value)) {
return value;
} else if (value != null) {
@ -452,4 +415,4 @@ function toArray(value) {
}
runBenchmark(getConfig());
runBenchmark(getConfig()).catch((error) => console.error(error));

View File

@ -1,28 +1,42 @@
/**
* @type {Config}
*
* @typedef Config
* @property {ConfigOptions} options
* @property {ConfigRemote[]} remotes
* @property {Object<string, ConfigCase>} cases
*
* @typedef ConfigOptions
* @property {'tokenize' | 'highlight'} testFunction
* @property {number} maxTime in seconds
* @property {string} [language] An optional comma separated list of languages than, if defined, will be the only
* languages for which the benchmark will be run.
* @property {boolean} [remotesOnly=false] Whether the benchmark will only run with remotes. If `true`, the local
* project will be ignored
*
* @typedef ConfigRemote
* @property {string} repo
* @property {string} [branch='master']
*
* @typedef ConfigCase
* @property {string | string[]} [extends]
* @property {string | string[]} [files]
*/
const config = {
export interface Config {
options: ConfigOptions;
remotes: ConfigRemote[];
cases: Record<string, ConfigCase>;
}
export interface ConfigOptions {
testFunction: 'tokenize' | 'highlight';
/**
* in seconds
*/
maxTime: number;
/**
* An optional comma separated list of languages than, if defined, will be the only languages for which the
* benchmark will be run
*/
language?: string;
/**
* Whether the benchmark will only run with remotes. If `true`, the local project will be ignored
*
* @default false
*/
remotesOnly?: boolean;
}
export interface ConfigRemote {
repo: string;
/**
* @default 'master'
*/
branch?: string;
}
export interface ConfigCase {
extends?: string | string[];
files?: string | string[];
}
export const config: Config = {
options: {
testFunction: 'tokenize',
maxTime: 3,
@ -48,7 +62,7 @@ const config = {
cases: {
'css': {
files: [
'../assets/style.css'
'../website/assets/style.css'
]
},
'css!+css-extras': { extends: 'css' },
@ -59,19 +73,19 @@ const config = {
'https://cdnjs.cloudflare.com/ajax/libs/prism/1.20.0/prism.min.js',
'https://code.jquery.com/jquery-3.4.1.js',
'https://code.jquery.com/jquery-3.4.1.min.js',
'../assets/vendor/utopia.js'
'../website/assets/vendor/utopia.js'
]
},
'json': {
files: [
'../components.json',
'../src/components.json',
'../package-lock.json'
]
},
'markup': {
files: [
'../download.html',
'../index.html',
'../website/download.html',
'../website/index.html',
'https://github.com/PrismJS/prism', // the PrismJS/prism GitHub page
]
},
@ -100,5 +114,3 @@ const config = {
}
}
};
module.exports = config;

78
benchmark/tsconfig.json Normal file
View File

@ -0,0 +1,78 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "CommonJS", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
"checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
"noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
},
"include": ["./**/*"]
}

View File

@ -1,71 +0,0 @@
'use strict';
const del = require('del');
const { src, dest, series } = require('gulp');
const jsdoc = require('gulp-jsdoc3');
const replace = require('gulp-replace');
const pump = require('pump');
const jsDoc = {
config: '../.jsdoc.json',
readme: 'README.md',
files: ['components/prism-core.js'],
junk: ['docs/fonts/Source-Sans-Pro', 'docs/**/Apache-License-2.0.txt']
};
function docsClean() {
return del([
// everything in the docs folder
'docs/**/*',
// except for our CSS overwrites
'!docs/styles',
'!docs/styles/overwrites.css',
]);
}
function docsCreate(cb) {
const config = require(jsDoc.config);
const files = [jsDoc.readme].concat(jsDoc.files);
src(files, { read: false }).pipe(jsdoc(config, cb));
}
function docsAddFavicon(cb) {
return pump([
src('docs/*.html'),
replace(
/\s*<\/head>/,
'\n <link rel="icon" type="image/png" href="/favicon.png"/>$&'
),
dest('docs/')
], cb);
}
function docsRemoveExcessFiles() {
return del(jsDoc.junk);
}
function docsFixLineEnds(cb) {
// https://github.com/jsdoc/jsdoc/issues/1837
return pump([
src('docs/*.html'),
replace(/\r\n?|\n/g, '\n'),
dest('docs/')
], cb);
}
const docs = series(docsClean, docsCreate, docsRemoveExcessFiles, docsAddFavicon, docsFixLineEnds);
module.exports = {
docs,
handlers: {
jsdocCommentFound(comment) {
// This is a hack.
// JSDoc doesn't support TS' type import syntax (e.g. `@type {import("./my-file.js").Type}`) and throws an
// error if used. So we just replace the "function" with some literal that JSDoc will interpret as a
// namespace. Not pretty but it works.
comment.comment = comment.comment
.replace(/\bimport\s*\(\s*(?:"(?:[^"\r\n\\]|\\.)*"|'(?:[^'\r\n\\]|\\.)*')\s*\)/g, '__dyn_import__');
}
}
};

View File

@ -1,10 +0,0 @@
'use strict';
module.exports = {
componentsFile: 'components.json',
componentsFileJS: 'components.js',
components: ['components/**/*.js', '!components/index.js', '!components/**/*.min.js'],
themes: ['themes/*.css', '!themes/*.min.css'],
pluginsCSS: ['src/plugins/**/*.css'],
changelog: 'CHANGELOG.md'
};

10923
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,21 +8,20 @@
"node": ">=14"
},
"scripts": {
"benchmark": "node benchmark/benchmark.js",
"build": "gulp build",
"start": "http-server -c-1",
"benchmark": "ts-node benchmark/benchmark.ts",
"build": "ts-node scripts/build.ts",
"lint": "eslint . --cache",
"lint:fix": "npm run lint -- --fix",
"lint:ci": "eslint . --max-warnings 0",
"regex-coverage": "mocha tests/coverage.js",
"test:components": "mocha tests/components-test.js",
"test:core": "mocha tests/core/**/*.js",
"test:examples": "mocha tests/examples-test.js",
"test:identifiers": "mocha tests/identifier-test.js",
"test:languages": "mocha tests/run.js",
"test:patterns": "mocha tests/pattern-tests.js",
"test:plugins": "mocha tests/plugins/**/*.js",
"test:runner": "mocha tests/testrunner-tests.js",
"regex-coverage": "ts-mocha tests/coverage.ts",
"test:components": "ts-mocha tests/components-test.ts",
"test:core": "ts-mocha tests/core/**/*.ts",
"test:examples": "ts-mocha tests/examples-test.ts",
"test:identifiers": "ts-mocha tests/identifier-test.ts",
"test:languages": "ts-mocha tests/run.ts",
"test:patterns": "ts-mocha tests/pattern-tests.ts",
"test:plugins": "ts-mocha tests/plugins/**/*.ts",
"test:runner": "ts-mocha tests/testrunner-tests.ts",
"test": "npm-run-all test:*",
"tsc": "tsc && tsc -p tests/tsconfig.json"
},
@ -41,38 +40,35 @@
"@babel/core": "^7.18.13",
"@babel/preset-env": "^7.18.10",
"@babel/register": "^7.18.9",
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-typescript": "^11.1.1",
"@types/benchmark": "^2.1.2",
"@types/chai": "^4.3.3",
"@types/clean-css": "^4.2.6",
"@types/jsdom": "^20.0.0",
"@types/mocha": "^9.1.1",
"@types/node-fetch": "^2.5.5",
"@types/node": "^20.2.5",
"@types/prettier": "^2.7.0",
"@types/yargs": "^17.0.11",
"@typescript-eslint/eslint-plugin": "^5.35.1",
"@typescript-eslint/parser": "^5.35.1",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"benchmark": "^2.1.4",
"chai": "^4.2.0",
"clean-css": "^5.3.2",
"cross-fetch": "^3.1.6",
"danger": "^10.5.0",
"del": "^4.1.1",
"docdash": "^1.2.0",
"eslint": "^8.23.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^39.3.6",
"eslint-plugin-regexp": "^1.9.0",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-jsdoc3": "^3.0.0",
"gulp-replace": "^1.0.0",
"gzip-size": "^5.1.1",
"htmlparser2": "^4.0.0",
"http-server": "^0.12.3",
"jsdom": "^16.7.0",
"magic-string": "^0.30.0",
"mocha": "^9.2.2",
"mocha-chai-jest-snapshot": "^1.1.4",
"node-fetch": "^3.1.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"pump": "^3.0.0",
"refa": "^0.9.1",
"regexp-ast-analysis": "^0.2.4",
"regexpp": "^3.2.0",
@ -80,8 +76,10 @@
"rollup-plugin-terser": "^7.0.2",
"scslre": "^0.1.6",
"simple-git": "^3.3.0",
"typescript": "^4.8.2",
"webfont": "^9.0.0",
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
"webfont": "^11.2.26",
"yargs": "^13.2.2"
},
"jspm": {

View File

@ -1,26 +1,69 @@
'use strict';
require('@babel/register');
const { babel } = require('@rollup/plugin-babel');
const fs = require('fs');
const { rm } = require('fs/promises');
const { src, dest, series, parallel } = require('gulp');
const cleanCSS = require('gulp-clean-css');
const path = require('path');
const pump = require('pump');
const { rollup } = require('rollup');
const { terser: rollupTerser } = require('rollup-plugin-terser');
const webfont = require('webfont').default;
const { changes, linkify } = require('./changelog');
const paths = require('./paths');
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import rollupTypescript from '@rollup/plugin-typescript';
import CleanCSS from 'clean-css';
import fs from 'fs';
import { mkdir, readFile, readdir, rm, writeFile, } from 'fs/promises';
import MagicString from 'magic-string';
import path from 'path';
import { rollup } from 'rollup';
import { terser as rollupTerser } from 'rollup-plugin-terser';
import { webfont } from 'webfont';
import { toArray } from '../src/shared/util';
import { components } from './components';
import { parallel, runTask, series } from './tasks';
import type { ComponentProto } from '../src/types';
import type { Plugin, SourceMapInput } from 'rollup';
function buildPluginCSS(cb) {
pump([src(paths.pluginsCSS), cleanCSS(), dest('dist/plugins')], cb);
const SRC_DIR = path.join(__dirname, '../src/');
const languageIds = fs.readdirSync(path.join(SRC_DIR, 'languages')).map((f) => f.slice('prism-'.length).slice(0, -'.js'.length)).sort();
const pluginIds = fs.readdirSync(path.join(SRC_DIR, 'plugins')).sort();
async function loadComponent(id: string) {
let file;
if (pluginIds.includes(id)) {
file = path.join(SRC_DIR, `plugins/${id}/prism-${id}.ts`);
} else {
file = path.join(SRC_DIR, `languages/prism-${id}.ts`);
}
const exports = (await import(file)) as { default: ComponentProto };
return exports.default;
}
function minifyThemes(cb) {
pump([src(paths.themes), cleanCSS(), dest('dist/themes')], cb);
async function minifyCSS() {
const input: Record<string, string> = {};
const THEMES_DIR = path.join(__dirname, '../themes');
const themes = await readdir(THEMES_DIR);
for (const theme of themes.filter((f) => /\.css$/i.test(f))) {
input[`themes/${theme}`] = path.join(THEMES_DIR, theme);
}
for (const id of pluginIds) {
const file = path.join(SRC_DIR, `plugins/${id}/prism-${id}.css`);
if (fs.existsSync(file)) {
input[`plugins/prism-${id}.css`] = file;
}
}
const DIST = path.join(__dirname, '../dist');
const clean = new CleanCSS({});
await Promise.all(Object.entries(input).map(async ([target, file]) => {
const content = await readFile(file, 'utf-8');
const output = clean.minify(content);
if (output.errors.length > 0) {
throw new Error(`CSS minify error:\n${output.errors.join('\n')}`);
}
for (const warn of output.warnings) {
console.warn(`${file}: ${warn}`);
}
const targetFile = path.join(DIST, target);
await mkdir(path.dirname(targetFile), { recursive: true });
await writeFile(targetFile, output.styles, 'utf-8');
}));
}
async function treeviewIconFont() {
@ -43,19 +86,8 @@ async function treeviewIconFont() {
sort: false
});
/** @type {Buffer} */
const woff = result.woff;
/**
* @type {{ contents: string; srcPath: string; metadata: Metadata }[]}
* @typedef Metadata
* @property {string} path
* @property {string} name
* @property {string[]} unicode
* @property {boolean} renamed
* @property {number} width
* @property {number} height
* */
const glyphsData = result.glyphsData;
const woff = result.woff!;
const glyphsData = result.glyphsData!;
const fontFace = `
/* @GENERATED-FONT */
@ -68,8 +100,8 @@ async function treeviewIconFont() {
* Use the following escape sequences to refer to a specific icon:
*
* - ${glyphsData.map(({ metadata }) => {
const codePoint = metadata.unicode[0].codePointAt(0);
return `\\${codePoint.toString(16)} ${metadata.name}`;
const codePoint = metadata!.unicode![0].codePointAt(0)!;
return `\\${codePoint.toString(16)} ${metadata!.name}`;
}).join('\n\t * - ')}
*/
src: url("data:application/font-woff;base64,${woff.toString('base64')}")
@ -84,49 +116,6 @@ async function treeviewIconFont() {
fs.writeFileSync(cssPath, css.replace(fontFaceRegex, fontFace), 'utf-8');
}
/**
* @type {(arg: unknown) => arg is readonly any[]}
*/
const isReadonlyArray = Array.isArray;
/**
* Converts the given value to an array.
*
* If the given value is already an error, it will be returned as is.
*
* @param {T | readonly T[] | undefined | null} value
* @returns {readonly T[]}
* @template {{}} T
*/
function toArray(value) {
if (isReadonlyArray(value)) {
return value;
} else if (value == null) {
return [];
} else {
return [value];
}
}
const SRC_DIR = path.join(__dirname, '../src/');
const languageIds = fs.readdirSync(path.join(SRC_DIR, 'languages')).map((f) => f.slice('prism-'.length).slice(0, -'.js'.length)).sort();
const pluginIds = fs.readdirSync(path.join(SRC_DIR, 'plugins')).sort();
/**
* @param {string} id
* @returns {Promise<import('../src/types').ComponentProto>}
*/
async function loadComponent(id) {
let exports;
if (pluginIds.includes(id)) {
exports = require(path.join(SRC_DIR, `plugins/${id}/prism-${id}.js`));
} else {
exports = require(path.join(SRC_DIR, `languages/prism-${id}.js`));
}
return exports.default;
}
/** @type {Record<string, () => Promise<any>>} */
const dataToInsert = {
aliases_placeholder: async () => {
const data = await Promise.all([...languageIds, ...pluginIds].map(async (id) => {
@ -135,12 +124,9 @@ const dataToInsert = {
}));
return Object.fromEntries(data.flatMap(({ id, alias }) => alias.map((a) => [a, id])));
},
all_languages_placeholder: async () => languageIds,
all_languages_placeholder: () => Promise.resolve(languageIds),
title_placeholder: async () => {
// eslint-disable-next-line import/extensions
const components = require('../src/components.json');
/** @type {Map<string, string>} */
const rawTitles = new Map();
const rawTitles = new Map<string, string>();
for (const [id, entry] of Object.entries(components.languages)) {
if (id === 'meta') {
continue;
@ -165,10 +151,9 @@ const dataToInsert = {
/**
* Tries to guess the name of a language given its id.
*
* @param {string} name The language id.
* @returns {string}
* @param name The language id.
*/
function guessTitle(name) {
function guessTitle(name: string) {
return (name.substring(0, 1).toUpperCase() + name.substring(1)).replace(/s(?=cript)/, 'S');
}
@ -178,46 +163,50 @@ const dataToInsert = {
}
};
/** @type {import("rollup").Plugin} */
const dataInsertPlugin = {
const dataInsertPlugin: Plugin = {
name: 'data-insert',
async renderChunk(code, chunk) {
const placeholderPattern = /\/\*\s*(\w+)\[\s*\*\/[\s\S]*?\/\*\s*\]\s*\*\//g;
const pattern = /\/\*\s*(\w+)\[\s*\*\/[\s\S]*?\/\*\s*\]\s*\*\//g;
let result = '';
let last = 0;
// search for placeholders
const contained = new Set<string>();
let m;
while ((m = pattern.exec(code))) {
contained.add(m[1]);
}
while ((m = placeholderPattern.exec(code))) {
const [, name] = m;
if (contained.size === 0) {
return null;
}
// fetch placeholder data
const dataByName: Record<string, unknown> = {};
for (const name of contained) {
if (name in dataToInsert) {
result += code.slice(last, m.index);
last = m.index + m[0].length;
const data = await dataToInsert[name]();
result += JSON.stringify(data);
dataByName[name] = await dataToInsert[name as keyof typeof dataToInsert]();
} else {
throw new Error(`Unknown placeholder ${name} in ${chunk.fileName}`);
}
}
if (last < code.length) {
result += code.slice(last);
}
return result;
// replace placeholders
const str = new MagicString(code);
str.replace(pattern, (_, name: string) => {
return JSON.stringify(dataByName[name]);
});
return toRenderedChunk(str);
},
};
/** @type {import("rollup").Plugin} */
const inlineRegexSourcePlugin = {
const inlineRegexSourcePlugin: Plugin = {
name: 'inline-regex-source',
renderChunk(code) {
return code.replace(
const str = new MagicString(code);
str.replace(
/\/((?:[^\n\r[\\\/]|\\.|\[(?:[^\n\r\\\]]|\\.)*\])+)\/\s*\.\s*source\b/g,
(m, source) => {
(m, source: string) => {
// escape backslashes
source = source.replace(/\\(.)|\[(?:\\s\\S|\\S\\s)\]/g, (m, g1) => {
source = source.replace(/\\(.)|\[(?:\\s\\S|\\S\\s)\]/g, (m, g1: string) => {
if (g1) {
// characters like /\n/ can just be kept as "\n" instead of being escaped to "\\n"
if (/[nrt0/]/.test(g1)) {
@ -237,6 +226,7 @@ const inlineRegexSourcePlugin = {
return "'" + source + "'";
}
);
return toRenderedChunk(str);
},
};
@ -248,18 +238,26 @@ const inlineRegexSourcePlugin = {
* is a waste of CPU and memory, and it causes the JS thread to be block for roughly 200ms during page load.
*
* @see https://github.com/PrismJS/prism/issues/2768
* @type {import("rollup").Plugin}
*/
const lazyGrammarPlugin = {
const lazyGrammarPlugin: Plugin = {
name: 'lazy-grammar',
renderChunk(code) {
return code.replace(
const str = new MagicString(code);
str.replace(
/^(?<indent>[ \t]+)grammar: (\{[\s\S]*?^\k<indent>\})/m,
(m, _, grammar) => `\tgrammar: () => (${grammar})`
(m, _, grammar: string) => `\tgrammar: () => (${grammar})`
);
return toRenderedChunk(str);
},
};
function toRenderedChunk(s: MagicString): { code: string; map: SourceMapInput } {
return {
code: s.toString(),
map: s.generateMap({ hires: true }) as SourceMapInput,
};
}
const terserPlugin = rollupTerser({
ecma: 2015,
module: true,
@ -276,57 +274,62 @@ const terserPlugin = rollupTerser({
keep_classnames: true
});
const babelPlugin = babel({
babelHelpers: 'bundled',
babelrc: true,
});
async function clean() {
const outputDir = path.join(__dirname, '../dist');
await rm(outputDir, { recursive: true, force: true });
}
async function buildJS() {
/** @type {Record<string, string>} */
const input = {
'core': path.join(SRC_DIR, 'core.js'),
'shared': path.join(SRC_DIR, 'shared.js'),
const input: Record<string, string> = {
'core': path.join(SRC_DIR, 'core.ts'),
'shared': path.join(SRC_DIR, 'shared.ts'),
};
for (const id of languageIds) {
input[`languages/prism-${id}`] = path.join(SRC_DIR, `languages/prism-${id}.js`);
input[`languages/prism-${id}`] = path.join(SRC_DIR, `languages/prism-${id}.ts`);
}
for (const id of pluginIds) {
input[`plugins/${id}/prism-${id}`] = path.join(SRC_DIR, `plugins/${id}/prism-${id}.js`);
input[`plugins/prism-${id}`] = path.join(SRC_DIR, `plugins/${id}/prism-${id}.ts`);
}
/** @type {import("rollup").OutputOptions} */
const outputOptions = {
dir: 'dist',
chunkFileNames: '_chunks/[name]-[hash].js',
validate: true,
plugins: [
lazyGrammarPlugin,
dataInsertPlugin,
inlineRegexSourcePlugin,
terserPlugin
]
};
let bundle;
try {
bundle = await rollup({
input,
plugins: [babelPlugin]
plugins: [rollupTypescript({ module: 'esnext' })],
});
// ESM
await bundle.write({
dir: 'dist/esm',
chunkFileNames: '_chunks/[name]-[hash].js',
validate: true,
sourcemap: 'hidden',
plugins: [
lazyGrammarPlugin,
dataInsertPlugin,
inlineRegexSourcePlugin,
terserPlugin
]
});
// CommonJS
await bundle.write({
dir: 'dist/cjs',
chunkFileNames: '_chunks/[name]-[hash].js',
validate: true,
sourcemap: 'hidden',
format: 'cjs',
exports: 'named',
plugins: [
lazyGrammarPlugin,
dataInsertPlugin,
inlineRegexSourcePlugin,
terserPlugin
]
});
await bundle.write(outputOptions);
} finally {
await bundle?.close();
}
}
module.exports = {
build: series(clean, parallel(buildJS, buildPluginCSS, minifyThemes)),
buildTreeviewCss: treeviewIconFont,
changes,
linkify
};
runTask(series(clean, parallel(buildJS, series(treeviewIconFont, minifyCSS))));

4
scripts/components.ts Normal file
View File

@ -0,0 +1,4 @@
import fs from 'fs';
import path from 'path';
export const components = JSON.parse(fs.readFileSync(path.join(__dirname, '../src/components.json'), 'utf-8')) as Record<string, Record<string, { title: string, aliasTitles?: Record<string, string> }>>;

View File

@ -1,36 +1,20 @@
'use strict';
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import simpleGit from 'simple-git';
import { components } from './components';
import { runTask } from './tasks';
const fs = require('fs/promises');
const git = require('simple-git').gitP(__dirname);
const { changelog } = require('./paths');
async function linkify() {
let content = await fs.readFile(changelog, 'utf-8');
content = content.repeat(/#(\d+)(?![\d\]])/g, '[#$1](https://github.com/PrismJS/prism/issues/$1)');
content = content.repeat(
/\[[\da-f]+(?:, *[\da-f]+)*\]/g,
(m) => m.replace(/([\da-f]{7})[\da-f]*/g, '[`$1`](https://github.com/PrismJS/prism/commit/$1)')
);
await fs.writeFile(changelog, content, 'utf-8');
}
const git = simpleGit(__dirname);
/**
* Creates an array which iterates its items in the order given by `compareFn`.
*
* The array may not be sorted at all times.
*
* @param {(a: T, b: T) => number} compareFn
* @returns {T[]}
* @template T
*/
function createSortedArray(compareFn) {
/** @type {T[]} */
const a = [];
function createSortedArray<T>(compareFn: (a: T, b: T) => number): T[] {
const a: T[] = [];
a['sort'] = function () {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return Array.prototype.sort.call(this, compareFn);
};
a[Symbol.iterator] = function () {
@ -40,19 +24,24 @@ function createSortedArray(compareFn) {
return a;
}
interface CommitInfo {
message: string
hash: string
changes: CommitChange[]
}
interface CommitChange {
file: string
mode: ChangeMode
}
type ChangeMode = 'A' | 'C' | 'D' | 'M' | 'R' | 'T' | 'U' | 'X' | 'B'
/**
* Parses the given log line and adds the list of the changed files to the output.
*
* @param {string} line A one-liner log line consisting of the commit hash and the commit message.
* @returns {Promise<CommitInfo>}
*
* @typedef {{ message: string, hash: string, changes: CommitChange[] }} CommitInfo
* @typedef {{ file: string, mode: ChangeMode }} CommitChange
* @typedef {"A" | "C" | "D" | "M" | "R" | "T" | "U" | "X" | "B"} ChangeMode
* @param line A one-liner log line consisting of the commit hash and the commit message.
*/
async function getCommitInfo(line) {
async function getCommitInfo(line: string): Promise<CommitInfo> {
// eslint-disable-next-line regexp/no-super-linear-backtracking
const [, hash, message] = /^([a-f\d]+)\s+(.*)$/i.exec(line);
const [, hash, message] = /^([a-f\d]+)\s+(.*)$/i.exec(line)!;
/* The output looks like this:
*
@ -63,9 +52,9 @@ async function getCommitInfo(line) {
*/
const output = await git.raw(['diff-tree', '--no-commit-id', '--name-status', '-r', hash]);
const changes = !output ? [] : output.trim().split(/\n/g).map((line) => {
const [, mode, file] = /(\w)\s+(.+)/.exec(line);
return { mode, file };
const changes = !output ? [] : output.trim().split(/\n/).map((line): CommitChange => {
const [, mode, file] = /(\w)\s+(.+)/.exec(line)!;
return { mode: mode as ChangeMode, file };
});
return { hash, message, changes };
@ -74,10 +63,9 @@ async function getCommitInfo(line) {
/**
* Parses the output of `git log` with the given revision range.
*
* @param {string | Promise<string>} range The revision range in which the log will be parsed.
* @returns {Promise<CommitInfo[]>}
* @param range The revision range in which the log will be parsed.
*/
async function getLog(range) {
async function getLog(range: string | Promise<string>): Promise<CommitInfo[]> {
/* The output looks like this:
*
* bfbe4464 Invoke `callback` after `after-highlight` hook (#1588)
@ -86,7 +74,7 @@ async function getLog(range) {
const output = await git.raw(['log', await Promise.resolve(range), '--oneline']);
if (output) {
const commits = output.trim().split(/\n/g);
const commits = output.trim().split(/\n/);
return Promise.all(commits.map(getCommitInfo));
} else {
return [];
@ -98,14 +86,14 @@ const revisionRanges = {
return git.raw(['describe', '--abbrev=0', '--tags']).then((res) => `${res.trim()}..HEAD`);
}
};
const strCompare = (a, b) => a.localeCompare(b, 'en');
const strCompare = (a: string, b: string) => a.localeCompare(b, 'en');
async function changes() {
const { languages, plugins } = require('../src/components');
runTask(async () => {
const { languages, plugins } = components;
const infos = await getLog(revisionRanges.nextRelease());
const entries = {
const entries: Record<string, Record<string, string[] | Record<string, string[]>>> = {
'TODO:': {},
'New components': {
['']: createSortedArray(strCompare)
@ -115,12 +103,7 @@ async function changes() {
'Updated themes': {},
'Other': {},
};
/**
*
* @param {string} category
* @param {string | { message: string, hash: string }} info
*/
function addEntry(category, info) {
function addEntry(category: string, info: string | { message: string, hash: string }) {
const path = category.split(/\s*>>\s*/);
if (path[path.length - 1] !== '') {
path.push('');
@ -129,48 +112,37 @@ async function changes() {
let current = entries;
for (const key of path) {
if (key) {
current = current[key] = current[key] || {};
current = (current[key] = current[key] || {}) as never;
} else {
(current[key] = current[key] || []).push(info);
((current[key] = current[key] || []) as unknown as unknown[]).push(info);
}
}
}
/** @param {CommitChange} change */
function notGenerated(change) {
function notGenerated(change: CommitChange) {
return !change.file.endsWith('.min.js')
&& !change.file.startsWith('docs/')
&& !['prism.js', 'components.js', 'package-lock.json'].includes(change.file);
}
/** @param {CommitChange} change */
function notPartlyGenerated(change) {
function notPartlyGenerated(change: CommitChange) {
return change.file !== 'plugins/autoloader/prism-autoloader.js' &&
change.file !== 'plugins/show-language/prism-show-language.js';
}
/** @param {CommitChange} change */
function notTests(change) {
function notTests(change: CommitChange) {
return !/^tests\//.test(change.file);
}
/** @param {CommitChange} change */
function notExamples(change) {
function notExamples(change: CommitChange) {
return !/^examples\//.test(change.file);
}
/** @param {CommitChange} change */
function notFailures(change) {
function notFailures(change: CommitChange) {
return !/^known-failures.html$/.test(change.file);
}
/** @param {CommitChange} change */
function notComponentsJSON(change) {
function notComponentsJSON(change: CommitChange) {
return change.file !== 'components.json';
}
/**
* @param {((e: T, index: number) => boolean)[]} filters
* @returns {(e: T, index: number) => boolean}
* @template T
*/
function and(...filters) {
function and<T>(...filters: ((e: T, index: number) => boolean)[]): (e: T, index: number) => boolean {
return (e, index) => {
for (let i = 0, l = filters.length; i < l; i++) {
if (!filters[i](e, index)) {
@ -184,12 +156,8 @@ async function changes() {
/**
* Some commit message have the format `component changed: actual message`.
* This function can be used to remove this prefix.
*
* @param {string} prefix
* @param {CommitInfo} info
* @returns {{ message: string, hash: string }}
*/
function removeMessagePrefix(prefix, info) {
function removeMessagePrefix(prefix: string, info: CommitInfo) {
const source = String.raw`^${prefix.replace(/([^-\w\s])/g, '\\$1').replace(/[-\s]/g, '[-\\s]')}:\s*`;
const patter = RegExp(source, 'i');
return {
@ -199,10 +167,7 @@ async function changes() {
}
/**
* @type {((info: CommitInfo) => boolean)[]}
*/
const commitSorters = [
const commitSorters: ((info: CommitInfo) => boolean | undefined)[] = [
function rebuild(info) {
if (info.changes.length > 0 && info.changes.filter(notGenerated).length === 0) {
@ -222,7 +187,7 @@ async function changes() {
if (relevantChanges.length === 1) {
const change = relevantChanges[0];
if (change.mode === 'A' && change.file.startsWith('components/prism-')) {
const lang = change.file.match(/prism-([\w-]+)\.js$/)[1];
const lang = change.file.match(/prism-([\w-]+)\.js$/)![1];
const entry = languages[lang] || {
title: 'REMOVED LANGUAGE ' + lang,
};
@ -248,11 +213,11 @@ async function changes() {
if (relevantChanges.length === 1) {
const change = relevantChanges[0];
if (change.mode === 'M' && change.file.startsWith('components/prism-')) {
const lang = change.file.match(/prism-([\w-]+)\.js$/)[1];
const lang = change.file.match(/prism-([\w-]+)\.js$/)![1];
if (lang === 'core') {
addEntry('Other >> Core', removeMessagePrefix('Core', info));
} else {
const title = languages[lang].title;
const title = languages[lang]!.title;
addEntry('Updated components >> ' + title, removeMessagePrefix(title, info));
}
return true;
@ -268,8 +233,8 @@ async function changes() {
if (relevantChanges.length === 1) {
const change = relevantChanges[0];
const id = change.file.match(/\/prism-([\w-]+)\.js/)[1];
const title = plugins[id].title || plugins[id];
const id = change.file.match(/\/prism-([\w-]+)\.js/)![1];
const title = plugins[id]!.title;
addEntry('Updated plugins >> ' + title, removeMessagePrefix(title, info));
} else {
addEntry('Updated plugins', info);
@ -284,6 +249,7 @@ async function changes() {
})) {
if (info.changes.length === 1) {
const change = info.changes[0];
// eslint-disable-next-line no-sparse-arrays
let name = (change.file.match(/prism-(\w+)\.css$/) || [, 'Default'])[1];
name = name[0].toUpperCase() + name.substr(1);
addEntry('Updated themes >> ' + name, removeMessagePrefix(name, info));
@ -351,23 +317,21 @@ async function changes() {
/**
* Stringifies the given commit info.
*
* @param {string | CommitInfo} info
* @returns {string}
*/
function infoToString(info) {
function infoToString(info: string | CommitInfo) {
if (typeof info === 'string') {
return info;
}
return `${info.message} [\`${info.hash}\`](https://github.com/PrismJS/prism/commit/${info.hash})`;
}
function printCategory(category, indentation = '') {
function printCategory(category: Record<string, Record<string, never>>, indentation = '') {
for (const subCategory of Object.keys(category).sort(strCompare)) {
if (subCategory) {
md += `${indentation}* __${subCategory}__\n`;
printCategory(category[subCategory], indentation + ' ');
} else {
for (const info of category['']) {
const infos = category[''] as unknown as (string | CommitInfo)[];
for (const info of infos) {
md += `${indentation}* ${infoToString(info)}\n`;
}
}
@ -376,13 +340,7 @@ async function changes() {
for (const category of Object.keys(entries)) {
md += `\n### ${category}\n\n`;
printCategory(entries[category]);
printCategory(entries[category] as never);
}
console.log(md);
}
module.exports = {
linkify,
changes
};
});

View File

@ -0,0 +1,15 @@
import fs from 'fs/promises';
import { runTask } from './tasks';
runTask(async () => {
const changelog = 'CHANGELOG.md';
let content = await fs.readFile(changelog, 'utf-8');
content = content.replace(/#(\d+)(?![\d\]])/g, '[#$1](https://github.com/PrismJS/prism/issues/$1)');
content = content.replace(
/\[[\da-f]+(?:, *[\da-f]+)*\]/g,
(m) => m.replace(/([\da-f]{7})[\da-f]*/g, '[`$1`](https://github.com/PrismJS/prism/commit/$1)')
);
await fs.writeFile(changelog, content, 'utf-8');
});

60
scripts/tasks.ts Normal file
View File

@ -0,0 +1,60 @@
type Task = () => (void | Promise<void>);
function wrapTask(name: string, task: Task): Task {
return async function $() {
console.log(`[Starting: ${name}]`);
const start = Date.now();
await task();
const duration = (Date.now() - start) / 1000;
console.log(`[Done: ${name} (${duration.toFixed(2)}s)]`);
};
}
function wrapUnnamedTask(task: Task): Task {
const name = task.name;
if (!name.startsWith('$')) {
task = wrapTask(name, task);
}
return task;
}
export function series(...tasks: Task[]): Task {
return async function $() {
for (const task of tasks.map(wrapUnnamedTask)) {
await task();
}
};
}
export function parallel(...tasks: Task[]): Task {
return async function $() {
await Promise.all(tasks.map(async (task) => {
await wrapUnnamedTask(task)();
}));
};
}
/**
* Given a record of tasks, it will run the task as dictated by the CLI arguments.
*
* To run a specific task, run `node path/to/script.js taskName`.
*/
export function run(tasks: Partial<Record<string, Task>>) {
const selected = String(process.argv[2]);
const task = tasks[selected];
if (!task) {
console.error(`No such task ${selected}. Available tasks: ${Object.keys(tasks).join(', ')}`);
} else {
runTask(task);
}
}
/**
* Runs the given task.
*/
export function runTask(tasks: Task) {
Promise.resolve().then(() => tasks()).catch(
(reason) => {
console.error('Error:');
console.error(reason);
}
);
}

78
scripts/tsconfig.json Normal file
View File

@ -0,0 +1,78 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "CommonJS", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
"checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
"noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
},
"include": ["./**/*"]
}

View File

@ -22,7 +22,6 @@ export const PrismConfig = {
* ```
*
* @default false
* @type {boolean}
* @public
*/
manual: false
@ -30,7 +29,7 @@ export const PrismConfig = {
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
// Get current script and highlight
const script = /** @type {HTMLScriptElement | null} */ (document.currentScript);
const script = document.currentScript as HTMLScriptElement | null;
if (script && script.hasAttribute('data-manual')) {
PrismConfig.manual = true;
}

View File

@ -1,48 +0,0 @@
/**
* @typedef {(string | symbol) & { __keyType?: T }} StateKey
* @template {{}} T
*/
/**
* A simple typed map from some key to its data.
*/
export class HookState {
/**
* @type {Map<string | symbol, {}>}
* @private
*/
_data = new Map();
/**
* @param {StateKey<{}>} key
* @returns {boolean}
*/
has(key) {
return this._data.has(key);
}
/**
* @param {StateKey<T>} key
* @param {T} defaultValue
* @returns {T}
* @template {{}} T
*/
get(key, defaultValue) {
let current = this._data.get(key);
if (current === undefined) {
current = defaultValue;
this._data.set(key, current);
}
return /** @type {T} */ (current);
}
/**
* @param {StateKey<T>} key
* @param {T} value
* @returns {void}
* @template {{}} T
*/
set(key, value) {
this._data.set(key, value);
}
}

25
src/core/hook-state.ts Normal file
View File

@ -0,0 +1,25 @@
export type StateKey<T> = (string | symbol) & { __keyType?: T };
/**
* A simple typed map from some key to its data.
*/
export class HookState {
private _data = new Map<string | symbol, {}>();
has(key: StateKey<{}>): boolean {
return this._data.has(key);
}
get<T extends {}>(key: StateKey<T>, defaultValue: T) {
let current = this._data.get(key);
if (current === undefined) {
current = defaultValue;
this._data.set(key, current);
}
return current as T;
}
set<T extends {}>(key: StateKey<T>, value: T): void {
this._data.set(key, value);
}
}

View File

@ -1,67 +0,0 @@
export class Hooks {
constructor() {
/**
* @type {Map<string, ((env: any) => void)[]>}
* @private
*/
this._all = new Map();
}
/**
* Adds the given callback to the list of callbacks for the given hook and returns a function that
* removes the hook again when called.
*
* The callback will be invoked when the hook it is registered for is run.
* Hooks are usually directly run by a highlight function but you can also run hooks yourself.
*
* One callback function can be registered to multiple hooks.
*
* A callback function must not be registered for the same hook multiple times. Doing so will cause
* undefined behavior. However, registering a callback again after removing it is fine.
*
* @param {Name} name The name of the hook.
* @param {import("./hooks-env").HookCallback<Name>} callback The callback function which is given environment variables.
* @returns {() => void}
* @template {string} Name
* @public
*/
add(name, callback) {
let hooks = this._all.get(name);
if (hooks === undefined) {
hooks = [];
this._all.set(name, hooks);
}
const list = hooks;
list.push(callback);
return () => {
const index = list.indexOf(callback);
if (index !== -1) {
list.splice(index, 1);
}
};
}
/**
* Runs a hook invoking all registered callbacks with the given environment variables.
*
* Callbacks will be invoked synchronously and in the order in which they were registered.
*
* @param {Name} name The name of the hook.
* @param {import("./hooks-env").HookEnv<Name>} env The environment variables of the hook passed to all callbacks registered.
* @template {string} Name
* @public
*/
run(name, env) {
const callbacks = this._all.get(name);
if (!callbacks || !callbacks.length) {
return;
}
for (const callback of callbacks) {
callback(env);
}
}
}

View File

@ -1,6 +1,65 @@
import { Grammar, TokenName } from '../types';
import { HookState } from './hook-state';
import { TokenStream } from './token';
import type { Grammar, TokenName } from '../types';
import type { HookState } from './hook-state';
import type { TokenStream } from './token';
export class Hooks {
// eslint-disable-next-line func-call-spacing
private _all = new Map<string, ((env: unknown) => void)[]>();
/**
* Adds the given callback to the list of callbacks for the given hook and returns a function that
* removes the hook again when called.
*
* The callback will be invoked when the hook it is registered for is run.
* Hooks are usually directly run by a highlight function but you can also run hooks yourself.
*
* One callback function can be registered to multiple hooks.
*
* A callback function must not be registered for the same hook multiple times. Doing so will cause
* undefined behavior. However, registering a callback again after removing it is fine.
*
* @param name The name of the hook.
* @param callback The callback function which is given environment variables.
*/
add<Name extends string>(name: Name, callback: HookCallback<Name>): () => void {
let hooks = this._all.get(name);
if (hooks === undefined) {
hooks = [];
this._all.set(name, hooks);
}
const list = hooks;
list.push(callback as never);
return () => {
const index = list.indexOf(callback as never);
if (index !== -1) {
list.splice(index, 1);
}
};
}
/**
* Runs a hook invoking all registered callbacks with the given environment variables.
*
* Callbacks will be invoked synchronously and in the order in which they were registered.
*
* @param name The name of the hook.
* @param env The environment variables of the hook passed to all callbacks registered.
*/
run<Name extends string>(name: Name, env: HookEnv<Name>): void {
const callbacks = this._all.get(name);
if (!callbacks || !callbacks.length) {
return;
}
for (const callback of callbacks) {
callback(env);
}
}
}
/**
* An interface containing all hooks Prism runs.
@ -52,19 +111,19 @@ export interface BeforeSanityCheckEnv extends StatefulEnv {
grammar: Grammar | undefined;
code: string;
}
export interface BeforeHighlightEnv extends StatefulEnv{
export interface BeforeHighlightEnv extends StatefulEnv {
element: Element;
language: string;
grammar: Grammar | undefined;
code: string;
}
export interface CompleteEnv extends StatefulEnv{
export interface CompleteEnv extends StatefulEnv {
element: Element;
language: string;
grammar: Grammar | undefined;
code: string;
}
export interface BeforeInsertEnv extends StatefulEnv{
export interface BeforeInsertEnv extends StatefulEnv {
element: Element;
language: string;
grammar: Grammar | undefined;

View File

@ -1,91 +0,0 @@
/**
* @typedef LinkedListMiddleNode
* @property {T} value
* @property {LinkedListMiddleNode<T> | LinkedListHeadNode<T>} prev
* @property {LinkedListMiddleNode<T> | LinkedListTailNode<T>} next
* @template T
*/
/**
* @typedef LinkedListHeadNode
* @property {null} value
* @property {null} prev
* @property {LinkedListMiddleNode<T> | LinkedListTailNode<T>} next
* @template T
*/
/**
* @typedef LinkedListTailNode
* @property {null} value
* @property {LinkedListMiddleNode<T> | LinkedListHeadNode<T>} prev
* @property {null} next
* @template T
*/
/**
* @typedef {LinkedListHeadNode<T> | LinkedListTailNode<T> | LinkedListMiddleNode<T>} LinkedListNode
* @template T
*/
/**
* @template T
*/
export class LinkedList {
constructor() {
/** @type {LinkedListHeadNode<T>} */
const head = { value: null, prev: null, next: /** @type {any} */ (null) };
/** @type {LinkedListTailNode<T>} */
const tail = { value: null, prev: head, next: null };
head.next = tail;
this.head = head;
this.tail = tail;
this.length = 0;
}
/**
* Adds a new node with the given value to the list.
*
* @param {LinkedListHeadNode<T> | LinkedListMiddleNode<T>} node
* @param {T} value
* @returns {LinkedListMiddleNode<T>} The added node.
*/
addAfter(node, value) {
// assumes that node != list.tail && values.length >= 0
const next = node.next;
const newNode = { value, prev: node, next };
node.next = newNode;
next.prev = newNode;
this.length++;
return newNode;
}
/**
* Removes `count` nodes after the given node. The given node will not be removed.
*
* @param {LinkedListHeadNode<T> | LinkedListMiddleNode<T>} node
* @param {number} count
*/
removeRange(node, count) {
let next = node.next;
let i = 0;
for (; i < count && next.next !== null; i++) {
next = next.next;
}
node.next = next;
next.prev = node;
this.length -= i;
}
/**
* @returns {T[]}
*/
toArray() {
const array = [];
let node = this.head.next;
while (node.next !== null) {
array.push(node.value);
node = node.next;
}
return array;
}
}

74
src/core/linked-list.ts Normal file
View File

@ -0,0 +1,74 @@
export interface LinkedListMiddleNode<T> {
value: T;
prev: LinkedListMiddleNode<T> | LinkedListHeadNode<T>;
next: LinkedListMiddleNode<T> | LinkedListTailNode<T>;
}
export interface LinkedListHeadNode<T> {
value: null;
prev: null;
next: LinkedListMiddleNode<T> | LinkedListTailNode<T>;
}
export interface LinkedListTailNode<T> {
value: null;
prev: LinkedListMiddleNode<T> | LinkedListHeadNode<T>;
next: null;
}
export class LinkedList<T> {
readonly head: LinkedListHeadNode<T>;
readonly tail: LinkedListTailNode<T>;
length: number;
constructor() {
const head: LinkedListHeadNode<T> = { value: null, prev: null, next: null as never };
const tail: LinkedListTailNode<T> = { value: null, prev: head, next: null };
head.next = tail;
this.head = head;
this.tail = tail;
this.length = 0;
}
/**
* Adds a new node with the given value to the list.
*
* @param node
* @param value
* @returns The added node.
*/
addAfter(node: LinkedListHeadNode<T> | LinkedListMiddleNode<T>, value: T): LinkedListMiddleNode<T> {
// assumes that node != list.tail && values.length >= 0
const next = node.next;
const newNode = { value, prev: node, next };
node.next = newNode;
next.prev = newNode;
this.length++;
return newNode;
}
/**
* Removes `count` nodes after the given node. The given node will not be removed.
*/
removeRange(node: LinkedListHeadNode<T> | LinkedListMiddleNode<T>, count: number): void {
let next = node.next;
let i = 0;
for (; i < count && next.next !== null; i++) {
next = next.next;
}
node.next = next;
next.prev = node;
this.length -= i;
}
toArray(): T[] {
const array: T[] = [];
let node = this.head.next;
while (node.next !== null) {
array.push(node.value);
node = node.next;
}
return array;
}
}

View File

@ -1,37 +0,0 @@
import { Grammar } from '../types';
export interface AsyncHighlightingData {
language: string;
code: string;
grammar: Grammar;
}
export type AsyncHighlighter = (data: AsyncHighlightingData) => Promise<string>;
export interface HighlightAllOptions {
/**
* The root element, whose descendants that have a `.language-xxxx` class will be highlighted.
*/
root?: ParentNode;
async?: AsyncHighlighter;
/**
* An optional callback to be invoked on each element after its highlighting is done.
*
* @see HighlightElementOptions.callback
*/
callback?: (element: Element) => void;
}
export interface HighlightElementOptions {
async?: AsyncHighlighter;
/**
* An optional callback to be invoked after the highlighting is done.
* Mostly useful when `async` is `true`, since in that case, the highlighting is done asynchronously.
*
* @param element The element successfully highlighted.
*/
callback?: (element: Element) => void;
}
export interface HighlightOptions {
grammar?: Grammar;
}

View File

@ -6,10 +6,11 @@ import { Hooks } from './hooks';
import { LinkedList } from './linked-list';
import { Registry } from './registry';
import { Token } from './token';
/**
* @typedef {import("./hooks-env").HookEnvMap} EnvMap
*/
import type { KnownPlugins } from '../known-plugins';
import type { Grammar, GrammarToken, GrammarTokens } from '../types';
import type { HookEnvMap } from './hooks';
import type { LinkedListHeadNode, LinkedListMiddleNode, LinkedListTailNode } from './linked-list';
import type { TokenStream } from './token';
/**
* Prism: Lightweight, robust, elegant syntax highlighting
@ -18,14 +19,10 @@ import { Token } from './token';
* @author Lea Verou <https://lea.verou.me>
*/
export class Prism {
constructor() {
this.hooks = new Hooks();
this.components = new Registry(this);
/**
* @type {Partial<Record<string, unknown> & import('../known-plugins').KnownPlugins>}
*/
this.plugins = {};
}
hooks = new Hooks();
components = new Registry(this);
plugins: Partial<Record<string, unknown> & KnownPlugins> = {};
/**
* This is the most high-level function in Prisms API.
@ -36,28 +33,24 @@ export class Prism {
* 1. `before-highlightall`
* 2. `before-all-elements-highlight`
* 3. All hooks of {@link Prism#highlightElement} for each element.
*
* @param {import("./prism-types").HighlightAllOptions} [options]
*/
highlightAll(options = {}) {
highlightAll(options: HighlightAllOptions = {}) {
const { root, async, callback } = options;
const env = /** @type {EnvMap["before-highlightall"] | EnvMap["before-all-elements-highlight"]} */ ({
const env: HookEnvMap['before-highlightall'] | HookEnvMap['before-all-elements-highlight'] = {
callback,
root: root ?? document,
selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code',
state: new HookState()
});
};
this.hooks.run('before-highlightall', env);
// @ts-ignore
assertEnv<'before-all-elements-highlight'>(env);
env.elements = [...env.root.querySelectorAll(env.selector)];
// @ts-ignore
this.hooks.run('before-all-elements-highlight', env);
// @ts-ignore
for (const element of env.elements) {
this.highlightElement(element, { async, callback: env.callback });
}
@ -77,11 +70,10 @@ export class Prism {
* Some the above hooks will be skipped if the element doesn't contain any text or there is no grammar loaded for
* the element's language.
*
* @param {Element} element The element containing the code.
* @param element The element containing the code.
* It must have a class of `language-xxxx` to be processed, where `xxxx` is a valid language identifier.
* @param {import("./prism-types").HighlightElementOptions} [options]
*/
highlightElement(element, options = {}) {
highlightElement(element: Element, options: HighlightElementOptions = {}) {
const { async, callback } = options;
// Find language
@ -98,10 +90,9 @@ export class Prism {
setLanguage(parent, language);
}
const code = /** @type {string} */ (element.textContent);
const code = element.textContent as string;
/** @type {EnvMap["before-sanity-check"]} */
const env = {
const env: HookEnvMap['before-sanity-check'] = {
element,
language,
grammar,
@ -109,18 +100,13 @@ export class Prism {
state: new HookState()
};
/** @param {string} highlightedCode */
const insertHighlightedCode = (highlightedCode) => {
// @ts-ignore
const insertHighlightedCode = (highlightedCode: string) => {
assertEnv<'before-insert'>(env);
env.highlightedCode = highlightedCode;
// @ts-ignore
this.hooks.run('before-insert', env);
// @ts-ignore
env.element.innerHTML = env.highlightedCode;
// @ts-ignore
this.hooks.run('after-highlight', env);
this.hooks.run('complete', env);
callback && callback(env.element);
@ -152,7 +138,7 @@ export class Prism {
language: env.language,
code: env.code,
grammar: env.grammar,
}).then(insertHighlightedCode);
}).then(insertHighlightedCode, (error) => console.log(error));
} else {
insertHighlightedCode(this.highlight(env.code, env.language, { grammar: env.grammar }));
}
@ -167,21 +153,20 @@ export class Prism {
* 2. `after-tokenize`
* 3. `wrap`: On each {@link Token}.
*
* @param {string} text A string with the code to be highlighted.
* @param {string} language The name of the language definition passed to `grammar`.
* @param {import("./prism-types").HighlightOptions} [options] An object containing the tokens to use.
* @param text A string with the code to be highlighted.
* @param language The name of the language definition passed to `grammar`.
* @param options An object containing the tokens to use.
*
* Usually a language definition like `Prism.languages.markup`.
* @returns {string} The highlighted HTML.
* @returns The highlighted HTML.
* @example
* Prism.highlight('var foo = true;', 'javascript');
*/
highlight(text, language, options) {
highlight(text: string, language: string, options?: HighlightOptions): string {
const languageId = this.components.resolveAlias(language);
const grammar = options?.grammar ?? this.components.getLanguage(languageId);
/** @type {EnvMap["before-tokenize"] | EnvMap["after-tokenize"]} */
const env = ({
const env: HookEnvMap['before-tokenize'] | HookEnvMap['after-tokenize'] = ({
code: text,
grammar,
language
@ -190,12 +175,11 @@ export class Prism {
if (!env.grammar) {
throw new Error('The language "' + env.language + '" has no grammar.');
}
// @ts-ignore
assertEnv<'after-tokenize'>(env);
env.tokens = this.tokenize(env.code, env.grammar);
// @ts-ignore
this.hooks.run('after-tokenize', env);
// @ts-ignore
return stringify(env.tokens, env.language, this.hooks);
}
@ -207,11 +191,11 @@ export class Prism {
*
* This method could be useful in other contexts as well, as a very crude parser.
*
* @param {string} text A string with the code to be highlighted.
* @param {import("../types").Grammar} grammar An object containing the tokens to use.
* @param text A string with the code to be highlighted.
* @param grammar An object containing the tokens to use.
*
* Usually a language definition like `Prism.languages.markup`.
* @returns {import("./token").TokenStream} An array of strings and tokens, a token stream.
* @returns An array of strings and tokens, a token stream.
* @example
* let code = `var foo = 0;`;
* let tokens = Prism.tokenize(code, Prism.getLanguage('javascript'));
@ -221,7 +205,7 @@ export class Prism {
* }
* });
*/
tokenize(text, grammar) {
tokenize(text: string, grammar: Grammar): TokenStream {
const customTokenize = grammar[tokenize];
if (customTokenize) {
return customTokenize(text, grammar, this);
@ -233,8 +217,7 @@ export class Prism {
restGrammar = resolve(this.components, restGrammar[rest]);
}
/** @type {LinkedList<string | Token>} */
const tokenList = new LinkedList();
const tokenList = new LinkedList<string | Token>();
tokenList.addAfter(tokenList.head, text);
this._matchGrammar(text, tokenList, grammar, tokenList.head, 0);
@ -242,21 +225,14 @@ export class Prism {
return tokenList.toArray();
}
/**
* @param {string} text
* @param {LinkedList<string | Token>} tokenList
* @param {import("../types").GrammarTokens} grammar
* @param {import("./linked-list").LinkedListHeadNode<string | Token> | import("./linked-list").LinkedListMiddleNode<string | Token>} startNode
* @param {number} startPos
* @param {RematchOptions} [rematch]
* @returns {void}
* @private
*
* @typedef RematchOptions
* @property {string} cause
* @property {number} reach
*/
_matchGrammar(text, tokenList, grammar, startNode, startPos, rematch) {
private _matchGrammar(
text: string,
tokenList: LinkedList<string | Token>,
grammar: GrammarTokens,
startNode: LinkedListHeadNode<string | Token> | LinkedListMiddleNode<string | Token>,
startPos: number,
rematch?: RematchOptions
): void {
for (const token in grammar) {
const tokenValue = grammar[token];
if (!grammar.hasOwnProperty(token) || !tokenValue) {
@ -266,7 +242,7 @@ export class Prism {
const patterns = Array.isArray(tokenValue) ? tokenValue : [tokenValue];
for (let j = 0; j < patterns.length; ++j) {
if (rematch && rematch.cause === token + ',' + j) {
if (rematch && rematch.cause === `${token},${j}`) {
return;
}
@ -332,8 +308,7 @@ export class Prism {
}
// find the last node which is affected by this match
/** @type {import("./linked-list").LinkedListMiddleNode<Token | string> | import("./linked-list").LinkedListTailNode<Token | string>} */
let k = currentNode;
let k: LinkedListMiddleNode<Token | string> | LinkedListTailNode<Token | string> = currentNode;
for (; k.next !== null && (p < to || typeof k.value === 'string'); k = k.next) {
removeCount++;
p += k.value.length;
@ -381,9 +356,8 @@ export class Prism {
// at least one Token object was removed, so we have to do some rematching
// this can only happen if the current pattern is greedy
/** @type {RematchOptions} */
const nestedRematch = {
cause: token + ',' + j,
const nestedRematch: RematchOptions = {
cause: `${token},${j}`,
reach
};
this._matchGrammar(text, tokenList, grammar, currentNode.prev, pos, nestedRematch);
@ -399,15 +373,52 @@ export class Prism {
}
}
interface RematchOptions {
cause: string;
reach: number;
}
/**
* @param {RegExp} pattern
* @param {number} pos
* @param {string} text
* @param {boolean} lookbehind
* @returns {RegExpExecArray | null}
*/
function matchPattern(pattern, pos, text, lookbehind) {
export interface AsyncHighlightingData {
language: string;
code: string;
grammar: Grammar;
}
export type AsyncHighlighter = (data: AsyncHighlightingData) => Promise<string>;
export interface HighlightAllOptions {
/**
* The root element, whose descendants that have a `.language-xxxx` class will be highlighted.
*/
root?: ParentNode;
async?: AsyncHighlighter;
/**
* An optional callback to be invoked on each element after its highlighting is done.
*
* @see HighlightElementOptions#callback
*/
callback?: (element: Element) => void;
}
export interface HighlightElementOptions {
async?: AsyncHighlighter;
/**
* An optional callback to be invoked after the highlighting is done.
* Mostly useful when `async` is `true`, since in that case, the highlighting is done asynchronously.
*
* @param element The element successfully highlighted.
*/
callback?: (element: Element) => void;
}
export interface HighlightOptions {
grammar?: Grammar;
}
function assertEnv<T extends keyof HookEnvMap>(env: unknown): asserts env is HookEnvMap[T] {
/* noop */
}
function matchPattern(pattern: RegExp, pos: number, text: string, lookbehind: boolean) {
pattern.lastIndex = pos;
const match = pattern.exec(text);
if (match && lookbehind && match[1]) {
@ -426,12 +437,11 @@ function matchPattern(pattern, pos, text, lookbehind) {
* The following hooks will be run:
* 1. `wrap`: On each {@link Token}.
*
* @param {string | Token | import("./token").TokenStream} o The token or token stream to be converted.
* @param {string} language The name of current language.
* @param {Hooks} hooks
* @returns {string} The HTML representation of the token or token stream.
* @param o The token or token stream to be converted.
* @param language The name of current language.
* @returns The HTML representation of the token or token stream.
*/
function stringify(o, language, hooks) {
function stringify(o: string | Token | TokenStream, language: string, hooks: Hooks): string {
if (typeof o === 'string') {
return htmlEncode(o);
}
@ -443,8 +453,7 @@ function stringify(o, language, hooks) {
return s;
}
/** @type {EnvMap["wrap"]} */
const env = {
const env: HookEnvMap['wrap'] = {
type: o.type,
content: stringify(o.content, language, hooks),
tag: 'span',
@ -472,11 +481,7 @@ function stringify(o, language, hooks) {
return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + attributes + '>' + env.content + '</' + env.tag + '>';
}
/**
* @param {import("../types").GrammarToken | RegExp} pattern
* @returns {import("../types").GrammarToken}
*/
function toGrammarToken(pattern) {
function toGrammarToken(pattern: GrammarToken | RegExp): GrammarToken {
if (pattern.exec) {
return { pattern };
} else {
@ -484,12 +489,7 @@ function toGrammarToken(pattern) {
}
}
/**
* @param {Registry} components
* @param {import("../types").Grammar | string | null | undefined} reference
* @returns {import("../types").Grammar | undefined}
*/
function resolve(components, reference) {
function resolve(components: Registry, reference: Grammar | string | null | undefined): Grammar | undefined {
if (reference) {
if (typeof reference === 'string') {
return components.getLanguage(reference);

View File

@ -1,12 +1,13 @@
import { extend } from '../shared/language-util';
import { forEach, kebabToCamelCase } from '../shared/util';
import type { ComponentProto, Grammar } from '../types';
import type { Prism } from './prism';
/**
* @typedef Entry
* @property {import('../types').ComponentProto} proto
* @property {import('../types').Grammar} [evaluatedGrammar]
* @property {() => void} [evaluatedEffect]
*/
interface Entry {
proto: ComponentProto;
evaluatedGrammar?: Grammar;
evaluatedEffect?: () => void;
}
/**
* TODO: docs
@ -14,62 +15,39 @@ import { forEach, kebabToCamelCase } from '../shared/util';
export class Registry {
/**
* A map from the aliases of components to the id of the component with that alias.
*
* @type {Map<string, string>}
* @private
*/
aliasMap = new Map();
private aliasMap = new Map<string, string>();
/**
* A map from the aliases of components to the id of the component with that alias.
*
* @type {Map<string, Entry>}
* @private
*/
entries = new Map();
private entries = new Map<string, Entry>();
/**
* @param {import('./prism').Prism} Prism
*/
constructor(Prism) {
/**
* @private
*/
private Prism: Prism;
constructor(Prism: Prism) {
this.Prism = Prism;
}
/**
* If the given name is a known alias, then the id of the component of the alias will be returned. Otherwise, the
* `name` will be returned as is.
*
* @param {string} name
* @returns {string}
*/
resolveAlias(name) {
resolveAlias(name: string): string {
return this.aliasMap.get(name) ?? name;
}
/**
* Returns whether this registry has a component with the given name or alias.
*
* @param {string} name
* @returns {boolean}
*/
has(name) {
has(name: string): boolean {
return this.entries.has(this.resolveAlias(name));
}
/**
* @param {import('../types').ComponentProto[]} components
*/
add(...components) {
/** @type {Set<string>} */
const added = new Set();
add(...components: ComponentProto[]): void {
const added = new Set<string>();
/**
* @param {import('../types').ComponentProto} proto
*/
const register = (proto) => {
const register = (proto: ComponentProto) => {
const { id } = proto;
if (this.entries.has(id)) {
return;
@ -86,7 +64,7 @@ export class Registry {
// add plugin namespace
if ('plugin' in proto && proto.plugin) {
this.Prism.plugins[kebabToCamelCase(id)] = proto.plugin(/** @type {any} */ (this.Prism));
this.Prism.plugins[kebabToCamelCase(id)] = proto.plugin(this.Prism as never);
}
};
components.forEach(register);
@ -94,23 +72,11 @@ export class Registry {
this.update(added);
}
/**
* @param {ReadonlySet<string>} changed
* @returns {void}
* @private
*/
update(changed) {
/** @type {Map<string, boolean>} */
const updateStatus = new Map();
private update(changed: ReadonlySet<string>): void {
const updateStatus = new Map<string, boolean>();
const idStack: string[] = [];
/** @type {string[]} */
const idStack = [];
/**
* @param {string} id
* @returns {boolean}
*/
const didUpdate = (id) => {
const didUpdate = (id: string): boolean => {
let status = updateStatus.get(id);
if (status !== undefined) {
return status;
@ -144,18 +110,13 @@ export class Registry {
// redo effects
if (entry.proto.effect) {
entry.evaluatedEffect = entry.proto.effect(/** @type {any} */ (this.Prism));
entry.evaluatedEffect = entry.proto.effect(this.Prism as never);
}
updateStatus.set(id, status = true);
return status;
};
/**
* @param {import('../types').ComponentProto} proto
* @returns {boolean}
*/
const shouldRunEffects = (proto) => {
/** @type {boolean} */
const shouldRunEffects = (proto: ComponentProto): boolean => {
let depsChanged = false;
forEach(proto.require, ({ id }) => {
@ -175,11 +136,7 @@ export class Registry {
this.entries.forEach((_, id) => didUpdate(id));
}
/**
* @param {string} id
* @returns {import("../types").Grammar | undefined}
*/
getLanguage(id) {
getLanguage(id: string): Grammar | undefined {
id = this.resolveAlias(id);
const entry = this.entries.get(id);
@ -200,10 +157,7 @@ export class Registry {
return entry.evaluatedGrammar = grammar;
}
/**
* @param {string} id
*/
const required = (id) => {
const required = (id: string) => {
const grammar = this.getLanguage(id);
if (!grammar) {
throw new Error(`The language ${id} was not found.`);

View File

@ -1,89 +0,0 @@
export class Token {
/**
* Creates a new token.
*
* @param {import("../types").TokenName} type See {@link Token#type type}
* @param {string | TokenStream} content See {@link Token#content content}
* @param {import("../types").TokenName | import("../types").TokenName[]} [alias] The alias(es) of the token.
* @param {string} [matchedStr=""] A copy of the full string this token was created from.
* @public
*/
constructor(type, content, alias, matchedStr = '') {
/**
* The type of the token.
*
* This is usually the key of a pattern in a {@link Grammar}.
*
* @type {import("../types").TokenName}
* @see GrammarToken
* @public
*/
this.type = type;
/**
* The strings or tokens contained by this token.
*
* This will be a token stream if the pattern matched also defined an `inside` grammar.
*
* @type {string | TokenStream}
* @public
*/
this.content = content;
/**
* The alias(es) of the token.
*
* @type {undefined | import("../types").TokenName | import("../types").TokenName[]}
* @see GrammarToken
* @public
*/
this.alias = alias;
// Copy of the full string this token was created from
this.length = matchedStr.length;
}
/**
* Adds the given alias to the list of aliases of this token.
*
* @param {import("../types").TokenName} alias
*/
addAlias(alias) {
let aliases = this.alias;
if (!aliases) {
this.alias = aliases = [];
} else if (!Array.isArray(aliases)) {
this.alias = aliases = [aliases];
}
aliases.push(alias);
}
}
/**
* A token stream is an array of strings and {@link Token Token} objects.
*
* Token streams have to fulfill a few properties that are assumed by most functions (mostly internal ones) that process
* them.
*
* 1. No adjacent strings.
* 2. No empty strings.
*
* The only exception here is the token stream that only contains the empty string and nothing else.
*
* @typedef {Array<string | Token>} TokenStream
* @public
*/
/**
* Returns the text content of the given token or token stream.
*
* @param {string | Token | TokenStream} token
* @returns {string}
*/
export function getTextContent(token) {
if (typeof token === 'string') {
return token;
} else if (Array.isArray(token)) {
return token.map(getTextContent).join('');
} else {
return getTextContent(token.content);
}
}

89
src/core/token.ts Normal file
View File

@ -0,0 +1,89 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { Grammar, GrammarToken, TokenName } from '../types';
export class Token {
/**
* The type of the token.
*
* This is usually the key of a pattern in a {@link Grammar}.
*
* @see {@link GrammarToken}
*/
type: TokenName;
/**
* The strings or tokens contained by this token.
*
* This will be a token stream if the pattern matched also defined an `inside` grammar.
*/
content: string | TokenStream;
/**
* The alias(es) of the token.
*
* @see {@link GrammarToken#alias}
*/
alias?: TokenName | TokenName[];
/**
* Length of the full string this token was created from.
*
* Only used internally. The API does not guarantee that this field has any particular value or meaning.
*
* @internal
*/
length: number;
/**
* Creates a new token.
*
* @param type See {@link Token#type}
* @param content See {@link Token#content}
* @param alias The alias(es) of the token.
* @param matchedStr A copy of the full string this token was created from.
* @public
*/
constructor(type: TokenName, content: string | TokenStream, alias?: TokenName | TokenName[], matchedStr = '') {
this.type = type;
this.content = content;
this.alias = alias;
this.length = matchedStr.length;
}
/**
* Adds the given alias to the list of aliases of this token.
*/
addAlias(alias: TokenName): void {
let aliases = this.alias;
if (!aliases) {
this.alias = aliases = [];
} else if (!Array.isArray(aliases)) {
this.alias = aliases = [aliases];
}
aliases.push(alias);
}
}
/**
* A token stream is an array of strings and {@link Token Token} objects.
*
* Token streams have to fulfill a few properties that are assumed by most functions (mostly internal ones) that process
* them.
*
* 1. No adjacent strings.
* 2. No empty strings.
*
* The only exception here is the token stream that only contains the empty string and nothing else.
*/
export type TokenStream = (string | Token)[]
/**
* Returns the text content of the given token or token stream.
*/
export function getTextContent(token: string | Token | TokenStream): string {
if (typeof token === 'string') {
return token;
} else if (Array.isArray(token)) {
return token.map(getTextContent).join('');
} else {
return getTextContent(token.content);
}
}

View File

@ -3,7 +3,7 @@ import { Prism } from './core/prism';
const globalSymbol = Symbol.for('Prism global');
// eslint-disable-next-line no-undef
const namespace = /** @type {Partial<Record<globalSymbol, Prism>>} */ (globalThis);
const namespace = globalThis as Partial<Record<typeof globalSymbol, Prism>>;
const globalPrism = namespace[globalSymbol] ??= new Prism();
/**

View File

@ -1,13 +1,13 @@
import { Autoloader } from './plugins/autoloader/prism-autoloader';
import { CustomClass } from './plugins/custom-class/prism-custom-class';
import { FileHighlight } from './plugins/file-highlight/prism-file-highlight';
import { FilterHighlightAll } from './plugins/filter-highlight-all/prism-filter-highlight-all';
import { JsonpHighlight } from './plugins/jsonp-highlight/prism-jsonp-highlight';
import { LineHighlight } from './plugins/line-highlight/prism-line-highlight';
import { LineNumbers } from './plugins/line-numbers/prism-line-numbers';
import { NormalizeWhitespace } from './plugins/normalize-whitespace/prism-normalize-whitespace';
import { PreviewerCollection } from './plugins/previewers/prism-previewers';
import { Toolbar } from './plugins/toolbar/prism-toolbar';
import type { Autoloader } from './plugins/autoloader/prism-autoloader';
import type { CustomClass } from './plugins/custom-class/prism-custom-class';
import type { FileHighlight } from './plugins/file-highlight/prism-file-highlight';
import type { FilterHighlightAll } from './plugins/filter-highlight-all/prism-filter-highlight-all';
import type { JsonpHighlight } from './plugins/jsonp-highlight/prism-jsonp-highlight';
import type { LineHighlight } from './plugins/line-highlight/prism-line-highlight';
import type { LineNumbers } from './plugins/line-numbers/prism-line-numbers';
import type { NormalizeWhitespace } from './plugins/normalize-whitespace/prism-normalize-whitespace';
import type { PreviewerCollection } from './plugins/previewers/prism-previewers';
import type { Toolbar } from './plugins/toolbar/prism-toolbar';
declare interface KnownPlugins {
autoloader: Autoloader;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'abap'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'abap',
grammar: {
'comment': /^\*.*/m,
@ -48,4 +50,4 @@ export default /** @type {import("../types").LanguageProto<'abap'>} */ ({
}],
'punctuation': /[,.:()]/
}
});
} as LanguageProto<'abap'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'abnf'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'abnf',
grammar() {
const coreRules = '(?:ALPHA|BIT|CHAR|CR|CRLF|CTL|DIGIT|DQUOTE|HEXDIG|HTAB|LF|LWSP|OCTET|SP|VCHAR|WSP)';
@ -52,4 +54,4 @@ export default /** @type {import("../types").LanguageProto<'abnf'>} */ ({
'punctuation': /[()\[\]]/
};
}
});
} as LanguageProto<'abnf'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import javascript from './prism-javascript';
import type { GrammarToken, LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'actionscript'>} */ ({
export default {
id: 'actionscript',
require: javascript,
grammar({ extend }) {
@ -10,7 +11,7 @@ export default /** @type {import("../types").LanguageProto<'actionscript'>} */ (
'operator': /\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<<?|>>?>?|[!=]=?)=?|[~?@]/
});
const className = /** @type {import('../types').GrammarToken} */(actionscript['class-name']);
const className = actionscript['class-name'] as GrammarToken;
className.alias = 'function';
delete actionscript['doc-comment'];
@ -29,4 +30,4 @@ export default /** @type {import("../types").LanguageProto<'actionscript'>} */ (
return actionscript;
}
});
} as LanguageProto<'actionscript'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'ada'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'ada',
grammar: {
'comment': /--.*/,
@ -22,4 +24,4 @@ export default /** @type {import("../types").LanguageProto<'ada'>} */ ({
'char': /'.'/,
'variable': /\b[a-z](?:\w)*\b/i
}
});
} as LanguageProto<'ada'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'agda'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'agda',
grammar: {
'comment': /\{-[\s\S]*?(?:-\}|$)|--.*/,
@ -21,4 +23,4 @@ export default /** @type {import("../types").LanguageProto<'agda'>} */ ({
},
'keyword': /\b(?:Set|abstract|constructor|data|eta-equality|field|forall|hiding|import|in|inductive|infix|infixl|infixr|instance|let|macro|module|mutual|no-eta-equality|open|overlap|pattern|postulate|primitive|private|public|quote|quoteContext|quoteGoal|quoteTerm|record|renaming|rewrite|syntax|tactic|unquote|unquoteDecl|unquoteDef|using|variable|where|with)\b/,
}
});
} as LanguageProto<'agda'>;

View File

@ -1,6 +1,8 @@
import type { LanguageProto } from '../types';
// based on https://github.com/microsoft/AL/blob/master/grammar/alsyntax.tmlanguage
export default /** @type {import("../types").LanguageProto<'al'>} */ ({
export default {
id: 'al',
grammar: {
'comment': /\/\/.*|\/\*[\s\S]*?\*\//,
@ -25,4 +27,4 @@ export default /** @type {import("../types").LanguageProto<'al'>} */ ({
'operator': /\.\.|:[=:]|[-+*/]=?|<>|[<>]=?|=|\b(?:and|div|mod|not|or|xor)\b/i,
'punctuation': /[()\[\]{}:.;,]/
}
});
} as LanguageProto<'al'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'antlr4'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'antlr4',
alias: 'g4',
grammar: {
@ -64,4 +66,4 @@ export default /** @type {import("../types").LanguageProto<'antlr4'>} */ ({
'operator': /\.\.|->|[|~]|[*+?]\??/,
'punctuation': /[;:()=]/
}
});
} as LanguageProto<'antlr4'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'apacheconf'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'apacheconf',
grammar: {
'comment': /#.*/,
@ -47,4 +49,4 @@ export default /** @type {import("../types").LanguageProto<'apacheconf'>} */ ({
'variable': /[$%]\{?(?:\w\.?[-+:]?)+\}?/,
'regex': /\^?.*\$|\^.*\$?/
}
});
} as LanguageProto<'apacheconf'>;

View File

@ -1,7 +1,8 @@
import clike from './prism-clike';
import sql from './prism-sql';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'apex'>} */ ({
export default {
id: 'apex',
require: [clike, sql],
grammar({ getLanguage }) {
@ -9,8 +10,7 @@ export default /** @type {import("../types").LanguageProto<'apex'>} */ ({
const className = /\b(?:(?=[a-z_]\w*\s*[<\[])|(?!<keyword>))[A-Z_]\w*(?:\s*\.\s*[A-Z_]\w*)*\b(?:\s*(?:\[\s*\]|<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>))*/.source
.replace(/<keyword>/g, () => keywords.source);
/** @param {string} pattern */
function insertClassName(pattern) {
function insertClassName(pattern: string) {
return RegExp(pattern.replace(/<CLASS-NAME>/g, () => className), 'i');
}
@ -69,4 +69,4 @@ export default /** @type {import("../types").LanguageProto<'apex'>} */ ({
'punctuation': /[()\[\]{};,.]/
};
}
});
} as LanguageProto<'apex'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'apl'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'apl',
grammar: {
'comment': /(?:⍝|#[! ]).*$/m,
@ -32,4 +34,4 @@ export default /** @type {import("../types").LanguageProto<'apl'>} */ ({
alias: 'builtin'
}
}
});
} as LanguageProto<'apl'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'applescript'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'applescript',
grammar: {
'comment': [
@ -17,4 +19,4 @@ export default /** @type {import("../types").LanguageProto<'applescript'>} */ ({
'class-name': /\b(?:POSIX file|RGB color|alias|application|boolean|centimeters|centimetres|class|constant|cubic centimeters|cubic centimetres|cubic feet|cubic inches|cubic meters|cubic metres|cubic yards|date|degrees Celsius|degrees Fahrenheit|degrees Kelvin|feet|file|gallons|grams|inches|integer|kilograms|kilometers|kilometres|list|liters|litres|meters|metres|miles|number|ounces|pounds|quarts|real|record|reference|script|square feet|square kilometers|square kilometres|square meters|square metres|square miles|square yards|text|yards)\b/,
'punctuation': /[{}():,¬«»《》]/
}
});
} as LanguageProto<'applescript'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'aql'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'aql',
grammar: {
'comment': /\/\/.*|\/\*[\s\S]*?\*\//,
@ -49,4 +51,4 @@ export default /** @type {import("../types").LanguageProto<'aql'>} */ ({
'operator': /\*{2,}|[=!]~|[!=<>]=?|&&|\|\||[-+*/%]/,
'punctuation': /::|[?.:,;()[\]{}]/
}
});
} as LanguageProto<'aql'>;

View File

@ -1,6 +1,7 @@
import cpp from './prism-cpp';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'arduino'>} */ ({
export default {
id: 'arduino',
require: cpp,
alias: 'ino',
@ -11,4 +12,4 @@ export default /** @type {import("../types").LanguageProto<'arduino'>} */ ({
'builtin': /\b(?:Audio|BSSID|Bridge|Client|Console|EEPROM|Esplora|EsploraTFT|Ethernet|EthernetClient|EthernetServer|EthernetUDP|File|FileIO|FileSystem|Firmata|GPRS|GSM|GSMBand|GSMClient|GSMModem|GSMPIN|GSMScanner|GSMServer|GSMVoiceCall|GSM_SMS|HttpClient|IPAddress|IRread|Keyboard|KeyboardController|LiquidCrystal|LiquidCrystal_I2C|Mailbox|Mouse|MouseController|PImage|Process|RSSI|RobotControl|RobotMotor|SD|SPI|SSID|Scheduler|Serial|Server|Servo|SoftwareSerial|Stepper|Stream|TFT|Task|USBHost|WiFi|WiFiClient|WiFiServer|WiFiUDP|Wire|YunClient|YunServer|abs|addParameter|analogRead|analogReadResolution|analogReference|analogWrite|analogWriteResolution|answerCall|attach|attachGPRS|attachInterrupt|attached|autoscroll|available|background|beep|begin|beginPacket|beginSD|beginSMS|beginSpeaker|beginTFT|beginTransmission|beginWrite|bit|bitClear|bitRead|bitSet|bitWrite|blink|blinkVersion|buffer|changePIN|checkPIN|checkPUK|checkReg|circle|cityNameRead|cityNameWrite|clear|clearScreen|click|close|compassRead|config|connect|connected|constrain|cos|countryNameRead|countryNameWrite|createChar|cursor|debugPrint|delay|delayMicroseconds|detach|detachInterrupt|digitalRead|digitalWrite|disconnect|display|displayLogos|drawBMP|drawCompass|encryptionType|end|endPacket|endSMS|endTransmission|endWrite|exists|exitValue|fill|find|findUntil|flush|gatewayIP|get|getAsynchronously|getBand|getButton|getCurrentCarrier|getIMEI|getKey|getModifiers|getOemKey|getPINUsed|getResult|getSignalStrength|getSocket|getVoiceCallStatus|getXChange|getYChange|hangCall|height|highByte|home|image|interrupts|isActionDone|isDirectory|isListening|isPIN|isPressed|isValid|keyPressed|keyReleased|keyboardRead|knobRead|leftToRight|line|lineFollowConfig|listen|listenOnLocalhost|loadImage|localIP|lowByte|macAddress|maintain|map|max|messageAvailable|micros|millis|min|mkdir|motorsStop|motorsWrite|mouseDragged|mouseMoved|mousePressed|mouseReleased|move|noAutoscroll|noBlink|noBuffer|noCursor|noDisplay|noFill|noInterrupts|noListenOnLocalhost|noStroke|noTone|onReceive|onRequest|open|openNextFile|overflow|parseCommand|parseFloat|parseInt|parsePacket|pauseMode|peek|pinMode|playFile|playMelody|point|pointTo|position|pow|prepare|press|print|printFirmwareVersion|printVersion|println|process|processInput|pulseIn|put|random|randomSeed|read|readAccelerometer|readBlue|readButton|readBytes|readBytesUntil|readGreen|readJoystickButton|readJoystickSwitch|readJoystickX|readJoystickY|readLightSensor|readMessage|readMicrophone|readNetworks|readRed|readSlider|readString|readStringUntil|readTemperature|ready|rect|release|releaseAll|remoteIP|remoteNumber|remotePort|remove|requestFrom|retrieveCallingNumber|rewindDirectory|rightToLeft|rmdir|robotNameRead|robotNameWrite|run|runAsynchronously|runShellCommand|runShellCommandAsynchronously|running|scanNetworks|scrollDisplayLeft|scrollDisplayRight|seek|sendAnalog|sendDigitalPortPair|sendDigitalPorts|sendString|sendSysex|serialEvent|setBand|setBitOrder|setClockDivider|setCursor|setDNS|setDataMode|setFirmwareVersion|setMode|setPINUsed|setSpeed|setTextSize|setTimeout|shiftIn|shiftOut|shutdown|sin|size|sqrt|startLoop|step|stop|stroke|subnetMask|switchPIN|tan|tempoWrite|text|tone|transfer|tuneWrite|turn|updateIR|userNameRead|userNameWrite|voiceCall|waitContinue|width|write|writeBlue|writeGreen|writeJSON|writeMessage|writeMicroseconds|writeRGB|writeRed|yield)\b/
});
}
});
} as LanguageProto<'arduino'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'arff'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'arff',
grammar: {
'comment': /%.*/,
@ -10,4 +12,4 @@ export default /** @type {import("../types").LanguageProto<'arff'>} */ ({
'number': /\b\d+(?:\.\d+)?\b/,
'punctuation': /[{},]/
}
});
} as LanguageProto<'arff'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'armasm'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'armasm',
alias: 'arm-asm',
grammar: {
@ -48,4 +50,4 @@ export default /** @type {import("../types").LanguageProto<'armasm'>} */ ({
'operator': /<>|<<|>>|&&|\|\||[=!<>/]=?|[+\-*%#?&|^]|:[A-Z]+:/,
'punctuation': /[()[\],]/
}
});
} as LanguageProto<'armasm'>;

View File

@ -1,8 +1,6 @@
/**
* @param {string} lang
* @param {string} [pattern]
*/
function createLanguageString(lang, pattern) {
import type { LanguageProto } from '../types';
function createLanguageString(lang: string, pattern?: string) {
return {
pattern: RegExp(/\{!/.source + '(?:' + (pattern || lang) + ')' + /$[\s\S]*\}/.source, 'm'),
greedy: true,
@ -18,7 +16,7 @@ function createLanguageString(lang, pattern) {
};
}
export default /** @type {import("../types").LanguageProto<'arturo'>} */ ({
export default {
id: 'arturo',
alias: 'art',
grammar: {
@ -102,4 +100,4 @@ export default /** @type {import("../types").LanguageProto<'arturo'>} */ ({
pattern: /\b(?:false|maybe|true)\b/
}
}
});
} as LanguageProto<'arturo'>;

View File

@ -1,10 +1,11 @@
import { rest } from '../shared/symbols';
import type { Grammar, GrammarToken, LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'asciidoc'>} */ ({
export default {
id: 'asciidoc',
alias: 'adoc',
grammar() {
const placeholder = /** @type {import('../types').GrammarToken["inside"]} */ (null);
const placeholder = null as GrammarToken['inside'];
const attributes = {
pattern: /(^[ \t]*)\[(?!\[)(?:(["'$`])(?:(?!\2)[^\\]|\\.)*\2|\[(?:[^\[\]\\]|\\.)*\]|[^\[\]\\"'$`]|\\.)*\]/m,
@ -206,12 +207,8 @@ export default /** @type {import("../types").LanguageProto<'asciidoc'>} */ ({
// Allow some nesting. There is no recursion though, so cloning should not be needed.
/**
* @param {(keyof typeof asciidoc)[]} keys
*/
function copyFromAsciiDoc(...keys) {
/** @type {import('../types').Grammar} */
const o = {};
function copyFromAsciiDoc(...keys: (keyof typeof asciidoc)[]) {
const o: Grammar = {};
for (const key of keys) {
o[key] = asciidoc[key];
}
@ -240,4 +237,4 @@ export default /** @type {import("../types").LanguageProto<'asciidoc'>} */ ({
}
});
}
});
} as LanguageProto<'asciidoc'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'asm6502'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'asm6502',
grammar: {
'comment': /;.*/,
@ -29,4 +31,4 @@ export default /** @type {import("../types").LanguageProto<'asm6502'>} */ ({
},
'punctuation': /[(),:]/
}
});
} as LanguageProto<'asm6502'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'asmatmel'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'asmatmel',
grammar: {
'comment': {
@ -43,4 +45,4 @@ export default /** @type {import("../types").LanguageProto<'asmatmel'>} */ ({
'operator': />>=?|<<=?|&[&=]?|\|[\|=]?|[-+*/%^!=<>?]=?/,
'punctuation': /[(),:]/
}
});
} as LanguageProto<'asmatmel'>;

View File

@ -2,13 +2,13 @@ import { insertBefore } from '../shared/language-util';
import { rest } from '../shared/symbols';
import csharp from './prism-csharp';
import markup from './prism-markup';
import type { Grammar, GrammarToken, LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'aspnet'>} */ ({
export default {
id: 'aspnet',
require: [markup, csharp],
grammar({ extend }) {
/** @type {import('../types').Grammar} */
const pageDirectiveInside = {
const pageDirectiveInside: Grammar = {
'page-directive': {
pattern: /<%\s*@\s*(?:Assembly|Control|Implements|Import|Master(?:Type)?|OutputCache|Page|PreviousPageType|Reference|Register)?|%>/i,
alias: 'tag'
@ -34,7 +34,7 @@ export default /** @type {import("../types").LanguageProto<'aspnet'>} */ ({
}
});
const tag = /** @type {import('../types').GrammarToken & { inside: { 'attr-value': { inside: import('../types').Grammar } } }} */ (aspnet['tag']);
const tag = aspnet['tag'] as GrammarToken & { inside: { 'attr-value': { inside: Grammar } } };
pageDirectiveInside[rest] = tag.inside;
// Regexp copied from prism-markup, with a negative look-ahead added
@ -64,4 +64,4 @@ export default /** @type {import("../types").LanguageProto<'aspnet'>} */ ({
return aspnet;
}
});
} as LanguageProto<'aspnet'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'autohotkey'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'autohotkey',
grammar() {
// NOTES - follows first-first highlight method, block is locked after highlight, different from SyntaxHl
@ -46,4 +48,4 @@ export default /** @type {import("../types").LanguageProto<'autohotkey'>} */ ({
'punctuation': /[{}[\]():,]/
};
}
});
} as LanguageProto<'autohotkey'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'autoit'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'autoit',
grammar: {
'comment': [
@ -34,4 +36,4 @@ export default /** @type {import("../types").LanguageProto<'autoit'>} */ ({
'operator': /<[=>]?|[-+*\/=&>]=?|[?^]|\b(?:And|Not|Or)\b/i,
'punctuation': /[\[\]().,:]/
}
});
} as LanguageProto<'autoit'>;

View File

@ -1,25 +1,17 @@
export default /** @type {import("../types").LanguageProto<'avisynth'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'avisynth',
alias: 'avs',
grammar() {
// http://avisynth.nl/index.php/The_full_AviSynth_grammar
/**
* @param {string} pattern
* @param {string[]} replacements
*/
function replace(pattern, replacements) {
function replace(pattern: string, replacements: string[]) {
return pattern.replace(/<<(\d+)>>/g, (m, index) => {
return replacements[+index];
});
}
/**
* @param {string} pattern
* @param {string[]} replacements
* @param {string} [flags]
*/
function re(pattern, replacements, flags) {
function re(pattern: string, replacements: string[], flags?: string) {
return RegExp(replace(pattern, replacements), flags || '');
}
@ -195,4 +187,4 @@ export default /** @type {import("../types").LanguageProto<'avisynth'>} */ ({
'punctuation': /[{}\[\]();,.]/
};
}
});
} as LanguageProto<'avisynth'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'avro-idl'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'avro-idl',
alias: 'avdl',
grammar() {
@ -51,4 +53,4 @@ export default /** @type {import("../types").LanguageProto<'avro-idl'>} */ ({
'punctuation': /[()\[\]{}<>.:,;-]/
};
}
});
} as LanguageProto<'avro-idl'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'awk'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'awk',
alias: 'gawk',
grammar: {
@ -31,4 +33,4 @@ export default /** @type {import("../types").LanguageProto<'awk'>} */ ({
'operator': /--|\+\+|!?~|>&|>>|<<|(?:\*\*|[<>!=+\-*/%^])=?|&&|\|[|&]|[?:]/,
'punctuation': /[()[\]{},;]/
}
});
} as LanguageProto<'awk'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'bash'>} */ ({
import type { Grammar, LanguageProto } from '../types';
export default {
id: 'bash',
alias: ['sh', 'shell'],
grammar() {
@ -15,8 +17,7 @@ export default /** @type {import("../types").LanguageProto<'bash'>} */ ({
inside: 'bash'
};
/** @type {import("../types").Grammar} */
const commandSubstitutionInside = {
const commandSubstitutionInside: Grammar = {
'variable': /^\$\(|^`|\)$|`$/
};
@ -212,8 +213,7 @@ export default /** @type {import("../types").LanguageProto<'bash'>} */ ({
};
/* Patterns in command substitution. */
/** @type {(keyof typeof bash)[]} */
const toBeCopied = [
const toBeCopied: (keyof typeof bash)[] = [
'comment',
'function-name',
'for-or-select',
@ -236,4 +236,4 @@ export default /** @type {import("../types").LanguageProto<'bash'>} */ ({
return bash;
}
});
} as LanguageProto<'bash'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'basic'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'basic',
grammar: {
'comment': {
@ -17,4 +19,4 @@ export default /** @type {import("../types").LanguageProto<'basic'>} */ ({
'operator': /<[=>]?|>=?|[+\-*\/^=&]|\b(?:AND|EQV|IMP|NOT|OR|XOR)\b/i,
'punctuation': /[,;:()]/
}
});
} as LanguageProto<'basic'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'batch'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'batch',
grammar() {
const variable = /%%?[~:\w]+%?|!\S+!/;
@ -99,4 +101,4 @@ export default /** @type {import("../types").LanguageProto<'batch'>} */ ({
'punctuation': /[()']/
};
}
});
} as LanguageProto<'batch'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'bbcode'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'bbcode',
alias: 'shortcode',
grammar: {
@ -28,4 +30,4 @@ export default /** @type {import("../types").LanguageProto<'bbcode'>} */ ({
}
}
}
});
} as LanguageProto<'bbcode'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'bbj'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'bbj',
grammar: {
'comment': {
@ -17,4 +19,4 @@ export default /** @type {import("../types").LanguageProto<'bbj'>} */ ({
'operator': /<[=>]?|>=?|[+\-*\/^=&]|\b(?:and|not|or|xor)\b/i,
'punctuation': /[.,;:()]/
}
});
} as LanguageProto<'bbj'>;

View File

@ -1,6 +1,8 @@
import type { LanguageProto } from '../types';
// based loosely upon: https://github.com/Azure/bicep/blob/main/src/textmate/bicep.tmlanguage
export default /** @type {import("../types").LanguageProto<'bicep'>} */ ({
export default {
id: 'bicep',
grammar: {
'comment': [
@ -77,4 +79,4 @@ export default /** @type {import("../types").LanguageProto<'bicep'>} */ ({
'operator': /--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/,
'punctuation': /[{}[\];(),.:]/,
}
});
} as LanguageProto<'bicep'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import clike from './prism-clike';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'birb'>} */ ({
export default {
id: 'birb',
require: clike,
grammar({ extend }) {
@ -31,4 +32,4 @@ export default /** @type {import("../types").LanguageProto<'birb'>} */ ({
return birb;
}
});
} as LanguageProto<'birb'>;

View File

@ -1,8 +1,9 @@
import { insertBefore } from '../shared/language-util';
import { rest } from '../shared/symbols';
import c from './prism-c';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'bison'>} */ ({
export default {
id: 'bison',
require: c,
grammar({ extend, getLanguage }) {
@ -49,4 +50,4 @@ export default /** @type {import("../types").LanguageProto<'bison'>} */ ({
return bison;
}
});
} as LanguageProto<'bison'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'bnf'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'bnf',
alias: 'rbnf',
grammar: {
@ -20,4 +22,4 @@ export default /** @type {import("../types").LanguageProto<'bnf'>} */ ({
},
'operator': /::=|[|()[\]{}*+?]|\.{3}/
}
});
} as LanguageProto<'bnf'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'bqn'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'bqn',
grammar: {
'shebang': {
@ -63,4 +65,4 @@ export default /** @type {import("../types").LanguageProto<'bqn'>} */ ({
},
'punctuation': /[←⇐↩(){}⟨⟩[\]‿·⋄,.;:?]/
}
});
} as LanguageProto<'bqn'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'brainfuck'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'brainfuck',
grammar: {
'pointer': {
@ -20,4 +22,4 @@ export default /** @type {import("../types").LanguageProto<'brainfuck'>} */ ({
'operator': /[.,]/,
'comment': /\S+/
}
});
} as LanguageProto<'brainfuck'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'brightscript'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'brightscript',
grammar: {
'comment': /(?:\brem|').*/i,
@ -42,4 +44,4 @@ export default /** @type {import("../types").LanguageProto<'brightscript'>} */ (
'punctuation': /[.,;()[\]{}]/,
'constant': /\b(?:LINE_NUM)\b/i
}
});
} as LanguageProto<'brightscript'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'bro'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'bro',
grammar: {
@ -37,4 +39,4 @@ export default /** @type {import("../types").LanguageProto<'bro'>} */ ({
'punctuation': /[{}[\];(),.:]/
}
});
} as LanguageProto<'bro'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'bsl'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'bsl',
alias: 'oscript',
grammar() {
@ -76,4 +78,4 @@ export default /** @type {import("../types").LanguageProto<'bsl'>} */ ({
]
};
}
});
} as LanguageProto<'bsl'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import clike from './prism-clike';
import type { GrammarToken, LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'c'>} */ ({
export default {
id: 'c',
require: clike,
optional: 'opencl-extensions',
@ -49,7 +50,7 @@ export default /** @type {import("../types").LanguageProto<'c'>} */ ({
pattern: /^(#\s*include\s*)<[^>]+>/,
lookbehind: true
},
/** @type {import('../types').GrammarToken} */(c['string'])
c['string'] as GrammarToken
],
'char': c['char'],
'comment': c['comment'],
@ -96,4 +97,4 @@ export default /** @type {import("../types").LanguageProto<'c'>} */ ({
return c;
}
});
} as LanguageProto<'c'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import clike from './prism-clike';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'cfscript'>} */ ({
export default {
id: 'cfscript',
require: clike,
alias: 'cfc',
@ -52,4 +53,4 @@ export default /** @type {import("../types").LanguageProto<'cfscript'>} */ ({
return cfscript;
}
});
} as LanguageProto<'cfscript'>;

View File

@ -2,8 +2,9 @@ import { insertBefore } from '../shared/language-util';
import { toArray } from '../shared/util';
import clike from './prism-clike';
import cpp from './prism-cpp';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'chaiscript'>} */ ({
export default {
id: 'chaiscript',
require: [clike, cpp],
grammar({ extend, getLanguage }) {
@ -72,4 +73,4 @@ export default /** @type {import("../types").LanguageProto<'chaiscript'>} */ ({
return chaiscript;
}
});
} as LanguageProto<'chaiscript'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'cil'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'cil',
grammar: {
'comment': /\/\/.*/,
@ -27,4 +29,4 @@ export default /** @type {import("../types").LanguageProto<'cil'>} */ ({
'punctuation': /[{}[\];(),:=]|IL_[0-9A-Za-z]+/
}
});
} as LanguageProto<'cil'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import c from './prism-c';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'cilkc'>} */ ({
export default {
id: 'cilkc',
require: c,
alias: 'cilk-c',
@ -15,4 +16,4 @@ export default /** @type {import("../types").LanguageProto<'cilkc'>} */ ({
});
return cilkc;
}
});
} as LanguageProto<'cilkc'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import cpp from './prism-cpp';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'cilkcpp'>} */ ({
export default {
id: 'cilkcpp',
require: cpp,
alias: ['cilk-cpp', 'cilk'],
@ -15,4 +16,4 @@ export default /** @type {import("../types").LanguageProto<'cilkcpp'>} */ ({
});
return cilkcpp;
}
});
} as LanguageProto<'cilkcpp'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'clike'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'clike',
grammar: {
'comment': [
@ -31,4 +33,4 @@ export default /** @type {import("../types").LanguageProto<'clike'>} */ ({
'operator': /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,
'punctuation': /[{}[\];(),.:]/
}
});
} as LanguageProto<'clike'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'clojure'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'clojure',
grammar() {
// Copied from https://github.com/jeluard/prism-clojure
@ -33,4 +35,4 @@ export default /** @type {import("../types").LanguageProto<'clojure'>} */ ({
'punctuation': /[{}\[\](),]/
};
}
});
} as LanguageProto<'clojure'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'cmake'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'cmake',
grammar: {
'comment': /#.*/,
@ -29,4 +31,4 @@ export default /** @type {import("../types").LanguageProto<'cmake'>} */ ({
'function': /\b[a-z_]\w*(?=\s*\()\b/i,
'punctuation': /[()>}]|\$[<{]/
}
});
} as LanguageProto<'cmake'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'cobol'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'cobol',
grammar: {
'comment': {
@ -53,4 +55,4 @@ export default /** @type {import("../types").LanguageProto<'cobol'>} */ ({
],
'punctuation': /[.:,()]/
}
});
} as LanguageProto<'cobol'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import javascript from './prism-javascript';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'coffeescript'>} */ ({
export default {
id: 'coffeescript',
require: javascript,
alias: 'coffee',
@ -101,4 +102,4 @@ export default /** @type {import("../types").LanguageProto<'coffeescript'>} */ (
return coffeescript;
}
});
} as LanguageProto<'coffeescript'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'concurnas'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'concurnas',
alias: 'conc',
grammar: {
@ -57,4 +59,4 @@ export default /** @type {import("../types").LanguageProto<'concurnas'>} */ ({
alias: 'builtin'
}
}
});
} as LanguageProto<'concurnas'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'cooklang'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'cooklang',
grammar() {
// see https://github.com/cooklang/spec/blob/main/EBNF.md
@ -145,4 +147,4 @@ export default /** @type {import("../types").LanguageProto<'cooklang'>} */ ({
}
};
}
});
} as LanguageProto<'cooklang'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'coq'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'coq',
grammar() {
// https://github.com/coq/coq
@ -52,4 +54,4 @@ export default /** @type {import("../types").LanguageProto<'coq'>} */ ({
'punctuation': /\.\(|`\(|@\{|`\{|\{\||\[=|:>|[:.,;(){}\[\]]/
};
}
});
} as LanguageProto<'coq'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import c from './prism-c';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'cpp'>} */ ({
export default {
id: 'cpp',
require: c,
optional: 'opencl-extensions',
@ -111,4 +112,4 @@ export default /** @type {import("../types").LanguageProto<'cpp'>} */ ({
return cpp;
}
});
} as LanguageProto<'cpp'>;

View File

@ -1,8 +1,9 @@
import { insertBefore } from '../shared/language-util';
import { toArray } from '../shared/util';
import ruby from './prism-ruby';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'crystal'>} */ ({
export default {
id: 'crystal',
require: ruby,
grammar({ extend, getLanguage }) {
@ -64,4 +65,4 @@ export default /** @type {import("../types").LanguageProto<'crystal'>} */ ({
return crystal;
}
});
} as LanguageProto<'crystal'>;

View File

@ -1,46 +1,37 @@
import { insertBefore } from '../shared/language-util';
import clike from './prism-clike';
import type { LanguageProto } from '../types';
/**
* Replaces all placeholders "<<n>>" of given pattern with the n-th replacement (zero based).
*
* Note: This is a simple text based replacement. Be careful when using backreferences!
*
* @param {string} pattern the given pattern.
* @param {string[]} replacements a list of replacement which can be inserted into the given pattern.
* @returns {string} the pattern with all placeholders replaced with their corresponding replacements.
* @param pattern the given pattern.
* @param replacements a list of replacement which can be inserted into the given pattern.
* @returns the pattern with all placeholders replaced with their corresponding replacements.
* @example replace(/a<<0>>a/.source, [/b+/.source]) === /a(?:b+)a/.source
*/
function replace(pattern, replacements) {
function replace(pattern: string, replacements: string[]) {
return pattern.replace(/<<(\d+)>>/g, (m, index) => {
return '(?:' + replacements[+index] + ')';
});
}
/**
* @param {string} pattern
* @param {string[]} replacements
* @param {string} [flags]
* @returns {RegExp}
*/
function re(pattern, replacements, flags) {
function re(pattern: string, replacements: string[], flags?: string) {
return RegExp(replace(pattern, replacements), flags || '');
}
/**
* Creates a nested pattern where all occurrences of the string `<<self>>` are replaced with the pattern itself.
*
* @param {string} pattern
* @param {number} depthLog2
* @returns {string}
*/
function nested(pattern, depthLog2) {
function nested(pattern: string, depthLog2: number) {
for (let i = 0; i < depthLog2; i++) {
pattern = pattern.replace(/<<self>>/g, () => '(?:' + pattern + ')');
}
return pattern.replace(/<<self>>/g, '[^\\s\\S]');
}
export default /** @type {import("../types").LanguageProto<'csharp'>} */ ({
export default {
id: 'csharp',
require: clike,
optional: 'xml-doc',
@ -60,10 +51,7 @@ export default /** @type {import("../types").LanguageProto<'csharp'>} */ ({
};
// keywords
/**
* @param {string} words
*/
function keywordsToPattern(words) {
function keywordsToPattern(words: string) {
return '\\b(?:' + words.trim().replace(/ /g, '|') + ')\\b';
}
const typeDeclarationKeywords = keywordsToPattern(keywordKinds.typeDeclaration);
@ -325,11 +313,7 @@ export default /** @type {import("../types").LanguageProto<'csharp'>} */ ({
const sInterpolationRound = nested(replace(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<<self>>*\)/.source, [regularStringOrCharacter]), 2);
const sInterpolation = replace(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source, [sInterpolationRound, formatString]);
/**
* @param {string} interpolation
* @param {string} interpolationRound
*/
function createInterpolationInside(interpolation, interpolationRound) {
function createInterpolationInside(interpolation: string, interpolationRound: string) {
return {
'interpolation': {
pattern: re(/((?:^|[^{])(?:\{\{)*)<<0>>/.source, [interpolation]),
@ -381,4 +365,4 @@ export default /** @type {import("../types").LanguageProto<'csharp'>} */ ({
return csharp;
}
});
} as LanguageProto<'csharp'>;

View File

@ -1,8 +1,9 @@
import { insertBefore } from '../shared/language-util';
import csharp from './prism-csharp';
import markup from './prism-markup';
import type { Grammar, GrammarToken, LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'cshtml'>} */ ({
export default {
id: 'cshtml',
require: [markup, csharp],
alias: 'razor',
@ -14,18 +15,14 @@ export default /** @type {import("../types").LanguageProto<'cshtml'>} */ ({
const commentLike = /\/(?![/*])|\/\/.*[\r\n]|\/\*[^*]*(?:\*(?!\/)[^*]*)*\*\//.source;
const stringLike =
/@(?!")|"(?:[^\r\n\\"]|\\.)*"|@"(?:[^\\"]|""|\\[\s\S])*"(?!")/.source +
'|' +
/'(?:(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'|(?=[^\\](?!')))/.source;
/@(?!")|"(?:[^\r\n\\"]|\\.)*"|@"(?:[^\\"]|""|\\[\s\S])*"(?!")/.source +
'|' +
/'(?:(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'|(?=[^\\](?!')))/.source;
/**
* Creates a nested pattern where all occurrences of the string `<<self>>` are replaced with the pattern itself.
*
* @param {string} pattern
* @param {number} depthLog2
* @returns {string}
*/
function nested(pattern, depthLog2) {
function nested(pattern: string, depthLog2: number) {
for (let i = 0; i < depthLog2; i++) {
pattern = pattern.replace(/<self>/g, () => '(?:' + pattern + ')');
}
@ -41,10 +38,10 @@ export default /** @type {import("../types").LanguageProto<'cshtml'>} */ ({
const angle = nested(/<(?:[^<>'"@/]|<comment>|<self>)*>/.source, 1);
const inlineCs = /@/.source +
/(?:await\b\s*)?/.source +
'(?:' + /(?!await\b)\w+\b/.source + '|' + round + ')' +
'(?:' + /[?!]?\.\w+\b/.source + '|' + '(?:' + angle + ')?' + round + '|' + square + ')*' +
/(?![?!\.(\[]|<(?!\/))/.source;
/(?:await\b\s*)?/.source +
'(?:' + /(?!await\b)\w+\b/.source + '|' + round + ')' +
'(?:' + /[?!]?\.\w+\b/.source + '|' + '(?:' + angle + ')?' + round + '|' + square + ')*' +
/(?![?!\.(\[]|<(?!\/))/.source;
// Note about the above bracket patterns:
// They all ignore HTML expressions that might be in the C# code. This is a problem because HTML (like strings and
@ -60,51 +57,51 @@ export default /** @type {import("../types").LanguageProto<'cshtml'>} */ ({
const tagAttrInlineCs = /@(?![\w()])/.source + '|' + inlineCs;
const tagAttrValue = '(?:' +
/"[^"@]*"|'[^'@]*'|[^\s'"@>=]+(?=[\s>])/.source +
'|' +
'["\'][^"\'@]*(?:(?:' + tagAttrInlineCs + ')[^"\'@]*)+["\']' +
')';
/"[^"@]*"|'[^'@]*'|[^\s'"@>=]+(?=[\s>])/.source +
'|' +
'["\'][^"\'@]*(?:(?:' + tagAttrInlineCs + ')[^"\'@]*)+["\']' +
')';
const tagAttrs = /(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*<tagAttrValue>|(?=[\s/>])))+)?/.source.replace(/<tagAttrValue>/, tagAttrValue);
const tagContent = /(?!\d)[^\s>\/=$<%]+/.source + tagAttrs + /\s*\/?>/.source;
const tagRegion =
/\B@?/.source +
'(?:' +
/<([a-zA-Z][\w:]*)/.source + tagAttrs + /\s*>/.source +
'(?:' +
(
/[^<]/.source +
'|' +
// all tags that are not the start tag
// eslint-disable-next-line regexp/strict
/<\/?(?!\1\b)/.source + tagContent +
'|' +
// nested start tag
nested(
// eslint-disable-next-line regexp/strict
/<\1/.source + tagAttrs + /\s*>/.source +
'(?:' +
(
/[^<]/.source +
'|' +
// all tags that are not the start tag
// eslint-disable-next-line regexp/strict
/<\/?(?!\1\b)/.source + tagContent +
'|' +
'<self>'
) +
')*' +
// eslint-disable-next-line regexp/strict
/<\/\1\s*>/.source,
2
)
) +
')*' +
// eslint-disable-next-line regexp/strict
/<\/\1\s*>/.source +
/\B@?/.source +
'(?:' +
/<([a-zA-Z][\w:]*)/.source + tagAttrs + /\s*>/.source +
'(?:' +
(
/[^<]/.source +
'|' +
/</.source + tagContent +
')';
// all tags that are not the start tag
// eslint-disable-next-line regexp/strict
/<\/?(?!\1\b)/.source + tagContent +
'|' +
// nested start tag
nested(
// eslint-disable-next-line regexp/strict
/<\1/.source + tagAttrs + /\s*>/.source +
'(?:' +
(
/[^<]/.source +
'|' +
// all tags that are not the start tag
// eslint-disable-next-line regexp/strict
/<\/?(?!\1\b)/.source + tagContent +
'|' +
'<self>'
) +
')*' +
// eslint-disable-next-line regexp/strict
/<\/\1\s*>/.source,
2
)
) +
')*' +
// eslint-disable-next-line regexp/strict
/<\/\1\s*>/.source +
'|' +
/</.source + tagContent +
')';
// Now for the actual language definition(s):
//
@ -143,12 +140,11 @@ export default /** @type {import("../types").LanguageProto<'cshtml'>} */ ({
}
};
const tag = /** @type {import('../types').GrammarToken} */ (cshtml.tag);
const tag = cshtml.tag as GrammarToken;
tag.pattern = RegExp(/<\/?/.source + tagContent);
// @ts-ignore
tag.inside['attr-value'].pattern = RegExp(/=\s*/.source + tagAttrValue);
// @ts-ignore
insertBefore(tag.inside['attr-value'].inside, 'punctuation', { 'value': inlineValue });
const attrValue = (tag.inside as Grammar)['attr-value'] as GrammarToken;
attrValue.pattern = RegExp(/=\s*/.source + tagAttrValue);
insertBefore(attrValue.inside as Grammar, 'punctuation', { 'value': inlineValue });
insertBefore(cshtml, 'prolog', {
'razor-comment': {
@ -160,24 +156,24 @@ export default /** @type {import("../types").LanguageProto<'cshtml'>} */ ({
'block': {
pattern: RegExp(
/(^|[^@])@/.source +
'(?:' +
[
// @{ ... }
curly,
// @code{ ... }
/(?:code|functions)\s*/.source + curly,
// @for (...) { ... }
/(?:for|foreach|lock|switch|using|while)\s*/.source + round + /\s*/.source + curly,
// @do { ... } while (...);
/do\s*/.source + curly + /\s*while\s*/.source + round + /(?:\s*;)?/.source,
// @try { ... } catch (...) { ... } finally { ... }
/try\s*/.source + curly + /\s*catch\s*/.source + round + /\s*/.source + curly + /\s*finally\s*/.source + curly,
// @if (...) {...} else if (...) {...} else {...}
/if\s*/.source + round + /\s*/.source + curly + '(?:' + /\s*else/.source + '(?:' + /\s+if\s*/.source + round + ')?' + /\s*/.source + curly + ')*',
// @helper Ident(params) { ... }
/helper\s+\w+\s*/.source + round + /\s*/.source + curly,
].join('|') +
')'
'(?:' +
[
// @{ ... }
curly,
// @code{ ... }
/(?:code|functions)\s*/.source + curly,
// @for (...) { ... }
/(?:for|foreach|lock|switch|using|while)\s*/.source + round + /\s*/.source + curly,
// @do { ... } while (...);
/do\s*/.source + curly + /\s*while\s*/.source + round + /(?:\s*;)?/.source,
// @try { ... } catch (...) { ... } finally { ... }
/try\s*/.source + curly + /\s*catch\s*/.source + round + /\s*/.source + curly + /\s*finally\s*/.source + curly,
// @if (...) {...} else if (...) {...} else {...}
/if\s*/.source + round + /\s*/.source + curly + '(?:' + /\s*else/.source + '(?:' + /\s+if\s*/.source + round + ')?' + /\s*/.source + curly + ')*',
// @helper Ident(params) { ... }
/helper\s+\w+\s*/.source + round + /\s*/.source + curly,
].join('|') +
')'
),
lookbehind: true,
greedy: true,
@ -208,4 +204,4 @@ export default /** @type {import("../types").LanguageProto<'cshtml'>} */ ({
return cshtml;
}
});
} as LanguageProto<'cshtml'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'csp'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'csp',
grammar() {
/**
@ -13,11 +15,7 @@ export default /** @type {import("../types").LanguageProto<'csp'>} */ ({
*/
/**
* @param {string} source
* @returns {RegExp}
*/
function value(source) {
function value(source: string) {
return RegExp(/([ \t])/.source + '(?:' + source + ')' + /(?=[\s;]|$)/.source, 'i');
}
@ -49,10 +47,10 @@ export default /** @type {import("../types").LanguageProto<'csp'>} */ ({
'host': {
pattern: value(
/[a-z][a-z0-9.+-]*:\/\/[^\s;,']*/.source +
'|' +
/\*[^\s;,']*/.source +
'|' +
/[a-z0-9-]+(?:\.[a-z0-9-]+)+(?::[\d*]+)?(?:\/[^\s;,']*)?/.source
'|' +
/\*[^\s;,']*/.source +
'|' +
/[a-z0-9-]+(?:\.[a-z0-9-]+)+(?::[\d*]+)?(?:\/[^\s;,']*)?/.source
),
lookbehind: true,
alias: 'url',
@ -75,4 +73,4 @@ export default /** @type {import("../types").LanguageProto<'csp'>} */ ({
'punctuation': /;/
};
}
});
} as LanguageProto<'csp'>;

View File

@ -1,6 +1,7 @@
import cssSelector from './prism-css-selector';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'css-extras'>} */ ({
export default {
id: 'css-extras',
require: cssSelector,
grammar() {
@ -46,4 +47,4 @@ export default /** @type {import("../types").LanguageProto<'css-extras'>} */ ({
'number': number
};
}
});
} as LanguageProto<'css-extras'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'css-selector'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'css-selector',
grammar() {
const string = /(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;
@ -62,4 +64,4 @@ export default /** @type {import("../types").LanguageProto<'css-selector'>} */ (
'punctuation': /[(),]/,
};
}
});
} as LanguageProto<'css-selector'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import { rest } from '../shared/symbols';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'css'>} */ ({
export default {
id: 'css',
optional: 'css-extras',
grammar({ getOptionalLanguage }) {
@ -71,4 +72,4 @@ export default /** @type {import("../types").LanguageProto<'css'>} */ ({
return css;
}
});
} as LanguageProto<'css'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'csv'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'csv',
grammar() {
// https://tools.ietf.org/html/rfc4180
@ -8,4 +10,4 @@ export default /** @type {import("../types").LanguageProto<'csv'>} */ ({
'punctuation': /,/
};
}
});
} as LanguageProto<'csv'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'cue'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'cue',
grammar() {
// https://cuelang.org/docs/references/spec/
@ -80,4 +82,4 @@ export default /** @type {import("../types").LanguageProto<'cue'>} */ ({
'punctuation': /[()[\]{},.:]/
};
}
});
} as LanguageProto<'cue'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'cypher'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'cypher',
grammar: {
// https://neo4j.com/docs/cypher-manual/current/syntax/comments/
@ -36,4 +38,4 @@ export default /** @type {import("../types").LanguageProto<'cypher'>} */ ({
'operator': /:|<--?|--?>?|<>|=~?|[<>]=?|[+*/%^|]|\.\.\.?/,
'punctuation': /[()[\]{},;.]/
}
});
} as LanguageProto<'cypher'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import clike from './prism-clike';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'d'>} */ ({
export default {
id: 'd',
require: clike,
grammar({ extend }) {
@ -92,4 +93,4 @@ export default /** @type {import("../types").LanguageProto<'d'>} */ ({
return d;
}
});
} as LanguageProto<'d'>;

View File

@ -1,7 +1,8 @@
import { insertBefore } from '../shared/language-util';
import clike from './prism-clike';
import type { LanguageProto } from '../types';
export default /** @type {import("../types").LanguageProto<'dart'>} */ ({
export default {
id: 'dart',
require: clike,
grammar({ extend }) {
@ -85,4 +86,4 @@ export default /** @type {import("../types").LanguageProto<'dart'>} */ ({
return dart;
}
});
} as LanguageProto<'dart'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'dataweave'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'dataweave',
grammar: {
'url': /\b[A-Za-z]+:\/\/[\w/:.?=&-]+|\burn:[\w:.?=&-]+/,
@ -38,4 +40,4 @@ export default /** @type {import("../types").LanguageProto<'dataweave'>} */ ({
'operator': /<<|>>|->|[<>~=]=?|!=|--?-?|\+\+?|!|\?/,
'boolean': /\b(?:false|true)\b/,
}
});
} as LanguageProto<'dataweave'>;

View File

@ -1,4 +1,6 @@
export default /** @type {import("../types").LanguageProto<'dax'>} */ ({
import type { LanguageProto } from '../types';
export default {
id: 'dax',
grammar: {
'comment': {
@ -27,4 +29,4 @@ export default /** @type {import("../types").LanguageProto<'dax'>} */ ({
'operator': /:=|[-+*\/=^]|&&?|\|\||<(?:=>?|<|>)?|>[>=]?|\b(?:IN|NOT)\b/i,
'punctuation': /[;\[\](){}`,.]/
}
});
} as LanguageProto<'dax'>;

View File

@ -1,7 +1,9 @@
import type { LanguageProto } from '../types';
// ABNF grammar:
// https://github.com/dhall-lang/dhall-lang/blob/master/standard/dhall.abnf
export default /** @type {import("../types").LanguageProto<'dhall'>} */ ({
export default {
id: 'dhall',
grammar: {
// Multi-line comments can be nested. E.g. {- foo {- bar -} -}
@ -67,4 +69,4 @@ export default /** @type {import("../types").LanguageProto<'dhall'>} */ ({
// we'll just assume that every capital word left is a type name
'class-name': /\b[A-Z]\w*\b/
}
});
} as LanguageProto<'dhall'>;

View File

@ -1,3 +1,5 @@
import type { Grammar, LanguageProto } from '../types';
/**
* A map from the name of a block to its line prefix.
*/
@ -10,11 +12,10 @@ export const PREFIXES = {
'diff': '!',
};
export default /** @type {import("../types").LanguageProto<'diff'>} */ ({
export default {
id: 'diff',
grammar() {
/** @type {import("../types").Grammar} */
const diff = {
const diff: Grammar = {
'coord': [
// Match all kinds of coord lines (prefixed by "+++", "---" or "***").
/^(?:\*{3}|-{3}|\+{3}).*$/m,
@ -29,7 +30,7 @@ export default /** @type {import("../types").LanguageProto<'diff'>} */ ({
// add a token for each prefix
Object.keys(PREFIXES).forEach((name) => {
const prefix = PREFIXES[/** @type {keyof PREFIXES} */ (name)];
const prefix = PREFIXES[name as keyof typeof PREFIXES];
const alias = [];
const mainName = /\w+/.exec(name)?.[0] || name;
@ -55,4 +56,4 @@ export default /** @type {import("../types").LanguageProto<'diff'>} */ ({
return diff;
}
});
} as LanguageProto<'diff'>;

View File

@ -1,11 +1,12 @@
import { embeddedIn } from '../shared/languages/templating';
import { tokenize } from '../shared/symbols';
import markup from './prism-markup';
import type { LanguageProto } from '../types';
// Django/Jinja2 syntax definition for Prism.js <http://prismjs.com> syntax highlighter.
// Mostly it works OK but can paint code incorrectly on complex html/template tag combinations.
export default /** @type {import("../types").LanguageProto<'django'>} */ ({
export default {
id: 'django',
require: markup,
alias: 'jinja2',
@ -48,4 +49,4 @@ export default /** @type {import("../types").LanguageProto<'django'>} */ ({
},
[tokenize]: embeddedIn('markup')
}
});
} as LanguageProto<'django'>;

Some files were not shown because too many files have changed in this diff Show More