Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#### :nail_care: Polish

- Rewatch cli: do not show build command options in the root help. https://github.com/rescript-lang/rescript/pull/7715
- Deprecate reanalyze `@raises` in favor of `@throws`. https://github.com/rescript-lang/rescript/pull/7932

#### :house: Internal

Expand Down
26 changes: 6 additions & 20 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,30 +334,15 @@ Note that there's currently still a manual step involved on [rescript-lang.org](

## Contribute to the API Reference

The API reference is generated from doc comments in the source code. [Here](https://github.com/rescript-lang/rescript-compiler/blob/99650/jscomp/others/js_re.mli#L146-L161)'s a good example.
The API reference is generated from doc comments in the source code. [Here](https://github.com/rescript-lang/rescript/blob/57c696b1a38f53badaddcc082ed29188d80df70d/packages/%40rescript/runtime/Stdlib_String.resi#L441-L458)'s a good example.

Some tips:

- The first sentence or line should be a very short summary. This is used in indexes and by tools like merlin.
- Ideally, every function should have **at least one** `@example`.
- Cross-reference another definition with `{! identifier}`. But use them sparingly, they’re a bit verbose (currently, at least).
- Wrap non-cross-referenced identifiers and other code in `[ ... ]`.
- Escape `{`, `}`, `[`, `]` and `@` using `\`.
- It’s possible to use `{%html ...}` to generate custom html, but use this very, very sparingly.
- A number of "documentation tags" are provided that would be nice to use, but unfortunately they’re often not supported for \`external\`s. Which is of course most of the API.
- `@param` usually doesn’t work. Use `{b <param>} ...` instead
- `@returns` usually doesn’t work. Use `{b returns} ...` instead.
- The first sentence or line should show the function call with a very short summary.
- Ideally, every function should have an `## Examples` section with **at least one** example. The examples are compiled to check that they are correct. Use `==` to generate tests from the examples.
- Always use `@deprecated` when applicable.
- Always use `@raise` when applicable.
- Always provide a `@see` tag pointing to MDN for more information when available.

See [Ocamldoc documentation](http://caml.inria.fr/pub/docs/manual-ocaml/ocamldoc.html#sec333) for more details.

To generate the html:

```sh
../scripts/ninja docs
```
- Always use `@throw` when applicable.
- Always provide a `See` section pointing to MDN for more information when available.

## Contribute to JSX `domProps`

Expand Down Expand Up @@ -389,6 +374,7 @@ Adding a new entry there requires re-running the analysis tests. Follow these st
(If a `make` command fails, consider using the [DevContainer](#b-devcontainer).)

Finally, add a line to [CHANGELOG.md](CHANGELOG.md), using the `#### :nail_care: Polish` section.

## Code structure

The highlevel architecture is illustrated as below:
Expand Down
6 changes: 3 additions & 3 deletions analysis/reanalyze/src/Common.ml
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,12 @@ type line = {mutable declarations: decl list; original: string}

module ExnSet = Set.Make (Exn)

type missingRaiseInfo = {
type missingThrowInfo = {
exnName: string;
exnTable: (Exn.t, LocSet.t) Hashtbl.t;
locFull: Location.t;
missingAnnotations: ExnSet.t;
raiseSet: ExnSet.t;
throwSet: ExnSet.t;
}

type severity = Warning | Error
Expand All @@ -234,7 +234,7 @@ type lineAnnotation = (decl * line) option
type description =
| Circular of {message: string}
| ExceptionAnalysis of {message: string}
| ExceptionAnalysisMissing of missingRaiseInfo
| ExceptionAnalysisMissing of missingThrowInfo
| DeadModule of {message: string}
| DeadOptional of {deadOptional: deadOptional; message: string}
| DeadWarning of {
Expand Down
78 changes: 41 additions & 37 deletions analysis/reanalyze/src/Exception.ml
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ module Event = struct
type kind =
| Catches of t list (* with | E => ... *)
| Call of {callee: Common.Path.t; modulePath: Common.Path.t} (* foo() *)
| DoesNotRaise of
t list (* DoesNotRaise(events) where events come from an expression *)
| Raises (** raise E *)
| DoesNotThrow of
t list (* DoesNotThrow(events) where events come from an expression *)
| Throws (** raise E *)

and t = {exceptions: Exceptions.t; kind: kind; loc: Location.t}

Expand All @@ -81,14 +81,14 @@ module Event = struct
(modulePath |> Common.Path.toString)
(Exceptions.pp ~exnTable:None)
exceptions
| {kind = DoesNotRaise nestedEvents; loc} ->
Format.fprintf ppf "%s DoesNotRaise(%a)@."
| {kind = DoesNotThrow nestedEvents; loc} ->
Format.fprintf ppf "%s DoesNotThrow(%a)@."
(loc.loc_start |> posToString)
(fun ppf () ->
nestedEvents |> List.iter (fun e -> Format.fprintf ppf "%a " print e))
()
| {kind = Raises; exceptions; loc} ->
Format.fprintf ppf "%s raises %a@."
| {kind = Throws; exceptions; loc} ->
Format.fprintf ppf "%s throws %a@."
(loc.loc_start |> posToString)
(Exceptions.pp ~exnTable:None)
exceptions
Expand Down Expand Up @@ -118,7 +118,7 @@ module Event = struct
in
let rec loop exnSet events =
match events with
| ({kind = Raises; exceptions; loc} as ev) :: rest ->
| ({kind = Throws; exceptions; loc} as ev) :: rest ->
if !Common.Cli.debug then Log_.item "%a@." print ev;
exceptions |> Exceptions.iter (fun exn -> extendExnTable exn loc);
loop (Exceptions.union exnSet exceptions) rest
Expand All @@ -134,7 +134,7 @@ module Event = struct
in
exceptions |> Exceptions.iter (fun exn -> extendExnTable exn loc);
loop (Exceptions.union exnSet exceptions) rest
| ({kind = DoesNotRaise nestedEvents; loc} as ev) :: rest ->
| ({kind = DoesNotThrow nestedEvents; loc} as ev) :: rest ->
if !Common.Cli.debug then Log_.item "%a@." print ev;
let nestedExceptions = loop Exceptions.empty nestedEvents in
(if Exceptions.isEmpty nestedExceptions (* catch-all *) then
Expand All @@ -148,8 +148,8 @@ module Event = struct
{
message =
Format.asprintf
"@{<info>%s@} does not raise and is annotated with \
redundant @doesNotRaise"
"@{<info>%s@} does not throw and is annotated with \
redundant @doesNotThrow"
(name |> Name.toString);
}));
loop exnSet rest
Expand Down Expand Up @@ -188,13 +188,13 @@ module Checks = struct
checks := {events; exceptions; loc; locFull; moduleName; exnName} :: !checks

let doCheck {events; exceptions; loc; locFull; moduleName; exnName} =
let raiseSet, exnTable = events |> Event.combine ~moduleName in
let missingAnnotations = Exceptions.diff raiseSet exceptions in
let redundantAnnotations = Exceptions.diff exceptions raiseSet in
let throwSet, exnTable = events |> Event.combine ~moduleName in
let missingAnnotations = Exceptions.diff throwSet exceptions in
let redundantAnnotations = Exceptions.diff exceptions throwSet in
(if not (Exceptions.isEmpty missingAnnotations) then
let description =
Common.ExceptionAnalysisMissing
{exnName; exnTable; raiseSet; missingAnnotations; locFull}
{exnName; exnTable; throwSet; missingAnnotations; locFull}
in
Log_.warning ~loc description);
if not (Exceptions.isEmpty redundantAnnotations) then
Expand All @@ -203,15 +203,15 @@ module Checks = struct
{
message =
(let raisesDescription ppf () =
if raiseSet |> Exceptions.isEmpty then
if throwSet |> Exceptions.isEmpty then
Format.fprintf ppf "raises nothing"
else
Format.fprintf ppf "might raise %a"
(Exceptions.pp ~exnTable:(Some exnTable))
raiseSet
throwSet
in
Format.asprintf
"@{<info>%s@} %a and is annotated with redundant @raises(%a)"
"@{<info>%s@} %a and is annotated with redundant @throws(%a)"
exnName raisesDescription ()
(Exceptions.pp ~exnTable:None)
redundantAnnotations);
Expand Down Expand Up @@ -249,7 +249,7 @@ let traverseAst () =
case.c_guard |> iterExprOpt self;
case.c_rhs |> iterExpr self)
in
let isRaise s = s = "Pervasives.raise" || s = "Pervasives.throw" in
let isThrow s = s = "Pervasives.raise" || s = "Pervasives.throw" in
let raiseArgs args =
match args with
| [(_, Some {Typedtree.exp_desc = Texp_construct ({txt}, _, _)})] ->
Expand All @@ -258,26 +258,29 @@ let traverseAst () =
[Exn.fromString "genericException"] |> Exceptions.fromList
| _ -> [Exn.fromString "TODO_from_raise1"] |> Exceptions.fromList
in
let doesNotRaise attributes =
let doesNotThrow attributes =
attributes
|> Annotation.getAttributePayload (fun s ->
s = "doesNotRaise" || s = "doesnotraise" || s = "DoesNoRaise"
|| s = "doesNotraise" || s = "doNotRaise" || s = "donotraise"
|| s = "DoNoRaise" || s = "doNotraise")
|> Annotation.getAttributePayload (function
| "doesNotRaise" | "doesnotraise" | "DoesNoRaise" | "doesNotraise"
| "doNotRaise" | "donotraise" | "DoNoRaise" | "doNotraise"
| "doesNotThrow" | "doesnotthrow" | "DoesNoThrow" | "doesNotthrow"
| "doNotThrow" | "donotthrow" | "DoNoThrow" | "doNotthrow" ->
true
| _ -> false)
<> None
in
let expr (self : Tast_mapper.mapper) (expr : Typedtree.expression) =
let loc = expr.exp_loc in
let isDoesNoRaise = expr.exp_attributes |> doesNotRaise in
let isDoesNoThrow = expr.exp_attributes |> doesNotThrow in
let oldEvents = !currentEvents in
if isDoesNoRaise then currentEvents := [];
if isDoesNoThrow then currentEvents := [];
(match expr.exp_desc with
| Texp_ident (callee_, _, _) ->
let callee =
callee_ |> Common.Path.fromPathT |> ModulePath.resolveAlias
in
let calleeName = callee |> Common.Path.toName in
if calleeName |> Name.toString |> isRaise then
if calleeName |> Name.toString |> isThrow then
Log_.warning ~loc
(Common.ExceptionAnalysis
{
Expand All @@ -299,17 +302,17 @@ let traverseAst () =
args = [(_lbl1, Some {exp_desc = Texp_ident (callee, _, _)}); arg];
}
when (* raise @@ Exn(...) *)
atat |> Path.name = "Pervasives.@@" && callee |> Path.name |> isRaise
atat |> Path.name = "Pervasives.@@" && callee |> Path.name |> isThrow
->
let exceptions = [arg] |> raiseArgs in
currentEvents := {Event.exceptions; loc; kind = Raises} :: !currentEvents;
currentEvents := {Event.exceptions; loc; kind = Throws} :: !currentEvents;
arg |> snd |> iterExprOpt self
| Texp_apply {funct = {exp_desc = Texp_ident (callee, _, _)} as e; args} ->
let calleeName = Path.name callee in
if calleeName |> isRaise then
if calleeName |> isThrow then
let exceptions = args |> raiseArgs in
currentEvents :=
{Event.exceptions; loc; kind = Raises} :: !currentEvents
{Event.exceptions; loc; kind = Throws} :: !currentEvents
else e |> iterExpr self;
args |> List.iter (fun (_, eOpt) -> eOpt |> iterExprOpt self)
| Texp_match (e, casesOk, casesExn, partial) ->
Expand All @@ -332,7 +335,7 @@ let traverseAst () =
{
Event.exceptions = [Exn.matchFailure] |> Exceptions.fromList;
loc;
kind = Raises;
kind = Throws;
}
:: !currentEvents
| Texp_try (e, cases) ->
Expand All @@ -348,21 +351,22 @@ let traverseAst () =
{Event.exceptions; loc; kind = Catches !currentEvents} :: oldEvents;
cases |> iterCases self
| _ -> super.expr self expr |> ignore);
(if isDoesNoRaise then
(if isDoesNoThrow then
let nestedEvents = !currentEvents in
currentEvents :=
{
Event.exceptions = Exceptions.empty;
loc;
kind = DoesNotRaise nestedEvents;
kind = DoesNotThrow nestedEvents;
}
:: oldEvents);
expr
in
let getExceptionsFromAnnotations attributes =
let raisesAnnotationPayload =
let throwsAnnotationPayload =
attributes
|> Annotation.getAttributePayload (fun s -> s = "raises" || s = "raise")
|> Annotation.getAttributePayload (fun s ->
s = "throws" || s = "throw" || s = "raises" || s = "raise")
in
let rec getExceptions payload =
match payload with
Expand All @@ -379,7 +383,7 @@ let traverseAst () =
|> List.concat |> Exceptions.fromList
| _ -> Exceptions.empty
in
match raisesAnnotationPayload with
match throwsAnnotationPayload with
| None -> Exceptions.empty
| Some payload -> payload |> getExceptions
in
Expand Down
16 changes: 8 additions & 8 deletions analysis/reanalyze/src/Log_.ml
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ let missingRaiseInfoToText {missingAnnotations; locFull} =
Format.asprintf "%a" (Exceptions.pp ~exnTable:None) missingAnnotations
in
if !Cli.json then
EmitJson.emitAnnotate ~action:"Add @raises annotation"
EmitJson.emitAnnotate ~action:"Add @throws annotation"
~pos:(EmitJson.locToPos locFull)
~text:(Format.asprintf "@raises(%s)\\n" missingTxt)
~text:(Format.asprintf "@throws(%s)\\n" missingTxt)
else ""

let logAdditionalInfo ~(description : description) =
Expand All @@ -117,17 +117,17 @@ let logAdditionalInfo ~(description : description) =
missingRaiseInfoToText missingRaiseInfo
| _ -> ""

let missingRaiseInfoToMessage {exnTable; exnName; missingAnnotations; raiseSet}
let missingThrowInfoToMessage {exnTable; exnName; missingAnnotations; throwSet}
=
let raisesTxt =
Format.asprintf "%a" (Exceptions.pp ~exnTable:(Some exnTable)) raiseSet
let throwsTxt =
Format.asprintf "%a" (Exceptions.pp ~exnTable:(Some exnTable)) throwSet
in
let missingTxt =
Format.asprintf "%a" (Exceptions.pp ~exnTable:None) missingAnnotations
in
Format.asprintf
"@{<info>%s@} might raise %s and is not annotated with @raises(%s)" exnName
raisesTxt missingTxt
"@{<info>%s@} might throw %s and is not annotated with @throws(%s)" exnName
throwsTxt missingTxt

let descriptionToMessage (description : description) =
match description with
Expand All @@ -138,7 +138,7 @@ let descriptionToMessage (description : description) =
Format.asprintf "@{<info>%s@} %s" path message
| ExceptionAnalysis {message} -> message
| ExceptionAnalysisMissing missingRaiseInfo ->
missingRaiseInfoToMessage missingRaiseInfo
missingThrowInfoToMessage missingRaiseInfo
| Termination {message} -> message

let descriptionToName (description : description) =
Expand Down
33 changes: 28 additions & 5 deletions analysis/src/CompletionDecorators.ml
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,24 @@ Alternatively, use the `@@deprecated` decorator to add a deprecation warning to
( "doesNotRaise",
None,
[
{|The `@doesNotRaise` decorator is for reanalyze, a static analysis tool for ReScript that can perform exception analysis.
{|The `@doesNotRaise` decorator is deprecated. Please use `@doesNotThrow` instead.

`@doesNotRaise` is uses to override the analysis and state that an expression does not raise any exceptions,
`@doesNotRaise` is uses to override the analysis and state that an expression does not throw any exceptions,
even though the analysis reports otherwise. This can happen for example in the case of array access where
the analysis does not perform range checks but takes a conservative stance that any access
could potentially raise.
could potentially throw.
[Read more and see examples in the documentation](https://github.com/rescript-association/reanalyze/blob/master/EXCEPTION.md).
> Hint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!|};
] );
( "doesNotThrow",
None,
[
{|The `@doesNotThrow` decorator is for reanalyze, a static analysis tool for ReScript that can perform exception analysis.

`@doesNotThrow` is uses to override the analysis and state that an expression does not throw any exceptions,
even though the analysis reports otherwise. This can happen for example in the case of array access where
the analysis does not perform range checks but takes a conservative stance that any access
could potentially throw.
[Read more and see examples in the documentation](https://github.com/rescript-association/reanalyze/blob/master/EXCEPTION.md).
> Hint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!|};
] );
Expand Down Expand Up @@ -149,12 +161,23 @@ The `@new` decorator is used whenever you need to bind to a JavaScript class con
( "raises",
Some "raises(\"$0\")",
[
{|The `@raises` decorator is for reanalyze, a static analysis tool for ReScript that can perform exception analysis.
{|The `@raises` decorator is deprecated. Please use `@throws` instead.

`@raises` acknowledges that a function can raise exceptions that are not caught, and suppresses
`@raises` acknowledges that a function can throw exceptions that are not caught, and suppresses
a warning in that case. Callers of the functions are then subjected to the same rule.
Example `@raises(Exn)` or `@raises([E1, E2, E3])` for multiple exceptions.
[Read more and see examples in the documentation](https://github.com/rescript-association/reanalyze/blob/master/EXCEPTION.md).
> Hint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!|};
] );
( "throws",
Some "throws(\"$0\")",
[
{|The `@throws` decorator is for reanalyze, a static analysis tool for ReScript that can perform exception analysis.

`@throws` acknowledges that a function can throw exceptions that are not caught, and suppresses
a warning in that case. Callers of the functions are then subjected to the same rule.
Example `@throws(Exn)` or `@throws([E1, E2, E3])` for multiple exceptions.
[Read more and see examples in the documentation](https://github.com/rescript-association/reanalyze/blob/master/EXCEPTION.md).
> Hint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!|};
] );
( "react.component",
Expand Down
Loading
Loading