• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hexojs / hexo-util / 16651986209

31 Jul 2025 02:35PM UTC coverage: 73.036% (-23.8%) from 96.875%
16651986209

Pull #426

github

web-flow
Merge e24598988 into d497bc760
Pull Request #426: build: restructure for dual ESM/CJS output, update tooling, and enhance utilities

811 of 1128 branches covered (71.9%)

1594 of 2028 new or added lines in 40 files covered. (78.6%)

6 existing lines in 1 file now uncovered.

5290 of 7243 relevant lines covered (73.04%)

69.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.31
/lib/prism.ts
1
import PrismCore from 'prismjs';
2✔
2
import prismLoadLanguages from 'prismjs/components/index.js';
2✔
3
import stripIndent from 'strip-indent';
2✔
4
import escapeHTML from './escape_html.js';
2✔
5

2✔
6
let Prism: typeof import('prismjs') | undefined;
2✔
7

2✔
8
const prismAlias = Object.entries(PrismCore.languages).reduce(
2✔
9
  (acc, [key, value]) => {
2✔
10
    if (value && typeof value === 'object' && 'alias' in value) {
38!
NEW
11
      const alias = (value as { alias?: string | string[] }).alias;
×
NEW
12
      if (alias) {
×
NEW
13
        if (Array.isArray(alias)) {
×
NEW
14
          alias.forEach(a => (acc[a] = key));
×
NEW
15
        } else if (typeof alias === 'string') {
×
NEW
16
          acc[alias] = key;
×
NEW
17
        }
×
NEW
18
      }
×
UNCOV
19
    }
×
20
    return acc;
38✔
21
  },
38✔
22
  {} as Record<string, string>
2✔
23
);
2✔
24

2✔
25
const prismSupportedLanguages = Object.keys(PrismCore.languages).concat(Object.keys(prismAlias));
2✔
26

2✔
27
/**
2✔
28
 * Wrapper of Prism.highlight()
2✔
29
 * @param {String} code
2✔
30
 * @param {String} language
2✔
31
 */
2✔
32
function prismHighlight(code: string, language: string) {
6✔
33
  if (!Prism) Prism = PrismCore;
6✔
34

6✔
35
  // Prism has not load the language pattern
6✔
36
  if (!Prism.languages[language] && prismSupportedLanguages.includes(language)) prismLoadLanguages(language);
6!
37

6✔
38
  if (Prism.languages[language]) {
6✔
39
    // Prism escapes output by default
6✔
40
    return Prism.highlight(code, Prism.languages[language], language);
6✔
41
  }
6✔
UNCOV
42

×
UNCOV
43
  // Current language is not supported by Prism, return origin code;
×
UNCOV
44
  return escapeHTML(code);
×
UNCOV
45
}
×
46

2✔
47
/**
2✔
48
 * Generate line number required HTML snippet
2✔
49
 * @param {String} code - Highlighted code
2✔
50
 */
2✔
51
function lineNumberUtil(code: string) {
10✔
52
  const matched = code.match(/\n(?!$)/g);
10✔
53
  const num = matched ? matched.length + 1 : 1;
10✔
54
  const lines = new Array(num + 1).join('<span></span>');
10✔
55

10✔
56
  return `<span aria-hidden="true" class="line-numbers-rows">${lines}</span>`;
10✔
57
}
10✔
58

2✔
59
function replaceTabs(str: string, tab: string) {
1✔
60
  return str.replace(/^\t+/gm, match => tab.repeat(match.length));
1✔
61
}
1✔
62

2✔
63
interface Options {
2✔
64
  caption?: string;
2✔
65
  firstLine?: number;
2✔
66
  isPreprocess?: boolean;
2✔
67
  lang?: string;
2✔
68
  lineNumber?: boolean;
2✔
69
  mark?: string;
2✔
70
  tab?: string;
2✔
71
  stripIndent?: boolean;
2✔
72
}
2✔
73

2✔
74
function PrismUtil(str: string, options: Options = {}) {
17✔
75
  if (typeof str !== 'string') throw new TypeError('str must be a string!');
17✔
76

16✔
77
  const {
16✔
78
    lineNumber = true,
16✔
79
    lang = 'none',
16✔
80
    tab,
16✔
81
    mark,
16✔
82
    firstLine,
16✔
83
    isPreprocess = true,
16✔
84
    caption,
16✔
85
    stripIndent: enableStripIndent = true
16✔
86
  } = options;
16✔
87

16✔
88
  if (enableStripIndent) {
16✔
89
    str = stripIndent(str);
16✔
90
  }
16✔
91

16✔
92
  // To be consistent with highlight.js
16✔
93
  // Normalize language aliases to canonical Prism names
16✔
94
  let language = lang === 'plaintext' || lang === 'none' ? 'none' : lang;
17✔
95
  // Manual alias mapping for common cases
17✔
96
  const manualAlias: Record<string, string> = {
17✔
97
    js: 'javascript',
17✔
98
    ts: 'typescript',
17✔
99
    py: 'python',
17✔
100
    rb: 'ruby',
17✔
101
    sh: 'bash',
17✔
102
    html: 'markup',
17✔
103
    md: 'markdown',
17✔
104
    csharp: 'cs',
17✔
105
    shell: 'bash',
17✔
106
    yml: 'yaml',
17✔
107
    vue: 'markup',
17✔
108
    plaintext: 'none',
17✔
109
    none: 'none'
17✔
110
  };
17✔
111
  if (manualAlias[language]) language = manualAlias[language];
17✔
112
  if (prismAlias[language]) language = prismAlias[language];
17!
113

16✔
114
  // Ensure Prism loads the language if not loaded
16✔
115
  if (language !== 'none' && PrismCore && !PrismCore.languages[language]) {
17✔
116
    try {
5✔
117
      prismLoadLanguages(language);
5✔
118
    } catch {
5!
NEW
119
      // ignore
×
NEW
120
    }
×
121
  }
5✔
122

16✔
123
  const preTagClassArr = [];
16✔
124
  const preTagAttrArr = [];
16✔
125
  let preTagAttr = '';
16✔
126

16✔
127
  if (lineNumber) preTagClassArr.push('line-numbers');
17✔
128
  preTagClassArr.push(`language-${language}`);
16✔
129

16✔
130
  // Show Languages plugin
16✔
131
  // https://prismjs.com/plugins/show-language/
16✔
132
  if (language !== 'none') preTagAttrArr.push(`data-language="${language}"`);
17✔
133

16✔
134
  if (!isPreprocess) {
17✔
135
    // Shift Line Numbers ('firstLine' option) should only be added under non-preprocess mode
5✔
136
    // https://prismjs.com/plugins/line-numbers/
5✔
137
    if (lineNumber && firstLine) preTagAttrArr.push(`data-start="${firstLine}"`);
5✔
138

5✔
139
    // Line Highlight ('mark' option) should only be added under non-preprocess mode
5✔
140
    // https://prismjs.com/plugins/line-highlight/
5✔
141
    if (mark) preTagAttrArr.push(`data-line="${mark}"`);
5✔
142

5✔
143
    // Apply offset for 'mark' option
5✔
144
    // https://github.com/hexojs/hexo-util/pull/172#issuecomment-571882480
5✔
145
    if (firstLine && mark) preTagAttrArr.push(`data-line-offset="${firstLine - 1}"`);
5✔
146
  }
5✔
147

16✔
148
  if (preTagAttrArr.length) preTagAttr = ' ' + preTagAttrArr.join(' ');
17✔
149

16✔
150
  if (tab) str = replaceTabs(str, tab);
17✔
151

16✔
152
  const codeCaption = caption ? `<div class="caption">${caption}</div>` : '';
17✔
153

17✔
154
  const startTag = `<pre class="${preTagClassArr.join(' ')}"${preTagAttr}>${codeCaption}<code class="language-${language}">`;
17✔
155
  const endTag = '</code></pre>';
17✔
156

17✔
157
  let parsedCode = '';
17✔
158

17✔
159
  // Always use Prism for supported languages, even if not loaded yet
17✔
160
  if (language !== 'none' && isPreprocess && PrismCore.languages[language]) {
17✔
161
    parsedCode = prismHighlight(str, language);
6✔
162
  } else if (language !== 'none' && isPreprocess && Prism && Prism.languages[language]) {
17!
UNCOV
163
    parsedCode = prismHighlight(str, language);
×
164
  } else if (language !== 'none' && isPreprocess) {
10✔
165
    // Try to load language and highlight
2✔
166
    try {
2✔
167
      prismLoadLanguages(language);
2✔
168
      if (PrismCore.languages[language]) {
2!
NEW
169
        parsedCode = prismHighlight(str, language);
×
170
      } else {
2✔
171
        parsedCode = escapeHTML(str);
2✔
172
      }
2✔
173
    } catch {
2!
NEW
174
      parsedCode = escapeHTML(str);
×
NEW
175
    }
×
176
  } else {
10✔
177
    parsedCode = escapeHTML(str);
8✔
178
  }
8✔
179

16✔
180
  // lineNumberUtil() should be used only under preprocess mode
16✔
181
  if (lineNumber && isPreprocess) {
17✔
182
    parsedCode += lineNumberUtil(parsedCode);
10✔
183
  }
10✔
184

16✔
185
  return startTag + parsedCode + endTag;
16✔
186
}
16✔
187

2✔
188

2✔
189
// For ESM compatibility
2✔
190
export default PrismUtil;
2✔
191
// For CommonJS compatibility
2✔
192
if (typeof module !== 'undefined' && typeof module.exports === 'object' && module.exports !== null) {
2!
193
  module.exports = PrismUtil;
1✔
194
  // For ESM compatibility
1✔
195
  module.exports.default = PrismUtil;
1✔
196
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc