Skip to content

Commit b7a7ef3

Browse files
committed
Better error messages
1 parent 2ef5951 commit b7a7ef3

File tree

12 files changed

+299
-24
lines changed

12 files changed

+299
-24
lines changed

lib/Bindings/Chalk.fs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
module Chalk
2+
3+
#nowarn "3390" // disable warnings for invalid XML comments
4+
5+
open System
6+
open Fable.Core
7+
open Fable.Core.JS
8+
9+
type ColorSupportLevel =
10+
| L0 = 0
11+
| L1 = 1
12+
| L2 = 2
13+
| L3 = 3
14+
15+
type ColorInfo = U2<ColorSupportLevel, bool>
16+
17+
type [<AllowNullLiteral>] ChalkInstance =
18+
[<Emit("$0($1...)")>] abstract draw: [<ParamArray>] text: obj[] -> string
19+
[<Emit("$0($1...)")>] abstract Item: [<ParamArray>] text: obj[] -> string
20+
/// <summary>
21+
/// The color support for Chalk.
22+
///
23+
/// By default, color support is automatically detected based on the environment.
24+
///
25+
/// Levels:
26+
/// - <c>0</c> - All colors disabled.
27+
/// - <c>1</c> - Basic 16 colors support.
28+
/// - <c>2</c> - ANSI 256 colors support.
29+
/// - <c>3</c> - Truecolor 16 million colors support.
30+
/// </summary>
31+
abstract level: ColorSupportLevel with get, set
32+
/// <summary>Use RGB values to set text color.</summary>
33+
/// <example>
34+
/// <code>
35+
/// import chalk from 'chalk';
36+
/// chalk.rgb(222, 173, 237);
37+
/// </code>
38+
/// </example>
39+
abstract rgb: float * float * float -> ChalkInstance
40+
/// <summary>Use HEX value to set text color.</summary>
41+
/// <param name="color">Hexadecimal value representing the desired color.</param>
42+
/// <example>
43+
/// <code>
44+
/// import chalk from 'chalk';
45+
/// chalk.hex('#DEADED');
46+
/// </code>
47+
/// </example>
48+
abstract hex: string -> ChalkInstance
49+
/// <summary>Use an <see href="https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit">8-bit unsigned number</see> to set text color.</summary>
50+
/// <example>
51+
/// <code>
52+
/// import chalk from 'chalk';
53+
/// chalk.ansi256(201);
54+
/// </code>
55+
/// </example>
56+
abstract ansi256: float -> ChalkInstance
57+
/// <summary>Use RGB values to set background color.</summary>
58+
/// <example>
59+
/// <code>
60+
/// import chalk from 'chalk';
61+
/// chalk.bgRgb(222, 173, 237);
62+
/// </code>
63+
/// </example>
64+
abstract bgRgb: float * float * float -> ChalkInstance
65+
/// <summary>Use HEX value to set background color.</summary>
66+
/// <param name="color">Hexadecimal value representing the desired color.</param>
67+
/// <example>
68+
/// <code>
69+
/// import chalk from 'chalk';
70+
/// chalk.bgHex('#DEADED');
71+
/// </code>
72+
/// </example>
73+
abstract bgHex: string -> ChalkInstance
74+
/// <summary>Use a <see href="https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit">8-bit unsigned number</see> to set background color.</summary>
75+
/// <example>
76+
/// <code>
77+
/// import chalk from 'chalk';
78+
/// chalk.bgAnsi256(201);
79+
/// </code>
80+
/// </example>
81+
abstract bgAnsi256: float -> ChalkInstance
82+
/// Modifier: Reset the current style.
83+
abstract reset: ChalkInstance
84+
/// Modifier: Make the text bold.
85+
abstract bold: ChalkInstance
86+
/// Modifier: Make the text have lower opacity.
87+
abstract dim: ChalkInstance
88+
/// Modifier: Make the text italic. *(Not widely supported)*
89+
abstract italic: ChalkInstance
90+
/// Modifier: Put a horizontal line below the text. *(Not widely supported)*
91+
abstract underline: ChalkInstance
92+
/// Modifier: Put a horizontal line above the text. *(Not widely supported)*
93+
abstract overline: ChalkInstance
94+
/// Modifier: Invert background and foreground colors.
95+
abstract inverse: ChalkInstance
96+
/// Modifier: Print the text but make it invisible.
97+
abstract hidden: ChalkInstance
98+
/// Modifier: Puts a horizontal line through the center of the text. *(Not widely supported)*
99+
abstract strikethrough: ChalkInstance
100+
/// Modifier: Print the text only when Chalk has a color level above zero.
101+
///
102+
/// Can be useful for things that are purely cosmetic.
103+
abstract visible: ChalkInstance
104+
abstract black: ChalkInstance
105+
abstract red: ChalkInstance
106+
abstract green: ChalkInstance
107+
abstract yellow: ChalkInstance
108+
abstract blue: ChalkInstance
109+
abstract magenta: ChalkInstance
110+
abstract cyan: ChalkInstance
111+
abstract white: ChalkInstance
112+
abstract gray: ChalkInstance
113+
abstract grey: ChalkInstance
114+
abstract blackBright: ChalkInstance
115+
abstract redBright: ChalkInstance
116+
abstract greenBright: ChalkInstance
117+
abstract yellowBright: ChalkInstance
118+
abstract blueBright: ChalkInstance
119+
abstract magentaBright: ChalkInstance
120+
abstract cyanBright: ChalkInstance
121+
abstract whiteBright: ChalkInstance
122+
abstract bgBlack: ChalkInstance
123+
abstract bgRed: ChalkInstance
124+
abstract bgGreen: ChalkInstance
125+
abstract bgYellow: ChalkInstance
126+
abstract bgBlue: ChalkInstance
127+
abstract bgMagenta: ChalkInstance
128+
abstract bgCyan: ChalkInstance
129+
abstract bgWhite: ChalkInstance
130+
abstract bgGray: ChalkInstance
131+
abstract bgGrey: ChalkInstance
132+
abstract bgBlackBright: ChalkInstance
133+
abstract bgRedBright: ChalkInstance
134+
abstract bgGreenBright: ChalkInstance
135+
abstract bgYellowBright: ChalkInstance
136+
abstract bgBlueBright: ChalkInstance
137+
abstract bgMagentaBright: ChalkInstance
138+
abstract bgCyanBright: ChalkInstance
139+
abstract bgWhiteBright: ChalkInstance
140+
141+
/// <summary>
142+
/// Main Chalk object that allows to chain styles together.
143+
///
144+
/// Call the last one as a method with a string argument.
145+
///
146+
/// Order doesn't matter, and later styles take precedent in case of a conflict.
147+
///
148+
/// This simply means that <c>chalk.red.yellow.green</c> is equivalent to <c>chalk.green</c>.
149+
/// </summary>
150+
let [<ImportDefault("chalk")>] chalk: ChalkInstance = jsNative
151+
let [<Import("chalkStderr","chalk")>] chalkStderr: ChalkInstance = jsNative
152+
let [<Import("supportsColor","chalk")>] supportsColor: ColorInfo = jsNative
153+
let [<Import("supportsColorStderr","chalk")>] supportsColorStderr: ColorInfo = jsNative

lib/Bindings/Codeframe.fs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
module Codeframe
2+
3+
#nowarn "3390" // disable warnings for invalid XML comments
4+
5+
open System
6+
open Fable.Core
7+
open Fable.Core.JS
8+
9+
type Position = {| line: int; column: int option |}
10+
11+
type [<AllowNullLiteral>] SourceLocation =
12+
abstract start: Position with get, set
13+
abstract ``end``: Position option with get, set
14+
15+
type [<AllowNullLiteral>] BabelCodeFrameOptions =
16+
/// Syntax highlight the code as JavaScript for terminals. default: false
17+
abstract highlightCode: bool option with get, set
18+
/// The number of lines to show above the error. default: 2
19+
abstract linesAbove: int option with get, set
20+
/// The number of lines to show below the error. default: 3
21+
abstract linesBelow: int option with get, set
22+
/// Forcibly syntax highlight the code as JavaScript (for non-terminals);
23+
/// overrides highlightCode.
24+
/// default: false
25+
abstract forceColor: bool option with get, set
26+
/// Pass in a string to be displayed inline (if possible) next to the
27+
/// highlighted location in the code. If it can't be positioned inline,
28+
/// it will be placed above the code frame.
29+
/// default: nothing
30+
abstract message: string option with get, set
31+
32+
type [<AllowNullLiteral>] ICodeframe =
33+
[<Emit("$0($1...)")>]
34+
abstract Invoke: rawLines: string * lineNumber: int * colNumber: int * ?options: BabelCodeFrameOptions -> string
35+
36+
type [<AllowNullLiteral>] ICodeframeColumns =
37+
[<Emit("$0($1...)")>]
38+
abstract Invoke: rawLines: string * location: SourceLocation * ?options: BabelCodeFrameOptions -> string
39+
40+
let [<ImportDefault("@babel/code-frame")>] codeFrame: ICodeframe = jsNative
41+
42+
let [<Import("codeFrameColumns", "@babel/code-frame")>] codeFrameColumns: ICodeframeColumns = jsNative
43+
44+
type Codeframe =
45+
static member CreateColumns(rawLines, startLine, ?startCol, ?endLine, ?endCol, ?highlightCode, ?linesAbove, ?linesBelow, ?forceColor, ?message) =
46+
let options : BabelCodeFrameOptions = JsInterop.createEmpty
47+
options.highlightCode <- highlightCode
48+
options.linesAbove <- linesAbove
49+
options.linesBelow <- linesBelow
50+
options.forceColor <- forceColor
51+
options.message <- message
52+
let startPos = {| line = startLine; column = startCol |}
53+
let endPos =
54+
match endLine with
55+
| Some x -> Some {| line = x; column = endCol |}
56+
| None -> None
57+
let loc : SourceLocation = JsInterop.createEmpty
58+
loc.start <- startPos
59+
loc.``end`` <- endPos
60+
codeFrameColumns.Invoke(rawLines, loc, options)
61+
static member Create(rawLines, line, col, ?highlightCode, ?linesAbove, ?linesBelow, ?forceColor, ?message) =
62+
let options : BabelCodeFrameOptions = JsInterop.createEmpty
63+
options.highlightCode <- highlightCode
64+
options.linesAbove <- linesAbove
65+
options.linesBelow <- linesBelow
66+
options.forceColor <- forceColor
67+
options.message <- message
68+
codeFrame.Invoke(rawLines, line, col, options)

lib/Extensions.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ module Path =
220220
if Node.path.isAbsolute(path) then path |> normalizeSlashes
221221
else Node.path.resolve(path) |> normalizeSlashes
222222

223+
let relativeToCwd (path: string) =
224+
if Node.path.isAbsolute(path) then
225+
Node.path.relative(Node.``process``.cwd(), path) |> normalizeSlashes
226+
else path |> normalizeSlashes
227+
223228
let diff (fromPath: Absolute) (toPath: Absolute) : string =
224229
let fromPath =
225230
if Node.fs.lstatSync(!^fromPath).isDirectory() then fromPath

lib/Parser.fs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,21 @@ module private ParserImpl =
3232
let ppLocation (n: Node) =
3333
let src = n.getSourceFile()
3434
let pos = src.getLineAndCharacterOfPosition (n.getStart())
35-
sprintf "line %i, col %i of %s" (int pos.line + 1) (int pos.character + 1) src.fileName
35+
sprintf "line %i, col %i of %s" (int pos.line + 1) (int pos.character + 1) (Path.relativeToCwd src.fileName)
3636

3737
let ppLine (n: Node) =
3838
let src = n.getSourceFile()
39-
let pos = src.getLineAndCharacterOfPosition (n.getStart())
40-
let startPos = int <| src.getPositionOfLineAndCharacter(pos.line, 0.)
41-
let endPos = int <| src.getLineEndOfPosition(n.getEnd())
42-
let lines =
43-
src.text.Substring(startPos, endPos - startPos) |> String.toLines
44-
lines |> Array.map (sprintf "> %s") |> String.concat System.Environment.NewLine
39+
let startPos = src.getLineAndCharacterOfPosition(n.getStart())
40+
let endPos = src.getLineAndCharacterOfPosition(n.getEnd())
41+
let text = src.text
42+
Codeframe.Codeframe.CreateColumns(
43+
text,
44+
int startPos.line + 1,
45+
int startPos.character + 1,
46+
int endPos.line + 1,
47+
int endPos.character + 1,
48+
linesAbove=0, linesBelow=0
49+
)
4550

4651
let nodeWarn (ctx: ParserContext) (node: Node) format =
4752
Printf.kprintf (fun s ->
@@ -1114,12 +1119,12 @@ let createDependencyGraph (sourceFiles: Ts.SourceFile[]) =
11141119
for source in sourceFiles do goSourceFile source
11151120
graph
11161121

1117-
let assertFileExistsAndHasCorrectExtension (fileName: string) =
1122+
let assertFileExistsAndHasCorrectExtension (ctx: #IContext<#IOptions>) (fileName: string) =
11181123
assertNode ()
11191124
if not <| Node.fs.existsSync(!^fileName) then
1120-
failwithf "file '%s' does not exist" fileName
1121-
if fileName.EndsWith(".d.ts") |> not then
1122-
failwithf "file '%s' is not a TypeScript declaration file" fileName
1125+
ctx.logger.errorf "file '%s' does not exist" fileName
1126+
if fileName.EndsWith(".ts") |> not then
1127+
ctx.logger.errorf "file '%s' is not a TypeScript file" fileName
11231128
fileName
11241129

11251130
let createContextFromFiles (ctx: #IContext<#IOptions>) compilerOptions (fileNames: string[]) : ParserContext =
@@ -1129,11 +1134,11 @@ let createContextFromFiles (ctx: #IContext<#IOptions>) compilerOptions (fileName
11291134
if not ctx.options.followRelativeReferences then
11301135
fileNames
11311136
|> Array.map Path.absolute
1132-
|> Array.map assertFileExistsAndHasCorrectExtension
1137+
|> Array.map (assertFileExistsAndHasCorrectExtension ctx)
11331138
else
11341139
fileNames
11351140
|> Array.map Path.absolute
1136-
|> Array.map assertFileExistsAndHasCorrectExtension
1141+
|> Array.map (assertFileExistsAndHasCorrectExtension ctx)
11371142
|> Array.map (fun a -> a, Node.fs.readFileSync(a, "utf-8"))
11381143
|> Array.map (fun (a, i) ->
11391144
ts.createSourceFile (a, i, !^Ts.ScriptTarget.Latest, setParentNodes=true, scriptKind=Ts.ScriptKind.TS))

lib/Syntax.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ with
4545
sprintf "line %i, col %i of %s"
4646
(int pos.line + 1)
4747
(int pos.character + 1)
48-
src.fileName
48+
(Path.relativeToCwd src.fileName)
4949
| Location l ->
5050
sprintf "line %i, col %i of %s"
5151
(int l.line + 1)
5252
(int l.character + 1)
53-
l.src.fileName
53+
(Path.relativeToCwd l.src.fileName)
5454
| MultipleLocation l ->
5555
l |> List.map (fun x -> x.AsString) |> String.concat " and "
5656
| UnknownLocation -> "<unknown>"

lib/Typer.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ type TyperContext<'Options, 'State when 'Options :> IOptions> = private {
136136
member this.logger = this.logger
137137

138138
let inline private warn (ctx: IContext<_>) (loc: Location) fmt =
139-
Printf.kprintf (fun s -> ctx.logger.warnf "%s at %s" s loc.AsString) fmt
139+
Printf.kprintf (fun s -> ctx.logger.warnf "%s at %s" s (Path.relativeToCwd loc.AsString)) fmt
140140

141141
module TyperContext =
142142
type private Anonoymous<'Options, 'State when 'Options :> IOptions> = {|

lib/ts2ml.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
<ItemGroup>
66
<Compile Include="Bindings/TypeScript.fs" />
77
<Compile Include="Bindings/BrowserOrNode.fs" />
8+
<Compile Include="Bindings/Chalk.fs" />
9+
<Compile Include="Bindings/Codeframe.fs" />
810
<Compile Include="Extensions.fs" />
911
<Compile Include="DataTypes/Text.fs" />
1012
<Compile Include="DataTypes/Trie.fs" />

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@
2727
"ts2ocaml": "./dist/ts2ocaml.js"
2828
},
2929
"dependencies": {
30+
"@babel/code-frame": "^7.18.6",
3031
"browser-or-node": "^2.0.0",
32+
"chalk": "^5.0.1",
3133
"typescript": "4.7",
3234
"yargs": "17.5.1"
3335
},
3436
"devDependencies": {
3537
"@angular/common": "^13.0.3",
3638
"@babel/core": "7.18.2",
39+
"@types/babel__code-frame": "^7.0.3",
3740
"@types/react-modal": "3.13.1",
3841
"@types/semver": "7.3.9",
3942
"@types/vscode": "^1.63.1",

src/Common.fs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,22 @@ module Log =
4545
let warnf (opt: 'Options) fmt : _ when 'Options :> GlobalOptions =
4646
Printf.ksprintf (fun str ->
4747
if not opt.nowarn then
48-
eprintfn "warn: %s" str
48+
eprintfn "%s %s" (Chalk.chalk.yellow["warn:"]) str
49+
) fmt
50+
51+
let errorf fmt =
52+
Printf.ksprintf (fun str ->
53+
eprintfn "%s %s" (Chalk.chalk.red["error:"]) str
54+
Node.Api.``process``.exit -1
55+
failwith str
4956
) fmt
5057

5158
let createBaseContext (opts: #GlobalOptions) : IContext<_> =
5259
let logger =
5360
{ new ILogger with
5461
member _.tracef fmt = Log.tracef opts fmt
5562
member _.warnf fmt = Log.warnf opts fmt
56-
member _.errorf fmt = failwithf fmt
63+
member _.errorf fmt = Log.errorf fmt
5764
}
5865
{ new IContext<_> with
5966
member _.options = opts

src/Targets/JsOfOCaml/Target.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ let private run (input: Input) (ctx: IContext<Options>) =
2222
if Node.Api.path.isAbsolute dir then dir
2323
else Node.Api.path.join [|curdir; dir|]
2424
let fail () =
25-
failwithf "The output directory '%s' does not exist." path
25+
ctx.logger.errorf "The output directory '%s' does not exist." path
2626
try
2727
if Node.Api.fs.lstatSync(!^path).isDirectory() then path
2828
else fail ()
@@ -60,7 +60,7 @@ let private run (input: Input) (ctx: IContext<Options>) =
6060
.Split([|Node.Api.os.EOL|], System.StringSplitOptions.RemoveEmptyEntries)
6161
|> Set.ofArray
6262
else
63-
failwithf "The path '%s' is not a file." stubFile
63+
ctx.logger.errorf "The path '%s' is not a file." stubFile
6464
let stubLines = Set.union existingStubLines newStubLines
6565
if stubLines <> existingStubLines then
6666
ctx.logger.tracef "* writing the stub file to '%s'..." stubFile

0 commit comments

Comments
 (0)