@reteps/tree-sitter-htmlmustache 0.0.30 → 0.0.34
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 +87 -31
- package/cli/out/main.js +3613 -0
- package/package.json +11 -5
- package/cli/out/check.js +0 -235
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reteps/tree-sitter-htmlmustache",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.34",
|
|
4
4
|
"description": "HTML with Mustache/Handlebars template syntax grammar for tree-sitter",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
},
|
|
13
13
|
"main": "bindings/node",
|
|
14
14
|
"bin": {
|
|
15
|
-
"htmlmustache": "cli/out/
|
|
15
|
+
"htmlmustache": "cli/out/main.js"
|
|
16
16
|
},
|
|
17
17
|
"types": "bindings/node",
|
|
18
18
|
"keywords": [
|
|
@@ -36,12 +36,17 @@
|
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"chalk": "^4.1.2",
|
|
39
|
+
"editorconfig": "^2.0.1",
|
|
39
40
|
"node-addon-api": "^8.2.2",
|
|
40
41
|
"node-gyp-build": "^4.8.2",
|
|
41
|
-
"tree-sitter": "^0.25.0"
|
|
42
|
+
"tree-sitter": "^0.25.0",
|
|
43
|
+
"vscode-languageserver-textdocument": "^1.0.12",
|
|
44
|
+
"web-tree-sitter": "^0.25.0"
|
|
42
45
|
},
|
|
43
46
|
"devDependencies": {
|
|
47
|
+
"@eslint/js": "^9.39.2",
|
|
44
48
|
"@types/node": "^22.19.11",
|
|
49
|
+
"esbuild": "^0.25.0",
|
|
45
50
|
"eslint": "^9.14.0",
|
|
46
51
|
"eslint-config-treesitter": "^1.0.2",
|
|
47
52
|
"prebuildify": "^6.0.1",
|
|
@@ -49,6 +54,7 @@
|
|
|
49
54
|
"tree-sitter": "^0.25.0",
|
|
50
55
|
"tree-sitter-cli": "^0.26.3",
|
|
51
56
|
"typescript": "^5.7.0",
|
|
57
|
+
"typescript-eslint": "^8.54.0",
|
|
52
58
|
"vitest": "^3.0.0"
|
|
53
59
|
},
|
|
54
60
|
"pnpm": {
|
|
@@ -61,8 +67,8 @@
|
|
|
61
67
|
"scripts": {
|
|
62
68
|
"install": "node-gyp-build",
|
|
63
69
|
"build": "tree-sitter build --wasm",
|
|
64
|
-
"prepack": "
|
|
65
|
-
"build:cli": "
|
|
70
|
+
"prepack": "node cli/esbuild.mjs",
|
|
71
|
+
"build:cli": "node cli/esbuild.mjs",
|
|
66
72
|
"check": "node cli/out/check.js check",
|
|
67
73
|
"lint": "eslint .",
|
|
68
74
|
"format": "prettier --write .",
|
package/cli/out/check.js
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.collectErrors = collectErrors;
|
|
8
|
-
exports.formatError = formatError;
|
|
9
|
-
exports.formatSummary = formatSummary;
|
|
10
|
-
exports.expandGlobs = expandGlobs;
|
|
11
|
-
exports.run = run;
|
|
12
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
15
|
-
function loadParser() {
|
|
16
|
-
let Parser;
|
|
17
|
-
try {
|
|
18
|
-
Parser = require('tree-sitter');
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
console.error(chalk_1.default.red('Error: tree-sitter is not installed.\n') +
|
|
22
|
-
'Install it with: npm install tree-sitter');
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
let language;
|
|
26
|
-
try {
|
|
27
|
-
language = require(node_path_1.default.resolve(__dirname, '..', '..'));
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
console.error(chalk_1.default.red('Error: could not load tree-sitter-htmlmustache bindings.\n') +
|
|
31
|
-
'Run "npm install" in the tree-sitter-htmlmustache root first.');
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
const parser = new Parser();
|
|
35
|
-
parser.setLanguage(language);
|
|
36
|
-
return parser;
|
|
37
|
-
}
|
|
38
|
-
// ── Error collection ──
|
|
39
|
-
function errorMessageForNode(nodeType, node) {
|
|
40
|
-
if (nodeType === 'mustache_erroneous_section_end' || nodeType === 'mustache_erroneous_inverted_section_end') {
|
|
41
|
-
const tagNameNode = node.children.find((c) => c.type === 'mustache_erroneous_tag_name');
|
|
42
|
-
return `Mismatched mustache section: {{/${tagNameNode?.text || '?'}}}`;
|
|
43
|
-
}
|
|
44
|
-
if (nodeType === 'html_erroneous_end_tag') {
|
|
45
|
-
const tagNameNode = node.children.find((c) => c.type === 'html_erroneous_end_tag_name');
|
|
46
|
-
return `Mismatched HTML end tag: </${tagNameNode?.text || '?'}>`;
|
|
47
|
-
}
|
|
48
|
-
if (nodeType === 'ERROR') {
|
|
49
|
-
return 'Syntax error';
|
|
50
|
-
}
|
|
51
|
-
// isMissing node
|
|
52
|
-
return `Missing ${nodeType}`;
|
|
53
|
-
}
|
|
54
|
-
const ERROR_NODE_TYPES = new Set([
|
|
55
|
-
'ERROR',
|
|
56
|
-
'mustache_erroneous_section_end',
|
|
57
|
-
'mustache_erroneous_inverted_section_end',
|
|
58
|
-
'html_erroneous_end_tag',
|
|
59
|
-
]);
|
|
60
|
-
function collectErrors(tree, file) {
|
|
61
|
-
const errors = [];
|
|
62
|
-
const cursor = tree.walk();
|
|
63
|
-
function visit() {
|
|
64
|
-
const node = cursor.currentNode;
|
|
65
|
-
const nodeType = cursor.nodeType;
|
|
66
|
-
if (ERROR_NODE_TYPES.has(nodeType) || cursor.nodeIsMissing) {
|
|
67
|
-
errors.push({
|
|
68
|
-
file,
|
|
69
|
-
line: node.startPosition.row + 1,
|
|
70
|
-
column: node.startPosition.column + 1,
|
|
71
|
-
endLine: node.endPosition.row + 1,
|
|
72
|
-
endColumn: node.endPosition.column + 1,
|
|
73
|
-
message: errorMessageForNode(nodeType, node),
|
|
74
|
-
nodeText: node.text,
|
|
75
|
-
});
|
|
76
|
-
// Don't recurse into ERROR nodes — the children are not meaningful
|
|
77
|
-
if (nodeType === 'ERROR')
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (cursor.gotoFirstChild()) {
|
|
81
|
-
do {
|
|
82
|
-
visit();
|
|
83
|
-
} while (cursor.gotoNextSibling());
|
|
84
|
-
cursor.gotoParent();
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
visit();
|
|
88
|
-
return errors;
|
|
89
|
-
}
|
|
90
|
-
// ── Formatting ──
|
|
91
|
-
function formatError(error, source) {
|
|
92
|
-
const lines = source.split('\n');
|
|
93
|
-
const errorLine = error.line - 1; // 0-based index
|
|
94
|
-
// Location header
|
|
95
|
-
const header = chalk_1.default.bold(`${error.file}:${error.line}:${error.column}`) +
|
|
96
|
-
' ' + chalk_1.default.red('error') + ': ' + error.message;
|
|
97
|
-
// Context lines: up to 2 before + the error line(s)
|
|
98
|
-
const contextStart = Math.max(0, errorLine - 2);
|
|
99
|
-
const contextEnd = Math.min(lines.length - 1, error.endLine - 1);
|
|
100
|
-
const gutterWidth = String(contextEnd + 1).length;
|
|
101
|
-
const pad = (n) => String(n).padStart(gutterWidth);
|
|
102
|
-
const outputLines = [header];
|
|
103
|
-
outputLines.push(chalk_1.default.dim(' '.repeat(gutterWidth) + ' |'));
|
|
104
|
-
for (let i = contextStart; i <= contextEnd; i++) {
|
|
105
|
-
const lineNum = i + 1;
|
|
106
|
-
outputLines.push(chalk_1.default.dim(`${pad(lineNum)} |`) + ' ' + lines[i]);
|
|
107
|
-
}
|
|
108
|
-
// Underline: only on the last displayed line of the error
|
|
109
|
-
const lastErrorLineIdx = error.endLine - 1;
|
|
110
|
-
const lastLine = lines[lastErrorLineIdx] || '';
|
|
111
|
-
let underlineStart;
|
|
112
|
-
let underlineEnd;
|
|
113
|
-
if (error.line === error.endLine) {
|
|
114
|
-
// Single-line error
|
|
115
|
-
underlineStart = error.column - 1;
|
|
116
|
-
underlineEnd = error.endColumn - 1;
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
// Multi-line: underline to end of last line
|
|
120
|
-
underlineStart = 0;
|
|
121
|
-
underlineEnd = lastLine.length;
|
|
122
|
-
}
|
|
123
|
-
const underlineLength = Math.max(1, underlineEnd - underlineStart);
|
|
124
|
-
const underline = ' '.repeat(underlineStart) + '^'.repeat(underlineLength) +
|
|
125
|
-
' ' + error.message;
|
|
126
|
-
outputLines.push(chalk_1.default.dim(' '.repeat(gutterWidth) + ' |') + ' ' + chalk_1.default.red(underline));
|
|
127
|
-
return outputLines.join('\n');
|
|
128
|
-
}
|
|
129
|
-
function formatSummary(totalErrors, filesWithErrors, totalFiles) {
|
|
130
|
-
if (totalErrors === 0) {
|
|
131
|
-
return chalk_1.default.green(`No errors found (${totalFiles} ${totalFiles === 1 ? 'file' : 'files'} checked)`);
|
|
132
|
-
}
|
|
133
|
-
const errStr = totalErrors === 1 ? 'error' : 'errors';
|
|
134
|
-
const errFileStr = filesWithErrors === 1 ? 'file' : 'files';
|
|
135
|
-
const totalStr = totalFiles === 1 ? 'file' : 'files';
|
|
136
|
-
return chalk_1.default.red(`${totalErrors} ${errStr} in ${filesWithErrors} ${errFileStr}`) +
|
|
137
|
-
` (${totalFiles} ${totalStr} checked)`;
|
|
138
|
-
}
|
|
139
|
-
// ── Glob expansion ──
|
|
140
|
-
function expandGlobs(patterns) {
|
|
141
|
-
const files = new Set();
|
|
142
|
-
const cwd = process.cwd();
|
|
143
|
-
for (const pattern of patterns) {
|
|
144
|
-
// If the pattern is an exact file path, use it directly
|
|
145
|
-
if (!pattern.includes('*') && !pattern.includes('?')) {
|
|
146
|
-
const resolved = node_path_1.default.resolve(cwd, pattern);
|
|
147
|
-
if (node_fs_1.default.existsSync(resolved)) {
|
|
148
|
-
files.add(resolved);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
for (const match of node_fs_1.default.globSync(pattern, { cwd })) {
|
|
153
|
-
files.add(node_path_1.default.resolve(cwd, match));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return [...files].sort();
|
|
158
|
-
}
|
|
159
|
-
// ── Main ──
|
|
160
|
-
const USAGE = `Usage: htmlmustache check <patterns...>
|
|
161
|
-
|
|
162
|
-
Check HTML Mustache templates for errors.
|
|
163
|
-
|
|
164
|
-
Arguments:
|
|
165
|
-
patterns One or more glob patterns (e.g. '**/*.mustache' '**/*.hbs')
|
|
166
|
-
|
|
167
|
-
Options:
|
|
168
|
-
--help Show this help message
|
|
169
|
-
|
|
170
|
-
Examples:
|
|
171
|
-
htmlmustache check '**/*.mustache'
|
|
172
|
-
htmlmustache check 'templates/**/*.hbs' 'partials/**/*.mustache'`;
|
|
173
|
-
function run(args) {
|
|
174
|
-
// Strip "check" subcommand if present
|
|
175
|
-
if (args[0] === 'check') {
|
|
176
|
-
args = args.slice(1);
|
|
177
|
-
}
|
|
178
|
-
if (args.includes('--help') || args.length === 0) {
|
|
179
|
-
console.log(USAGE);
|
|
180
|
-
return args.includes('--help') ? 0 : 1;
|
|
181
|
-
}
|
|
182
|
-
const files = expandGlobs(args);
|
|
183
|
-
if (files.length === 0) {
|
|
184
|
-
console.error(chalk_1.default.yellow('No files matched the given patterns:'));
|
|
185
|
-
for (const arg of args) {
|
|
186
|
-
console.error(chalk_1.default.yellow(` ${arg}`));
|
|
187
|
-
}
|
|
188
|
-
return 1;
|
|
189
|
-
}
|
|
190
|
-
const parser = loadParser();
|
|
191
|
-
let totalErrors = 0;
|
|
192
|
-
let filesWithErrors = 0;
|
|
193
|
-
const cwd = process.cwd();
|
|
194
|
-
const errorOutput = [];
|
|
195
|
-
for (const file of files) {
|
|
196
|
-
const displayPath = node_path_1.default.relative(cwd, file) || file;
|
|
197
|
-
const source = node_fs_1.default.readFileSync(file, 'utf-8');
|
|
198
|
-
const tree = parser.parse(source);
|
|
199
|
-
const errors = collectErrors(tree, displayPath);
|
|
200
|
-
if (errors.length > 0) {
|
|
201
|
-
filesWithErrors++;
|
|
202
|
-
totalErrors += errors.length;
|
|
203
|
-
for (const error of errors) {
|
|
204
|
-
errorOutput.push(formatError(error, source));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
console.log(errors.length > 0
|
|
208
|
-
? chalk_1.default.red(displayPath)
|
|
209
|
-
: chalk_1.default.dim(displayPath));
|
|
210
|
-
}
|
|
211
|
-
if (errorOutput.length > 0) {
|
|
212
|
-
console.log();
|
|
213
|
-
for (const output of errorOutput) {
|
|
214
|
-
console.log(output);
|
|
215
|
-
console.log();
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
console.log(formatSummary(totalErrors, filesWithErrors, files.length));
|
|
219
|
-
return totalErrors > 0 ? 1 : 0;
|
|
220
|
-
}
|
|
221
|
-
// CLI entry point
|
|
222
|
-
if (require.main === module) {
|
|
223
|
-
const args = process.argv.slice(2);
|
|
224
|
-
if (args[0] !== 'check' && !args.includes('--help')) {
|
|
225
|
-
if (args.length === 0) {
|
|
226
|
-
console.log(USAGE);
|
|
227
|
-
process.exit(1);
|
|
228
|
-
}
|
|
229
|
-
console.error(chalk_1.default.red(`Unknown command: ${args[0]}`));
|
|
230
|
-
console.error('Run "htmlmustache --help" for usage.');
|
|
231
|
-
process.exit(1);
|
|
232
|
-
}
|
|
233
|
-
const code = run(args);
|
|
234
|
-
process.exit(code);
|
|
235
|
-
}
|