ts-highlight 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,63 @@
1
- # <p align='center'> **es-highlight** </p>
1
+ # <p align='center'> **ts-highlight** </p>
2
+
3
+ <div align='center'>
4
+
5
+ [![CI](https://github.com/a-marigold/crumb/actions/workflows/ci.yaml/badge.svg)](https://github.com/a-marigold/ts-highlight/actions) [![npm](https://img.shields.io/npm/v/ts-highlight)](https://npmjs.com/package/ts-highlight)
6
+
7
+ </div>
8
+
9
+ <div align='center'>
10
+
11
+ ![marigolds](assets/marigolds.webp)
12
+
13
+ </div>
14
+
15
+ ### Usage
16
+
17
+ ```typescript
18
+ import { higlight, type HighlightCSSClasses } from 'ts-higlight';
19
+
20
+ // styles of generated code are extensible
21
+ const cssClasses: HiglightCSSClasses = {
22
+ pre: 'pre-class second-pre-class', // classes can be divided as if they are in HTML `class` attribute
23
+ code: 'code-class',
24
+ line: 'Example-module-css-module__m3Y3uq__line', // classes can be CSS module strings
25
+
26
+ token: 'token-class', // `token` class is the class of every token of generated code
27
+
28
+ // Every token has its own class for fine grained configuration
29
+ operator: 'operator-class',
30
+
31
+ identifier: 'identifier',
32
+ keyword: 'keyword',
33
+
34
+ bigintChar: 'bigint-class', // class of bigint character (`10n` - `n` is `bigintChar`)
35
+ stringLiteral: 'string',
36
+
37
+ whitespace: 'whitespace', // even whitespace has its own class
38
+
39
+
40
+ ...
41
+ };
42
+
43
+ highlight(`let a = 'hello';`, cssClasses);
44
+ ```
45
+
46
+ Real output will be:
47
+
48
+ ```html
49
+ <pre class="pre-class second-pre-class">
50
+ <code class="code-class">
51
+ <div class="Example-module-css-module__m3Y3uq__line">
52
+ <span class="token-class keyword">let</span>
53
+ <span class="token-class whitespace"> </span>
54
+ <span class="token-class identifier">a</span>
55
+ <span class="token-class whitespace"> </span>
56
+ <span class="token-class operator-class">=</span>
57
+ <span class="token-class whitespace"> </span>
58
+ <span class="token-class string">'hello'</span>
59
+ <span class="token-class operator">;</span>
60
+ </div>
61
+ </code>
62
+ </pre>
63
+ ```
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Type of CSS classes object.
3
+ *
4
+ * Classes are used in generated HTML.
5
+ *
6
+ * CSS modules also can be used
7
+ *
8
+ * &nbsp;
9
+ *
10
+ * Default generated HTML structure:
11
+ * ```html
12
+ * <pre>
13
+ * <code>
14
+ * <div> ... </div> <!-- line -->
15
+ *
16
+ * ...
17
+ * </code>
18
+ * </pre>
19
+ * ```
20
+ *
21
+ * @example
22
+ * const CSSClasses: HighlightCSSClasses = {
23
+ * token: 'my-token-classname',
24
+ * comment: 'my-comment-classname',
25
+ * ...
26
+ * };
27
+ */
28
+ type HighlightCSSClasses = Partial<{
29
+ /**
30
+ *
31
+ *
32
+ * `pre` class is used for root wrapper element
33
+ */
34
+ pre: string;
35
+ /**
36
+ * `code` class is used for generated code text element
37
+ */
38
+ code: string;
39
+ /**
40
+ * `line` class is used for every line of generated code
41
+ */
42
+ line: string;
43
+ /**
44
+ *
45
+ */
46
+ /**
47
+ * `token` class is used for every token of code, even whitespace
48
+ */
49
+ token: string;
50
+ whitespace: string;
51
+ comment: string;
52
+ keyword: string;
53
+ instruction: string;
54
+ operator: string;
55
+ constantIdentifier: string;
56
+ mutableIdentifier: string;
57
+ numberLiteral: string;
58
+ stringLiteral: string;
59
+ booleanLiteral: string;
60
+ NaNLiteral: string;
61
+ bigintChar: string;
62
+ }>;
63
+
64
+ /**
65
+ *
66
+ * Transforms received `source` string to HTML with higlighted source code.
67
+ *
68
+ * Classes could be divided with space as if they are in default HTML `class` attribute.
69
+ *
70
+ * @param {string} source JavaScript or TypeScript source code to highlight.
71
+ * @param {HiglightCSSClasses} cssClasses object with CSS classes.
72
+ *
73
+ * @returns {string} string with HTML of highlighted code.
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const cssClasses: HighlightCSSClasses = {
78
+ * pre: 'pre-element-class second-class',
79
+ * code: 'my-code-element-class',
80
+ *
81
+ * line: 'line-class my-line third-class',
82
+ * token: 'token-class',
83
+ *
84
+ * operator: 'operator-class',
85
+ *
86
+ * keyword: 'keyword-class',
87
+ * stringLiteral: 'string-class',
88
+ *
89
+ * ...
90
+ * }
91
+ *
92
+ * highlight('let a = "hello";', cssClasses);
93
+ * ```
94
+ * Output will be:
95
+ * ```html
96
+ * <pre class="pre-element-class">
97
+ * <code class="code-element-class">
98
+ * <div class="line-class"> ... </div> <!-- code line with generated tokens inside -->
99
+ * </code>
100
+ * </pre>
101
+ * ```
102
+ */
103
+ declare const highlight: (source: string, cssClasses: HighlightCSSClasses) => string;
104
+
105
+ export { highlight };
106
+ export type { HighlightCSSClasses };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ const e=new Set(["=","+","-","*","/","%","~","^",".",":","|","&","?","!","<",">","{","}","[","]","(",")",";",","]),t=new Set(["==","!=","<=",">=","++","--","**","*=","/=","%=","^=","&=","|=","&&","||","??"]),n=new Set(["===","!==","**=","<<=",">>=",">>>","&&=","||=","??="]),o={NaN:"NaNLiteral",abstract:"Keyword",as:"Instruction",assert:"Instruction",asserts:"Instruction",async:"Instruction",await:"Instruction",break:"Instruction",catch:"Instruction",class:"Keyword",const:"Keyword",continue:"Instruction",debugger:"Keyword",declare:"Keyword",default:"Instruction",delete:"Keyword",do:"Instruction",enum:"Keyword",export:"Instruction",false:"BooleanLiteral",finally:"Instruction",for:"Instruction",function:"Keyword",implements:"Keyword",import:"Instruction",in:"Keyword",instanceof:"Keyword",interface:"Keyword",is:"Instruction",keyof:"Keyword",let:"Keyword",new:"Keyword",package:"Instruction",this:"Keyword",throw:"Instruction",true:"BooleanLiteral",try:"Instruction",type:"Keyword",typeof:"Keyword",var:"Keyword",void:"Keyword",while:"Instruction",with:"Instruction",yield:"Instruction"},r=/^[a-zA-Zа-яА-Я_$]$/,i=/^[a-zA-Zа-яА-Я_$0-9]$/,s=/^[0-9]$/,a='<span class="',l="</span>",c=(c,u)=>{const y=(a=>{const l=[],c=a.length;let u=0;e:for(;u<c;){if(" "===a[u]||"\t"===a[u]){const e=u;for(u++;u<c&&(" "===a[u]||"\t"===a[u]);)u++;l[l.length]={type:"WhiteSpace",value:a.slice(e,u),start:e,end:u};continue e}if("\n"===a[u]||"\r"===a[u]){const e=u;"\r"===a[u]&&u++,u++,l[l.length]={type:"LineDivision",value:"\n",start:e,end:u};continue e}if(r.test(a[u])){const e=u;for(u++;u<c&&i.test(a[u]);)u++;const t=a.slice(e,u);l[l.length]={type:o[t]??"Identifier",value:t,start:e,end:u};continue e}if("'"===a[u]||'"'===a[u]||"`"===a[u]){const e=u,t=a[u];for(u++;u<c&&a[u]!==t;)u++;u++,l[l.length]={type:"StringLiteral",value:a.slice(e,u),start:e,end:u};continue e}if(s.test(a[u])){const e=u;for(;u<c&&s.test(a[u]);)u++;l[l.length]={type:"NumberLiteral",value:a.slice(e,u),start:e,end:u};continue e}if("/"===a[u]){const e=u;if(u++,"/"===a[u]){for(u++;u<c&&"\r"!==a[u]&&"\n"!==a[u];)u++;"\r"===a[u]&&(u+=2),l[l.length]={type:"Comment",value:a.slice(e,u),start:e,end:u},l[l.length]={type:"LineDivision",value:"\n",start:e,end:u};continue e}if("*"===a[u]){u++;let t=e;for(;u<c&&("*"!==a[u]||"/"!==a[u+1]);)"\n"!==a[u]&&"\r"!==a[u]||(l[l.length]={type:"Comment",value:a.slice(t,u),start:e,end:u},l[l.length]={type:"LineDivision",value:"\n",start:u,end:u+1},"\r"===a[u]&&u++,u++,t=u),u++;u+=2,l[l.length]={type:"Comment",value:a.slice(t,u),start:e,end:u};continue e}}if(a[u]+a[u+1]+a[u+2]+a[u+3]===">>>="){const e=u;u+=4,l[l.length]={type:"Operator",value:a.slice(e,u),start:e,end:u};continue e}if(n.has(a[u]+a[u+1]+a[u+2])){const e=u;u+=3,l[l.length]={type:"Operator",value:a.slice(e,u),start:e,end:u};continue e}if(t.has(a[u]+a[u+1])){const e=u;u+=2,l[l.length]={type:"Operator",value:a.slice(e,u),start:e,end:u};continue e}if(e.has(a[u])){const e=u;u++,l[l.length]={type:"Operator",value:a.slice(e,u),start:e,end:u};continue e}u++}return l})(c),d=((e,t)=>{const n=e.length;let o='<pre class="'+t.pre+'"><code class="'+t.code+'"><div class="'+t.line+'">',r=!0,i=0;for(;i<n;){const s=e[i];if("WhiteSpace"===s.type){o+=a+t.token+" "+t.whitespace+'">';let e=0;for(;e<s.value.length;)o+=" ",e++;o+=l,i++;continue}"LineDivision"!==s.type?"Operator"!==s.type?"Identifier"!==s.type?"Keyword"!==s.type?"Instruction"!==s.type?"StringLiteral"!==s.type?"NumberLiteral"!==s.type?"BooleanLiteral"!==s.type?"NaNLiteral"!==s.type?("Comment"!==s.type||(o+=a+t.token+" "+t.comment+'">'+s.value+l),i++):(o+=a+t.token+" "+t.NaNLiteral+'">'+s.value+l,i++):(o+=a+t.token+" "+t.booleanLiteral+'">'+s.value+l,i++):(o+=a+t.token+" "+t.numberLiteral+'">'+s.value+l,i++):(o+=a+t.token+" "+t.stringLiteral+'">'+s.value+l,i++):(o+=a+t.token+" "+t.instruction+">"+s.value+l,i++):(o+=a+t.token+" "+t.keyword+'">'+s.value+l,i++):(o+=a+t.token+" "+t.mutableIdentifier+'">'+s.value+l,i++):(o+=a+t.token+" "+t.operator+'">'+s.value+l,i++):(o+="</div>",i!==n-1&&(o+='<div class="'+t.line+'">',r=!1),i++)}return r&&(o+="</div>"),o+="</code></pre>",o})(y,u);return d};export{c as highlight};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-highlight",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "module": "./dist/index.js",
5
5
  "type": "module",
6
6
  "private": false,
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "repository": {
13
13
  "type": "github",
14
- "url": "https://github.com/a-marigold/es-higрlight"
14
+ "url": "https://github.com/a-marigold/ts-highlight"
15
15
  },
16
16
  "scripts": {
17
17
  "lint": "biome check",
@@ -26,6 +26,9 @@
26
26
  "higlight",
27
27
  "syntax-higlight"
28
28
  ],
29
+ "files": [
30
+ "dist"
31
+ ],
29
32
  "devDependencies": {
30
33
  "@biomejs/biome": "^2.3.10",
31
34
  "@rollup/plugin-terser": "^0.4.4",
@@ -1,29 +0,0 @@
1
- name: ci
2
-
3
- on:
4
- push:
5
- branches: '**'
6
-
7
- jobs:
8
- ci:
9
- name: ci
10
-
11
- runs-on: ubuntu-latest
12
-
13
- steps:
14
- - uses: actions/checkout@v4
15
-
16
- - name: setup bun
17
- uses: oven-sh/setup-bun@v2
18
- - name: install dependencies
19
- run: bun install
20
-
21
- - name: run linter
22
- run: bun run lint
23
-
24
- - name: run tests
25
- run: bun test
26
-
27
- - name: build
28
-
29
- run: bun run build
package/biome.json DELETED
@@ -1,30 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
3
- "vcs": {
4
- "enabled": true,
5
- "clientKind": "git"
6
- },
7
-
8
- "files": {
9
- "ignoreUnknown": false,
10
- "includes": ["*", "!node_modules", "!dist"]
11
- },
12
-
13
- "formatter": {
14
- "enabled": false
15
- },
16
- "linter": {
17
- "enabled": true,
18
- "rules": {
19
- "recommended": true
20
- }
21
- },
22
- "assist": {
23
- "enabled": true,
24
- "actions": {
25
- "source": {
26
- "organizeImports": "off"
27
- }
28
- }
29
- }
30
- }
package/rollup.config.js DELETED
@@ -1,34 +0,0 @@
1
- import { defineConfig } from 'rollup';
2
-
3
- import typescript from '@rollup/plugin-typescript';
4
- import dts from 'rollup-plugin-dts';
5
-
6
- import terser from '@rollup/plugin-terser';
7
-
8
- export default defineConfig([
9
- {
10
- input: './src/index.ts',
11
-
12
- external: ['./src/__tests__/**'],
13
-
14
- output: {
15
- file: './dist/index.js',
16
- format: 'esm',
17
- },
18
- plugins: [typescript({ exclude: ['**__tests__/**'] }), terser()],
19
- },
20
-
21
- {
22
- input: './src/index.ts',
23
-
24
- external: ['./src/__tests_/**'],
25
-
26
- output: {
27
- file: './dist/index.d.ts',
28
-
29
- format: 'esm',
30
- },
31
-
32
- plugins: [dts()],
33
- },
34
- ]);
File without changes
@@ -1,59 +0,0 @@
1
- import { describe, it, expect } from 'bun:test';
2
-
3
- import { tokenize } from '../tokenizer';
4
-
5
- describe('tokenizer', () => {
6
- it('should return a valid tokens array', () => {
7
- const source = `
8
- let num = 10;
9
-
10
-
11
- console.log(num++);
12
- `;
13
-
14
- const tokens = tokenize(source);
15
-
16
- console.log(tokens);
17
- expect(tokens.length).toBe(25);
18
- expect(
19
- tokens.some(
20
- (token) => token.type === 'Operator' && token.value === '++'
21
- )
22
- ).toBe(true);
23
- });
24
-
25
- it('should group `space` or `tab` order to one `WhiteSpace` token', () => {
26
- const tabSource = `\t\t\t\t\t\t`;
27
- const spaceSource = ' ';
28
-
29
- const tabTokens = tokenize(tabSource);
30
- const spaceTokens = tokenize(spaceSource);
31
-
32
- expect(tabTokens.length).toBe(1);
33
- expect(tabTokens[0].type).toBe('WhiteSpace');
34
- expect(tabTokens[0].value.length).toBe(tabSource.length);
35
-
36
- expect(spaceTokens.length).toBe(1);
37
- expect(spaceTokens[0].type).toBe('WhiteSpace');
38
- expect(spaceTokens[0].value.length).toBe(spaceSource.length);
39
- });
40
-
41
- it('should handle CRLF and LF line feeds to `LineDivision` token', () => {
42
- const CRLFSource = '\r\n\r\n\r\n\r\n\r\n\r\n';
43
- const LFSource = '\n\n\n\n\n\n';
44
-
45
- const CRLFTokens = tokenize(CRLFSource);
46
- const LFTokens = tokenize(LFSource);
47
-
48
- expect(CRLFTokens.length).toBe(CRLFSource.length / 2);
49
- for (let i = CRLFTokens.length - 1; i > 0; i--) {
50
- expect(CRLFTokens[i].type).toBe('LineDivision');
51
- }
52
-
53
- expect(LFTokens.length).toBe(LFSource.length);
54
-
55
- for (let i = 0; i < CRLFTokens.length; i++) {
56
- expect(LFTokens[i].type).toBe('LineDivision');
57
- }
58
- });
59
- });
@@ -1,25 +0,0 @@
1
- /**
2
- * String with opened `span` HTML element with opened `class` attribute.
3
- *
4
- * There is double quote `"` in `class` attribute
5
- *
6
- * @example
7
- * ```typescript
8
- * OPENED_SPAN_WITH_CLASS + 'text-class' + '">' + 'lorem ipsum' + '</span>';
9
- * // Output: '<span class="text-class">lorem ipsum</span>'
10
- * ```
11
- */
12
- export const OPENED_SPAN_WITH_CLASS = '<span class="';
13
-
14
- /**
15
- *
16
- * String with closed `span` HTML element.
17
- *
18
- *
19
- * @example
20
- * ```typescript
21
- * '<span class="' + 'text-class' + '">' + 'dolor' + CLOSED_SPAN;
22
- * // Output: <span class="text-class">dolor</span>
23
- * ```
24
- */
25
- export const CLOSED_SPAN = '</span>';
@@ -1,199 +0,0 @@
1
- import { OPENED_SPAN_WITH_CLASS, CLOSED_SPAN } from './constants';
2
-
3
- import type { Token } from '../tokenizer';
4
- import type { HighlightCSSClasses } from './types';
5
-
6
- export const generate = (
7
- tokens: Token[],
8
- cssClasses: HighlightCSSClasses
9
- ): string => {
10
- const tokensLength = tokens.length;
11
-
12
- let generated: string =
13
- '<pre class="' +
14
- cssClasses.pre +
15
- '"><code class="' +
16
- cssClasses.code +
17
- '"><div class="' +
18
- cssClasses.line +
19
- '">';
20
-
21
- /**
22
- *
23
- * Boolean flag that shows is the current line opened.
24
- * Needed for closing opened line in the end of loop.
25
- */
26
- let isLineOpened: boolean = true;
27
-
28
- let tokenPos = 0;
29
- while (tokenPos < tokensLength) {
30
- const currentToken = tokens[tokenPos];
31
-
32
- if (currentToken.type === 'WhiteSpace') {
33
- generated +=
34
- OPENED_SPAN_WITH_CLASS +
35
- cssClasses.token +
36
- ' ' +
37
- cssClasses.whitespace +
38
- '">';
39
-
40
- let charPos = 0;
41
- while (charPos < currentToken.value.length) {
42
- generated += ' ';
43
- charPos++;
44
- }
45
-
46
- generated += CLOSED_SPAN;
47
-
48
- tokenPos++;
49
-
50
- continue;
51
- }
52
-
53
- if (currentToken.type === 'LineDivision') {
54
- generated += '</div>';
55
-
56
- if (tokenPos !== tokensLength - 1) {
57
- generated += '<div class="' + cssClasses.line + '">';
58
- isLineOpened = false;
59
- }
60
-
61
- tokenPos++;
62
-
63
- continue;
64
- }
65
-
66
- if (currentToken.type === 'Operator') {
67
- generated +=
68
- OPENED_SPAN_WITH_CLASS +
69
- cssClasses.token +
70
- ' ' +
71
- cssClasses.operator +
72
- '">' +
73
- currentToken.value +
74
- CLOSED_SPAN;
75
-
76
- tokenPos++;
77
-
78
- continue;
79
- }
80
-
81
- if (currentToken.type === 'Identifier') {
82
- generated +=
83
- OPENED_SPAN_WITH_CLASS +
84
- cssClasses.token +
85
- ' ' +
86
- cssClasses.mutableIdentifier +
87
- '">' +
88
- currentToken.value +
89
- CLOSED_SPAN;
90
-
91
- tokenPos++;
92
-
93
- continue;
94
- }
95
-
96
- if (currentToken.type === 'Keyword') {
97
- generated +=
98
- OPENED_SPAN_WITH_CLASS +
99
- cssClasses.token +
100
- ' ' +
101
- cssClasses.keyword +
102
- '">' +
103
- currentToken.value +
104
- CLOSED_SPAN;
105
-
106
- tokenPos++;
107
-
108
- continue;
109
- }
110
-
111
- // literals
112
- if (currentToken.type === 'StringLiteral') {
113
- generated +=
114
- OPENED_SPAN_WITH_CLASS +
115
- cssClasses.token +
116
- ' ' +
117
- cssClasses.stringLiteral +
118
- '">' +
119
- currentToken.value +
120
- CLOSED_SPAN;
121
-
122
- tokenPos++;
123
-
124
- continue;
125
- }
126
-
127
- if (currentToken.type === 'NumberLiteral') {
128
- generated +=
129
- OPENED_SPAN_WITH_CLASS +
130
- cssClasses.token +
131
- ' ' +
132
- cssClasses.numberLiteral +
133
- '">' +
134
- currentToken.value +
135
- CLOSED_SPAN;
136
-
137
- tokenPos++;
138
-
139
- continue;
140
- }
141
-
142
- if (currentToken.type === 'BooleanLiteral') {
143
- generated +=
144
- OPENED_SPAN_WITH_CLASS +
145
- cssClasses.token +
146
- ' ' +
147
- cssClasses.booleanLiteral +
148
- '">' +
149
- currentToken.value +
150
- CLOSED_SPAN;
151
-
152
- tokenPos++;
153
-
154
- continue;
155
- }
156
-
157
- if (currentToken.type === 'NaNLiteral') {
158
- generated +=
159
- OPENED_SPAN_WITH_CLASS +
160
- cssClasses.token +
161
- ' ' +
162
- cssClasses.NaNLiteral +
163
- '">' +
164
- currentToken.value +
165
- CLOSED_SPAN;
166
-
167
- tokenPos++;
168
-
169
- continue;
170
- }
171
-
172
- // comments
173
- if (currentToken.type === 'Comment') {
174
- generated +=
175
- OPENED_SPAN_WITH_CLASS +
176
- cssClasses.token +
177
- ' ' +
178
- cssClasses.comment +
179
- '">' +
180
- currentToken.value +
181
- CLOSED_SPAN;
182
-
183
- tokenPos++;
184
-
185
- continue;
186
- }
187
-
188
- // fallback
189
- tokenPos++;
190
- }
191
-
192
- if (isLineOpened) {
193
- generated += '</div>';
194
- }
195
-
196
- generated += '</code></pre>';
197
-
198
- return generated;
199
- };
@@ -1,2 +0,0 @@
1
- export { generate } from './generate';
2
- export type { HighlightCSSClasses } from './types';
@@ -1,73 +0,0 @@
1
- /**
2
- * Type of CSS classes object.
3
- *
4
- * Classes are used in generated HTML.
5
- *
6
- * CSS modules also can be used
7
- *
8
- * &nbsp;
9
- *
10
- * Default generated HTML structure:
11
- * ```html
12
- * <pre>
13
- * <code>
14
- * <div> ... </div> <!-- line -->
15
- *
16
- * ...
17
- * </code>
18
- * </pre>
19
- * ```
20
- *
21
- * @example
22
- * const CSSClasses: HighlightCSSClasses = {
23
- * token: 'my-token-classname',
24
- * comment: 'my-comment-classname',
25
- * ...
26
- * };
27
- */
28
- export type HighlightCSSClasses = Partial<{
29
- /**
30
- *
31
- *
32
- * `pre` class is used for root wrapper element
33
- */
34
- pre: string;
35
-
36
- /**
37
- * `code` class is used for generated code text element
38
- */
39
- code: string;
40
-
41
- /**
42
- * `line` class is used for every line of generated code
43
- */
44
- line: string;
45
-
46
- /**
47
- *
48
- */
49
-
50
- /**
51
- * `token` class is used for every token of code, even whitespace
52
- */
53
- token: string;
54
-
55
- // trivia
56
- whitespace: string;
57
- comment: string;
58
-
59
- keyword: string;
60
-
61
- operator: string;
62
-
63
- // identifiers
64
- constantIdentifier: string;
65
- mutableIdentifier: string;
66
-
67
- // literals
68
- numberLiteral: string;
69
- stringLiteral: string;
70
- booleanLiteral: string;
71
- NaNLiteral: string;
72
- bigintChar: string;
73
- }>;
@@ -1,54 +0,0 @@
1
- import { tokenize } from '../tokenizer';
2
- import { generate } from '../generator';
3
- import type { HighlightCSSClasses } from '../generator';
4
-
5
- /**
6
- *
7
- * Transforms received `source` string to HTML with higlighted source code.
8
- *
9
- * Classes could be divided with space as if they are in default HTML `class` attribute.
10
- *
11
- * @param {string} source JavaScript or TypeScript source code to highlight.
12
- * @param {HiglightCSSClasses} cssClasses object with CSS classes.
13
- *
14
- * @returns {string} string with HTML of highlighted code.
15
- *
16
- * @example
17
- * ```typescript
18
- * const cssClasses: HighlightCSSClasses = {
19
- * pre: 'pre-element-class second-class',
20
- * code: 'my-code-element-class',
21
- *
22
- * line: 'line-class my-line third-class',
23
- * token: 'token-class',
24
- *
25
- * operator: 'operator-class',
26
- *
27
- * keyword: 'keyword-class',
28
- * stringLiteral: 'string-class',
29
- *
30
- * ...
31
- * }
32
- *
33
- * highlight('let a = "hello";', cssClasses);
34
- * ```
35
- * Output will be:
36
- * ```html
37
- * <pre class="pre-element-class">
38
- * <code class="code-element-class">
39
- * <div class="line-class"> ... </div> <!-- code line with generated tokens inside -->
40
- * </code>
41
- * </pre>
42
- * ```
43
- */
44
- export const highlight = (
45
- source: string,
46
-
47
- cssClasses: HighlightCSSClasses
48
- ): string => {
49
- const tokens = tokenize(source);
50
-
51
- const generated = generate(tokens, cssClasses);
52
-
53
- return generated;
54
- };
@@ -1 +0,0 @@
1
- export { highlight } from './highlight';
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { highlight } from './highlight';
2
- export type { HighlightCSSClasses } from './generator';
@@ -1,152 +0,0 @@
1
- import type {
2
- TokenSet,
3
- SingleOperators,
4
- DoubleOperators,
5
- TripleOperators,
6
- QuadrupleOperator,
7
- Keywords,
8
- } from './types';
9
-
10
- // operators
11
-
12
- const singleOperatorsInit: SingleOperators = [
13
- '=',
14
- '+',
15
- '-',
16
- '*',
17
- '/',
18
- '%',
19
- '~',
20
- '^',
21
- '.',
22
- ':',
23
- '|',
24
- '&',
25
- '?',
26
- '!',
27
- '<',
28
- '>',
29
- '{',
30
- '}',
31
- '[',
32
- ']',
33
- '(',
34
- ')',
35
- ';',
36
- ',',
37
- ];
38
- const doubleOperatorsInit: DoubleOperators = [
39
- '==',
40
- '!=',
41
-
42
- '<=',
43
- '>=',
44
-
45
- '++',
46
- '--',
47
- '**',
48
-
49
- '*=',
50
- '/=',
51
- '%=',
52
-
53
- '^=',
54
- '&=',
55
- '|=',
56
-
57
- '&&',
58
- '||',
59
- '??',
60
- ];
61
- const tripleOperatorsInit: TripleOperators = [
62
- '===',
63
- '!==',
64
-
65
- '**=',
66
- '<<=',
67
- '>>=',
68
- '>>>',
69
- '&&=',
70
- '||=',
71
- '??=',
72
- ];
73
-
74
- /**
75
- * `Set` with all javascript one-symbol operators
76
- *
77
- * @example '='
78
- */
79
- export const singleOperators: TokenSet = new Set(singleOperatorsInit);
80
-
81
- /**
82
- * `Set` with all javascript two-symbol operators
83
- *
84
- * @example '++'
85
- */
86
- export const doubleOperators: TokenSet = new Set(doubleOperatorsInit);
87
-
88
- /**
89
- * `Set` with all javascript three-symbol operators
90
- *
91
- * @example '>>>'
92
- */
93
- export const tripleOperators: TokenSet = new Set(tripleOperatorsInit);
94
-
95
- /**
96
- * The javascript four-symbol operator.
97
- *
98
- * Always equals '>>>=' on December 18, 2025 ECMAScript Specification
99
- */
100
- export const quadrupleOperator: QuadrupleOperator = '>>>=';
101
-
102
- // keywords
103
-
104
- const keywordsInit: Keywords = [
105
- 'var',
106
- 'let',
107
- 'const',
108
- 'typeof',
109
- 'class',
110
-
111
- 'in',
112
-
113
- 'new',
114
-
115
- 'instanceof',
116
- 'function',
117
-
118
- 'void',
119
- 'delete',
120
- 'keyof',
121
- 'abstract',
122
- 'interface',
123
- 'enum',
124
- 'type',
125
- 'implements',
126
- ];
127
-
128
- /**
129
- * `Set` with javascript and typescript keywords
130
- */
131
- export const keywords: TokenSet = new Set(keywordsInit);
132
-
133
- // regular expresions (RegExp)
134
-
135
- /**
136
- * RegExp that is used to match javascript identifier start symbol
137
- *
138
- */
139
-
140
- export const IDENTIFIER_START_REGEXP: RegExp = /^[a-zA-Zа-яА-Я_$]$/;
141
-
142
- /**
143
- * RegExp that is used to match javascript identifier symbols after the first symbol
144
- */
145
-
146
- export const IDENTIFIER_REGEXP: RegExp = /^[a-zA-Zа-яА-Я_$0-9]$/;
147
-
148
- /**
149
- *
150
- * RegExp that is used to match any number
151
- */
152
- export const NUMBER_REGEXP: RegExp = /^[0-9]$/;
@@ -1,2 +0,0 @@
1
- export type { TokenType, Token } from './types';
2
- export { tokenize } from './tokenize';
@@ -1,302 +0,0 @@
1
- import {
2
- IDENTIFIER_START_REGEXP,
3
- IDENTIFIER_REGEXP,
4
- NUMBER_REGEXP,
5
- singleOperators,
6
- doubleOperators,
7
- tripleOperators,
8
- quadrupleOperator,
9
- keywords,
10
- } from './constants';
11
-
12
- import type { Token } from './types';
13
-
14
- /**
15
- *
16
- * *Tokenizer* or *Lexer* function.
17
- *
18
- *
19
- * Divides `source` to tokens.
20
- *
21
- * @param {string} source - javascript or typescript source code to tokenize.
22
- *
23
- * @returns {Token[]} array with tokens from `source`.
24
- */
25
- export const tokenize = (source: string): Token[] => {
26
- const tokens: Token[] = [];
27
-
28
- const sourceLength = source.length;
29
-
30
- let pos = 0;
31
- main: while (pos < sourceLength) {
32
- if (source[pos] === ' ' || source[pos] === '\t') {
33
- const startPos = pos;
34
-
35
- pos++;
36
-
37
- while (
38
- pos < sourceLength &&
39
- (source[pos] === ' ' || source[pos] === '\t')
40
- ) {
41
- pos++;
42
- }
43
-
44
- tokens[tokens.length] = {
45
- type: 'WhiteSpace',
46
-
47
- value: source.slice(startPos, pos),
48
-
49
- start: startPos,
50
-
51
- end: pos,
52
- };
53
-
54
- continue main;
55
- }
56
-
57
- if (source[pos] === '\n' || source[pos] === '\r') {
58
- const startPos = pos;
59
-
60
- if (source[pos] === '\r') {
61
- pos++;
62
- }
63
-
64
- pos++;
65
-
66
- tokens[tokens.length] = {
67
- type: 'LineDivision',
68
-
69
- value: '\n',
70
-
71
- start: startPos,
72
- end: pos,
73
- };
74
-
75
- continue main;
76
- }
77
-
78
- // literals
79
- if (IDENTIFIER_START_REGEXP.test(source[pos])) {
80
- const startPos = pos;
81
- pos++;
82
-
83
- while (pos < sourceLength && IDENTIFIER_REGEXP.test(source[pos])) {
84
- pos++;
85
- }
86
-
87
- const identifier = source.slice(startPos, pos);
88
-
89
- tokens[tokens.length] = {
90
- type: keywords.has(identifier) ? 'Keyword' : 'Identifier',
91
- value: identifier,
92
- start: startPos,
93
- end: pos,
94
- };
95
-
96
- continue main;
97
- }
98
-
99
- if (source[pos] === "'" || source[pos] === '"' || source[pos] === '`') {
100
- const startPos = pos;
101
- const startQuote = source[pos];
102
-
103
- pos++;
104
-
105
- while (pos < sourceLength && source[pos] !== startQuote) {
106
- pos++;
107
- }
108
-
109
- pos++;
110
-
111
- tokens[tokens.length] = {
112
- type: 'StringLiteral',
113
- value: source.slice(startPos, pos),
114
- start: startPos,
115
- end: pos,
116
- };
117
-
118
- continue main;
119
- }
120
- if (NUMBER_REGEXP.test(source[pos])) {
121
- const startPos = pos;
122
-
123
- while (pos < sourceLength && NUMBER_REGEXP.test(source[pos])) {
124
- pos++;
125
- }
126
-
127
- tokens[tokens.length] = {
128
- type: 'NumberLiteral',
129
- value: source.slice(startPos, pos),
130
- start: startPos,
131
- end: pos,
132
- };
133
-
134
- continue main;
135
- }
136
-
137
- // comments
138
- if (source[pos] === '/') {
139
- const startPos = pos;
140
-
141
- pos++;
142
-
143
- if (source[pos] === '/') {
144
- pos++;
145
-
146
- while (
147
- pos < sourceLength &&
148
- source[pos] !== '\r' &&
149
- source[pos] !== '\n'
150
- ) {
151
- pos++;
152
- }
153
-
154
- if (source[pos] === '\r') {
155
- pos += 2;
156
- }
157
-
158
- tokens[tokens.length] = {
159
- type: 'Comment',
160
-
161
- value: source.slice(startPos, pos),
162
- start: startPos,
163
- end: pos,
164
- };
165
-
166
- tokens[tokens.length] = {
167
- type: 'LineDivision',
168
-
169
- value: '\n',
170
-
171
- start: startPos,
172
- end: pos,
173
- };
174
-
175
- continue main;
176
- }
177
-
178
- if (source[pos] === '*') {
179
- pos++;
180
-
181
- let lastCommentStart = startPos;
182
-
183
- while (
184
- pos < sourceLength &&
185
- !(source[pos] === '*' && source[pos + 1] === '/')
186
- ) {
187
- if (source[pos] === '\n' || source[pos] === '\r') {
188
- tokens[tokens.length] = {
189
- type: 'Comment',
190
-
191
- value: source.slice(lastCommentStart, pos),
192
-
193
- start: startPos,
194
- end: pos,
195
- };
196
- tokens[tokens.length] = {
197
- type: 'LineDivision',
198
-
199
- value: '\n',
200
-
201
- start: pos,
202
- end: pos + 1,
203
- };
204
-
205
- if (source[pos] === '\r') {
206
- pos++;
207
- }
208
-
209
- pos++;
210
- lastCommentStart = pos;
211
- }
212
-
213
- pos++;
214
- }
215
-
216
- pos += 2;
217
-
218
- tokens[tokens.length] = {
219
- type: 'Comment',
220
- value: source.slice(lastCommentStart, pos),
221
- start: startPos,
222
- end: pos,
223
- };
224
-
225
- continue main;
226
- }
227
- }
228
-
229
- // operators
230
- if (
231
- source[pos] +
232
- source[pos + 1] +
233
- source[pos + 2] +
234
- source[pos + 3] ===
235
- quadrupleOperator
236
- ) {
237
- const startPos = pos;
238
-
239
- pos += 4;
240
-
241
- tokens[tokens.length] = {
242
- type: 'Operator',
243
- value: source.slice(startPos, pos),
244
- start: startPos,
245
- end: pos,
246
- };
247
-
248
- continue main;
249
- }
250
-
251
- if (
252
- tripleOperators.has(source[pos] + source[pos + 1] + source[pos + 2])
253
- ) {
254
- const startPos = pos;
255
-
256
- pos += 3;
257
-
258
- tokens[tokens.length] = {
259
- type: 'Operator',
260
- value: source.slice(startPos, pos),
261
- start: startPos,
262
- end: pos,
263
- };
264
-
265
- continue main;
266
- }
267
-
268
- if (doubleOperators.has(source[pos] + source[pos + 1])) {
269
- const startPos = pos;
270
-
271
- pos += 2;
272
-
273
- tokens[tokens.length] = {
274
- type: 'Operator',
275
- value: source.slice(startPos, pos),
276
- start: startPos,
277
- end: pos,
278
- };
279
-
280
- continue main;
281
- }
282
-
283
- if (singleOperators.has(source[pos])) {
284
- const startPos = pos;
285
-
286
- pos++;
287
-
288
- tokens[tokens.length] = {
289
- type: 'Operator',
290
- value: source.slice(startPos, pos),
291
- start: startPos,
292
- end: pos,
293
- };
294
-
295
- continue main;
296
- }
297
-
298
- pos++;
299
- }
300
-
301
- return tokens;
302
- };
@@ -1,150 +0,0 @@
1
- type LiteralTokenType =
2
- | 'NumberLiteral'
3
- | 'StringLiteral'
4
- | 'BooleanLiteral'
5
- | 'NaNLiteral';
6
-
7
- /**
8
- * Variety of `Token` types
9
- */
10
- export type TokenType =
11
- | 'Identifier'
12
- | 'Keyword'
13
- | 'Operator'
14
- | 'WhiteSpace'
15
- | 'Comment'
16
- | 'LineDivision'
17
- | LiteralTokenType;
18
-
19
- /**
20
- *
21
- */
22
- export type Token = {
23
- type: TokenType;
24
- value: string;
25
-
26
- /**
27
- * Start position of token value in source code
28
- */
29
- start: number;
30
-
31
- /**
32
- * The end of token value in source code
33
- */
34
- end: number;
35
- };
36
-
37
- export type SingleOperators = [
38
- '=',
39
- '+',
40
- '-',
41
- '*',
42
- '/',
43
- '%',
44
- '~',
45
- '^',
46
- '.',
47
- ':',
48
- '|',
49
- '&',
50
- '?',
51
- '!',
52
- '<',
53
- '>',
54
-
55
- '{',
56
- '}',
57
- '[',
58
- ']',
59
- '(',
60
- ')',
61
-
62
- ';',
63
- ','
64
- ];
65
- export type SingleOperator = SingleOperators[number];
66
-
67
- export type DoubleOperators = [
68
- '==',
69
-
70
- '!=',
71
- '<=',
72
- '>=',
73
- '++',
74
- '--',
75
- '**',
76
- '*=',
77
- '/=',
78
- '%=',
79
- '^=',
80
- '&=',
81
- '|=',
82
-
83
- '&&',
84
- '||',
85
- '??'
86
- ];
87
- export type DoubleOperator = DoubleOperators[number];
88
-
89
- export type TripleOperators = [
90
- '===',
91
- '!==',
92
- '**=',
93
-
94
- '<<=',
95
- '>>=',
96
- '>>>',
97
-
98
- '&&=',
99
- '||=',
100
- '??='
101
- ];
102
- export type TripleOperator = TripleOperators[number];
103
-
104
- export type QuadrupleOperator = '>>>=';
105
-
106
- export type JSKeywords = [
107
- 'var',
108
- 'let',
109
-
110
- 'const',
111
-
112
- 'typeof',
113
-
114
- 'class',
115
- 'in',
116
-
117
- 'new',
118
- 'instanceof',
119
-
120
- 'function',
121
- 'void',
122
- 'delete'
123
- ];
124
- export type JSKeyword = JSKeywords[number];
125
- export type TSKeywords = [
126
- 'keyof',
127
- 'abstract',
128
- 'interface',
129
- 'enum',
130
- 'type',
131
- 'implements'
132
- ];
133
- export type TSKeyword = TSKeywords[number];
134
-
135
- export type Keywords = [...JSKeywords, ...TSKeywords];
136
- export type Keyword = Keywords[number];
137
-
138
- /**
139
- *
140
- * Type that contains `TokenType` values to be checked in tokenizer.
141
- *
142
- *
143
- * @example
144
- * ```typescript
145
- * const singleOperatorsInit: SingleOperators = ['=', '+', '-' ...];
146
- *
147
- * const singleOperators: TokenSet = new Set(singleOperatorsInit);
148
- * ```
149
- */
150
- export type TokenSet = Set<string>;
package/tsconfig.json DELETED
@@ -1,28 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "lib": ["ESNext"],
4
- "target": "ESNext",
5
- "module": "esnext",
6
- "moduleDetection": "force",
7
-
8
- "moduleResolution": "bundler",
9
-
10
- "verbatimModuleSyntax": true,
11
-
12
- "noEmit": true,
13
-
14
- "strict": true,
15
- "skipLibCheck": true,
16
- "noFallthroughCasesInSwitch": true,
17
- "noUncheckedIndexedAccess": false,
18
- "noPropertyAccessFromIndexSignature": true,
19
- "noImplicitOverride": true,
20
-
21
- "noUnusedLocals": true,
22
- "noUnusedParameters": true,
23
-
24
- "outDir": "dist"
25
- },
26
-
27
- "exclude": ["node_modules", "dist"]
28
- }