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

IT-Service-NPM / remark-include-code / 25316008395

04 May 2026 11:18AM UTC coverage: 97.519% (-0.1%) from 97.654%
25316008395

push

github

sergey-s-betke
Merge branch 'main' of github.com:IT-Service-NPM/remark-include-code

66 of 73 branches covered (90.41%)

Branch coverage included in aggregate %.

720 of 733 relevant lines covered (98.23%)

20.06 hits per line

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

95.6
/src/code-content.ts
1
import { readFileSync } from 'node:fs';
25✔
2
import { readFile } from 'node:fs/promises';
25✔
3
import type { LeafDirective } from 'mdast-util-directive';
25✔
4
import type { VFile } from 'vfile';
25✔
5
import iconv from 'iconv-lite';
25✔
6
import type { IDirectiveAttributes } from './options.js';
25✔
7

25✔
8
/**
25✔
9
 * Class for code file content
25✔
10
 *
25✔
11
 * @param file - Current markdown file
25✔
12
 * @param node - `::include-code` directive Node
25✔
13
 * @param attributes - `::include-code` attributes
25✔
14
 * @param content - file content
25✔
15
 *
25✔
16
 * @internal
25✔
17
 */
25✔
18
export class CodeFileContent {
25✔
19

25✔
20
  protected readonly file: VFile;
25✔
21
  protected readonly node: LeafDirective;
23✔
22
  protected readonly attributes: IDirectiveAttributes;
23✔
23
  protected fileContent?: Buffer<ArrayBuffer>;
23✔
24
  protected _content: string;
25✔
25

25✔
26
  public constructor(
25✔
27
    file: VFile,
23✔
28
    node: LeafDirective,
23✔
29
    attributes: IDirectiveAttributes,
23✔
30
    content?: Buffer<ArrayBuffer>
23✔
31
  ) {
23✔
32
    this.file = file;
23✔
33
    this.node = node;
23✔
34
    this.attributes = attributes;
23✔
35
    this.fileContent = content;
23✔
36
    this._content = '';
23✔
37
  }
23✔
38

25✔
39
  public static readFileSync(
25✔
40
    file: VFile,
6✔
41
    node: LeafDirective,
6✔
42
    attributes: IDirectiveAttributes,
6✔
43
    path: string
6✔
44
  ): CodeFileContent {
6✔
45
    const self = new CodeFileContent(file, node, attributes);
6✔
46
    try {
6✔
47
      self.fileContent = readFileSync(path);
6✔
48
    } catch (error) {
6✔
49
      self.catchFileError(error);
6✔
50
    };
6✔
51
    return self;
6✔
52
  }
6✔
53

25✔
54
  public static async readFile(
25✔
55
    file: VFile,
17✔
56
    node: LeafDirective,
17✔
57
    attributes: IDirectiveAttributes,
17✔
58
    path: string
17✔
59
  ): Promise<CodeFileContent> {
17✔
60
    const self = new CodeFileContent(file, node, attributes);
17✔
61
    try {
17✔
62
      self.fileContent = await readFile(path);
17✔
63
    } catch (error) {
17✔
64
      self.catchFileError(error);
17✔
65
    };
17✔
66
    return self;
17✔
67
  }
17✔
68

25✔
69
  public get content(): string {
25✔
70
    return this._content;
19✔
71
  }
19✔
72

25✔
73
  public toString(): string {
25✔
74
    return this.content;
×
75
  }
×
76

25✔
77
  public decode(): this {
25✔
78
    this._content = this.fileContent ?
19✔
79
      iconv.decode(this.fileContent, this.attributes.encoding) : '';
19!
80
    return this;
19✔
81
  }
19✔
82

25✔
83
  public normalizeEOL(): this {
25✔
84
    this._content = this._content.replaceAll(/\r?\n/g, '\n');
19✔
85
    return this;
19✔
86
  }
19✔
87

25✔
88
  public trimFinalNewline(): this {
25✔
89
    if (this.attributes.trimFinalNewline) {
19✔
90
      this._content = this._content.replace(/\n$/, '');
19✔
91
    }
19✔
92
    return this;
19✔
93
  }
19✔
94

25✔
95
  public selectLinesRange(): this {
25✔
96
    const contentLines = this._content.split('\n');
19✔
97
    this._content = contentLines
19✔
98
      .slice(
19✔
99
        (this.attributes.fromLine ?? 1) - 1,
19✔
100
        this.attributes.toLine ?? contentLines.length
19✔
101
      )
19✔
102
      .join('\n');
19✔
103
    return this;
19✔
104
  }
19✔
105

25✔
106
  public replaceTabs(): this {
25✔
107
    if (this.attributes.tabWidth !== undefined) {
19✔
108
      const w = this.attributes.tabWidth;
19✔
109
      this._content = this._content.replaceAll(
19✔
110
        new RegExp(
19✔
111
          String.raw`(?<=^( {${w.toString()}}| {0,${(w - 1).toString()}}\t)*) {0,${(w - 1).toString()}}\t`,
19✔
112
          'gm'
19✔
113
        ),
19✔
114
        ' '.repeat(w)
19✔
115
      );
19✔
116
    }
19✔
117
    return this;
19✔
118
  }
19✔
119

25✔
120
  public normalizeIndent(): this {
25✔
121
    if (
19✔
122
      this.attributes.trimExtraIndent &&
19✔
123
      (this.attributes.tabWidth !== undefined)
19✔
124
    ) {
19✔
125
      // eslint-disable-next-line sonarjs/slow-regex
19✔
126
      const indentsWidth = /^\s*(?=\S)/gmd
19✔
127
        .exec(this._content)
19✔
128
        ?.indices
19✔
129
        ?.map(
19✔
130
          (
19✔
131
            indentPosition?: [number, number]
2✔
132
          ): number =>
2✔
133
            indentPosition ? indentPosition[1] - indentPosition[0] : 0
2!
134
        );
19✔
135
      const extraIndentWidth = indentsWidth ?
19✔
136
        // eslint-disable-next-line unicorn/no-null
19✔
137
        Math.min.apply(null, indentsWidth) : 0;
19!
138
      if (extraIndentWidth) {
19✔
139
        this._content = this._content.replaceAll(
1✔
140
          new RegExp(String.raw`^ {${extraIndentWidth.toString()}}`, 'gm'),
1✔
141
          ''
1✔
142
        );
1✔
143
      }
1✔
144
    }
19✔
145
    return this;
19✔
146
  }
19✔
147

25✔
148
  protected catchFileError(error: any): void {
25✔
149
    if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
4✔
150
      const errorMessage = `::include-code, file(s) "${this.attributes.file}" not found`;
4✔
151
      if (this.attributes.optional) {
4✔
152
        throw this.file.info(errorMessage, this.node);
2✔
153
      } else {
2✔
154
        this.file.fail(errorMessage, this.node);
2✔
155
      }
2✔
156
    } else {
4!
157
      throw error;
×
158
    }
×
159
  }
4✔
160

25✔
161
}
25✔
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

© 2026 Coveralls, Inc