|
1 |
| -import { BlockFactory, CssBlockError, Importer, NodeJsImporter, Preprocessors, hasErrorPosition } from "@css-blocks/core"; |
| 1 | +import { BlockFactory, CssBlockError, ErrorWithPosition, Importer, NodeJsImporter, Preprocessors, hasErrorPosition } from "@css-blocks/core"; |
2 | 2 | import chalk = require("chalk");
|
3 | 3 | import fse = require("fs-extra");
|
4 | 4 | import path = require("path");
|
5 | 5 | import yargs = require("yargs");
|
6 | 6 |
|
| 7 | +import { ExtractionResult, extractLinesFromSource } from "./extract-lines-from-source"; |
| 8 | + |
7 | 9 | type Aliases = ConstructorParameters<typeof NodeJsImporter>[0];
|
8 | 10 |
|
9 | 11 | /**
|
@@ -123,33 +125,100 @@ export class CLI {
|
123 | 125 | let factory = new BlockFactory({preprocessors, importer});
|
124 | 126 | let errorCount = 0;
|
125 | 127 | for (let blockFile of blockFiles) {
|
| 128 | + let blockFileRelative = path.relative(process.cwd(), path.resolve(blockFile)); |
126 | 129 | try {
|
127 | 130 | if (importer) {
|
128 | 131 | let ident = importer.identifier(null, blockFile, factory.configuration);
|
129 |
| - blockFile = importer.filesystemPath(ident, factory.configuration) || blockFile; |
| 132 | + blockFile = importer.filesystemPath(ident, factory.configuration) || path.join(blockFile); |
130 | 133 | }
|
131 |
| - await factory.getBlockFromPath(blockFile); |
| 134 | + await factory.getBlockFromPath(path.resolve(blockFile)); |
132 | 135 | // if the above line doesn't throw then there wasn't a syntax error.
|
133 |
| - this.println(`${this.chalk.green("ok")}\t${path.relative(process.cwd(), path.resolve(blockFile))}`); |
| 136 | + this.println(`${this.chalk.green("ok")}\t${this.chalk.whiteBright(blockFileRelative)}`); |
134 | 137 | } catch (e) {
|
135 | 138 | errorCount++;
|
136 | 139 | if (e instanceof CssBlockError) {
|
137 | 140 | let loc = e.location;
|
138 |
| - let filename = path.relative(process.cwd(), path.resolve(loc && loc.filename || blockFile)); |
139 |
| - let message = `${this.chalk.red("error")}\t${this.chalk.whiteBright(filename)}`; |
140 |
| - if (hasErrorPosition(loc)) { |
141 |
| - message += `:${loc.start.line}:${loc.start.column}`; |
| 141 | + let message = `${this.chalk.red("error")}\t${this.chalk.whiteBright(blockFileRelative)}`; |
| 142 | + if (!hasErrorPosition(loc)) { |
| 143 | + this.println(message, e.origMessage); |
| 144 | + continue; |
| 145 | + } else { |
| 146 | + this.println(message); |
| 147 | + this.displayError(blockFileRelative, e); |
142 | 148 | }
|
143 |
| - message += ` ${e.origMessage}`; |
144 |
| - this.println(message); |
145 | 149 | } else {
|
146 | 150 | console.error(e);
|
147 | 151 | }
|
148 | 152 | }
|
149 | 153 | }
|
| 154 | + if (errorCount) { |
| 155 | + this.println(`Found ${this.chalk.redBright(`${errorCount} error${errorCount > 1 ? "s" : ""}`)} in ${blockFiles.length} file${blockFiles.length > 1 ? "s" : ""}.`); |
| 156 | + } |
150 | 157 | this.exit(errorCount);
|
151 | 158 | }
|
152 | 159 |
|
| 160 | + displayError(blockFileRelative: string, e: CssBlockError) { |
| 161 | + let loc = e.location; |
| 162 | + if (!hasErrorPosition(loc)) { |
| 163 | + return; |
| 164 | + } |
| 165 | + loc.end.line = 4; |
| 166 | + let filename = path.relative(process.cwd(), path.resolve(loc && loc.filename || blockFileRelative)); |
| 167 | + let context: ExtractionResult | undefined; |
| 168 | + let lineNumber: number | undefined; |
| 169 | + context = extractLinesFromSource(loc, 1, 1); |
| 170 | + lineNumber = loc.start.line - context.additionalLines.before; |
| 171 | + if (context) { |
| 172 | + this.println( |
| 173 | + this.chalk.bold.white("\tAt"), |
| 174 | + this.chalk.bold.whiteBright(`${filename}:${loc.start.line}:${loc.start.column}`), |
| 175 | + `${e.origMessage}`, |
| 176 | + ); |
| 177 | + for (let i = 0; i < context.lines.length; i++) { |
| 178 | + let prefix; |
| 179 | + let line = context.lines[i]; |
| 180 | + if (i < context.additionalLines.before || |
| 181 | + i >= context.lines.length - context.additionalLines.after) { |
| 182 | + prefix = this.chalk.bold(`${lineNumber}: `); |
| 183 | + } else { |
| 184 | + prefix = this.chalk.bold.redBright(`${lineNumber}: `); |
| 185 | + let {before, during, after } = this.splitLineOnErrorRange(line, lineNumber, loc); |
| 186 | + line = `${before}${this.chalk.underline.redBright(during)}${after}`; |
| 187 | + } |
| 188 | + this.println("\t" + prefix + line); |
| 189 | + if (lineNumber) lineNumber++; |
| 190 | + } |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + splitLineOnErrorRange(line: string, lineNumber: number, loc: ErrorWithPosition) { |
| 195 | + if (lineNumber === loc.start.line && lineNumber === loc.end.line) { |
| 196 | + let before = line.slice(0, loc.start.column - 1); |
| 197 | + let during = line.slice(loc.start.column - 1, loc.end.column); |
| 198 | + let after = line.slice(loc.end.column); |
| 199 | + return { before, during, after }; |
| 200 | + } else if (lineNumber === loc.start.line) { |
| 201 | + let before = line.slice(0, loc.start.column - 1); |
| 202 | + let during = line.slice(loc.start.column - 1); |
| 203 | + return { before, during, after: "" }; |
| 204 | + } else if (lineNumber === loc.end.line) { |
| 205 | + let leadingWhitespace = ""; |
| 206 | + let during = line.slice(0, loc.end.column); |
| 207 | + if (during.match(/^(\s+)/)) { |
| 208 | + leadingWhitespace = RegExp.$1; |
| 209 | + during = during.replace(/^\s+/, ""); |
| 210 | + } |
| 211 | + let after = line.slice(loc.end.column); |
| 212 | + return {before: leadingWhitespace, during, after }; |
| 213 | + } else { |
| 214 | + let leadingWhitespace = ""; |
| 215 | + if (line.match(/^(\s+)/)) { |
| 216 | + leadingWhitespace = RegExp.$1; |
| 217 | + line = line.replace(/^\s+/, ""); |
| 218 | + } |
| 219 | + return {before: leadingWhitespace, during: line, after: "" }; |
| 220 | + } |
| 221 | + } |
153 | 222 | /**
|
154 | 223 | * Instance method so tests can easily capture output.
|
155 | 224 | */
|
|
0 commit comments