• 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

98.81
/lib/highlight_shared.ts
1
import type { HighlightResult, HLJSApi } from 'highlight.js';
2✔
2
import _highlight from 'highlight.js';
2✔
3
import stripIndent from 'strip-indent';
2✔
4
import alias from './highlight_alias.js';
2✔
5
import { InternalHighlightOptions } from './types.js';
2✔
6

2✔
7
let hljs: HLJSApi | undefined;
2✔
8

2✔
9
function highlightUtil(str: string, options: InternalHighlightOptions = {}) {
68✔
10
  if (typeof str !== 'string') throw new TypeError('str must be a string!');
68✔
11

66✔
12
  const useHljs = Object.prototype.hasOwnProperty.call(options, 'hljs') ? options.hljs : false;
68✔
13
  const {
68✔
14
    gutter = true,
68✔
15
    firstLine = 1,
68✔
16
    caption,
68✔
17
    mark = [],
68✔
18
    languageAttr = false,
68✔
19
    tab,
68✔
20
    stripIndent: enableStripIndent = true
68✔
21
  } = options;
68✔
22
  let { wrap = true } = options;
68✔
23

68✔
24
  if (enableStripIndent) {
68✔
25
    str = stripIndent(str);
64✔
26
  }
64✔
27

66✔
28
  if (!hljs) {
68✔
29
    hljs = _highlight;
2✔
30
  }
2✔
31
  hljs.configure({ classPrefix: useHljs ? 'hljs-' : '' });
68✔
32
  let lang = options.lang || options.language || 'plaintext';
68✔
33
  // Register the language if it hasn't been registered yet
68✔
34
  if (!hljs.getLanguage(lang)) {
68✔
35
    try {
2✔
36
      const mod = global._require(
2✔
37
        `highlight.js/lib/languages/${alias.aliases[lang] || lang}`
2✔
38
      );
2✔
39
      hljs.registerLanguage(lang, mod.default || mod);
2!
40
    } catch {
2✔
41
      // If the language module does not exist, skip registration
2✔
42
    }
2✔
43
  }
2✔
44

66✔
45
  const data = highlight(str, options);
66✔
46
  lang = options.lang || data.language || '';
68!
47
  const classNames = (useHljs ? 'hljs' : 'highlight') + (lang ? ` ${lang}` : '');
68!
48

68✔
49
  if (gutter && !wrap) wrap = true; // arbitrate conflict ("gutter:true" takes priority over "wrap:false")
68✔
50

66✔
51
  const before = useHljs
66✔
52
    ? `<pre><code class="${classNames}"${languageAttr && lang ? ` data-language="${lang}"` : ''}>`
42!
53
    : '<pre>';
59✔
54
  const after = useHljs ? '</code></pre>' : '</pre>';
68✔
55

68✔
56
  const lines = data.value.split('\n');
68✔
57
  let numbers = '';
68✔
58
  let content = '';
68✔
59

68✔
60
  for (let i = 0, len = lines.length; i < len; i++) {
68✔
61
    let line = lines[i];
254✔
62
    if (tab) line = replaceTabs(line, tab);
254✔
63
    numbers += `<span class="line">${Number(firstLine) + i}</span><br>`;
254✔
64
    content += formatLine(line, Number(firstLine) + i, mark, options, wrap);
254✔
65
  }
254✔
66

66✔
67
  let codeCaption = '';
66✔
68

66✔
69
  if (caption) {
68✔
70
    codeCaption = wrap ? `<figcaption>${caption}</figcaption>` : `<div class="caption">${caption}</div>`;
4✔
71
  }
4✔
72

66✔
73
  if (!wrap) {
68✔
74
    // if original content has one trailing newline, replace it only once, else remove all trailing newlines
20✔
75
    content = /\r?\n$/.test(data.value) ? content.replace(/\n$/, '') : content.trimEnd();
20✔
76
    return `<pre>${codeCaption}<code class="${classNames}"${languageAttr && lang ? ` data-language="${lang}"` : ''}>${content}</code></pre>`;
20✔
77
  }
20✔
78

46✔
79
  let result = `<figure class="highlight${data.language ? ` ${data.language}` : ''}"${languageAttr && lang ? ` data-language="${lang}"` : ''}>`;
68!
80

68✔
81
  result += codeCaption;
68✔
82

68✔
83
  result += '<table><tr>';
68✔
84

68✔
85
  if (gutter) {
68✔
86
    result += `<td class="gutter"><pre>${numbers}</pre></td>`;
38✔
87
  }
38✔
88

46✔
89
  result += `<td class="code">${before}${content}${after}</td>`;
46✔
90
  result += '</tr></table></figure>';
46✔
91

46✔
92
  return result;
46✔
93
}
46✔
94

2✔
95
function formatLine(line: string, lineno: number, marked: number[], options: InternalHighlightOptions, wrap: boolean) {
254✔
96
  const useHljs = options.hljs || false || !wrap;
254✔
97
  const br = wrap ? '<br>' : '\n';
254✔
98
  let res = useHljs ? '' : '<span class="line';
254✔
99
  if (marked.includes(lineno)) {
254✔
100
    // Handle marked lines.
6✔
101
    res += useHljs ? `<mark>${line}</mark>` : ` marked">${line}</span>`;
6✔
102
  } else {
254✔
103
    res += useHljs ? line : `">${line}</span>`;
248✔
104
  }
248✔
105

254✔
106
  res += br;
254✔
107
  return res;
254✔
108
}
254✔
109

2✔
110
function replaceTabs(str: string, tab: string) {
26✔
111
  return str.replace(/\t+/, match => tab.repeat(match.length));
26✔
112
}
26✔
113

2✔
114
function highlight(str: string, options: InternalHighlightOptions) {
66✔
115
  let { lang } = options;
66✔
116
  const { autoDetect = false } = options;
66✔
117

66✔
118
  if (!hljs) {
66!
NEW
119
    hljs = _highlight;
×
NEW
120
  }
×
121

66✔
122
  if (lang) {
66✔
123
    lang = lang.toLowerCase();
32✔
124
  } else if (autoDetect) {
66✔
125
    const result = hljs.highlightAuto(str);
8✔
126
    return closeTags(result);
8✔
127
  }
8✔
128

58✔
129
  if (!lang || (!alias.aliases[lang] && !hljs.getLanguage(lang))) {
66✔
130
    lang = 'plaintext';
28✔
131
  }
28✔
132

58✔
133
  const res = hljs.highlight(str, {
58✔
134
    language: lang,
58✔
135
    ignoreIllegals: true
58✔
136
  });
58✔
137

58✔
138
  return closeTags(res);
58✔
139
}
58✔
140

2✔
141
// https://github.com/hexojs/hexo-util/issues/10
2✔
142
function closeTags(res: HighlightResult) {
66✔
143
  const tokenStack = [];
66✔
144

66✔
145
  res.value = res.value
66✔
146
    .split('\n')
66✔
147
    .map(line => {
66✔
148
      const prepend = tokenStack.map(token => `<span class="${token}">`).join('');
254✔
149
      const matches = line.matchAll(/(<span class="(.*?)">|<\/span>)/g);
254✔
150
      for (const match of matches) {
254✔
151
        if (match[0] === '</span>') tokenStack.shift();
536✔
152
        else tokenStack.unshift(match[2]);
268✔
153
      }
536✔
154
      const append = '</span>'.repeat(tokenStack.length);
254✔
155
      return prepend + line + append;
254✔
156
    })
66✔
157
    .join('\n');
66✔
158
  return res;
66✔
159
}
66✔
160

2✔
161
// For ESM compatibility
2✔
162
export default highlightUtil;
2✔
163
// For CommonJS compatibility
2✔
164
if (typeof module !== 'undefined' && typeof module.exports === 'object' && module.exports !== null) {
2!
165
  module.exports = highlightUtil;
1✔
166
  // For ESM compatibility
1✔
167
  module.exports.default = highlightUtil;
1✔
168
}
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