Skip to content
This repository was archived by the owner on Oct 31, 2023. It is now read-only.

Commit 04b580b

Browse files
authored
Release 0.2.3 (#26)
* feat(lang): add Typescript support (#12) refactor(lang): wrap Rust query-logic in a struct. Using the rust implementation as a base turned out to be really hard because the rust implementation was really hard to understand, so a big refactoring for code simplification also happened to the Rust version of the library. fix!(rust): Use the instrumented struct as part of the function, not module BREAKING: in Rust, the name of the structure for instrumented methods is now included in the `function` field, not the `module` anymore. Golang refactoring will happen later, but this makes changing code a lot easier. * chore: Release am_list version 0.2.3 * Enable actions on release branches
1 parent 524fe38 commit 04b580b

22 files changed

+2109
-460
lines changed

.github/workflows/check.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: test
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches: [ main, release/* ]
66
pull_request:
7-
branches: [ main ]
7+
branches: [ main, release/* ]
88

99
env:
1010
CARGO_TERM_COLOR: always

CHANGELOG.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
## [Unreleased] - ReleaseDate
1111

12+
## [Version 0.2.3] - 2023-07-04
13+
14+
### Added
15+
16+
- [Typescript] Support for typescript language
17+
1218
## [Version 0.2.2] - 2023-06-19
1319

1420
### Added
@@ -62,7 +68,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6268
### Security
6369

6470
<!-- next-url -->
65-
[Unreleased]: https://github.com/autometrics-dev/am_list/compare/v0.2.2...HEAD
71+
[Unreleased]: https://github.com/autometrics-dev/am_list/compare/v0.2.3...HEAD
72+
[Version 0.2.3]: https://github.com/autometrics-dev/am_list/compare/v0.2.2...v0.2.3
6673
[Version 0.2.2]: https://github.com/gagbo/am_list/compare/v0.2.1...v0.2.2
6774
[Version 0.2.1]: https://github.com/gagbo/am_list/compare/v0.2.0...v0.2.1
6875
[Version 0.2.0]: https://github.com/gagbo/am_list/compare/v0.1.0...v0.2.0

CONTRIBUTING.md

+21-6
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,29 @@ Also, you need to have the authorization to both:
2222
- Create a branch `release/0.Y` from main
2323
- Run `cargo release 0.Y.0` and make sure everything is okay
2424

25-
<details>
26-
<summary>If everything went ok</summary>
25+
**If everything went ok**
26+
2727
- Run the same command, but with `--execute` flag
28-
</details>
2928

30-
<details>
31-
<summary>If something went wrong</summary>
29+
**If something went wrong**
30+
3231
- Nothing got actually published, so you can reset the `release/0.Y` branch to `main`,
3332
- Then make and commit the changes on your `release/0.Y` branch (that is still local),
3433
- Then try the `cargo release 0.Y.0` again.
35-
</details>
34+
35+
### Release a new version on a release branch
36+
37+
In the example, we will push a new `0.2.3` version on `0.2`
38+
39+
- Create a release branch `rel_0.2.3` with all the changes that need to be included
40+
+ new features are cherry-picked from `main`
41+
+ bugfixes happen directly on the release branch (and later get cherry-picked _to_ `main` if relevant)
42+
- Push the new branch `rel_0.2.3` and create a PR to `release/0.2`
43+
- Prepare the release: `cargo release --no-publish --no-tag --allow-branch=rel_0.2.3 patch`
44+
- Cleanup the `CHANGELOG.md` that got bad replacement patterns because of the 2-step process
45+
- Push the new commits, and merge the PR
46+
- Switch locally to `release/0.2` (the "main" release branch), and pull the latest changes
47+
- Publish the tag and the new crate: `cargo release publish --execute && cargo release tag --execute && cargo release push --execute`
48+
49+
Bonus:
50+
- Merge the CHANGELOG/README/CONTRIBUTING back from `release/0.2` to `main`

Cargo.lock

+12-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "am_list"
3-
version = "0.2.2"
3+
version = "0.2.3"
44
edition = "2021"
55
repository = "https://github.com/autometrics-dev/am_list"
66
authors = ["Fiberplane <[email protected]>", "Gerry Agbobada <[email protected]>"]
@@ -23,6 +23,7 @@ thiserror = "1.0.40"
2323
tree-sitter = "0.20.10"
2424
tree-sitter-go = "0.19.1"
2525
tree-sitter-rust = "0.20.3"
26+
tree-sitter-typescript = "0.20.2"
2627
walkdir = "2.3.3"
2728

2829
# The profile that 'cargo dist' will build with

README.md

+60-17
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,72 @@ look at the metrics. In a nutshell,
3737
Language | Function name detection | Module detection
3838
:---:|:---:|:---:
3939
[Rust](https://github.com/autometrics-dev/autometrics-rs) | ✅ | ✅
40-
[Typescript](https://github.com/autometrics-dev/autometrics-ts) | |
41-
[Go](https://github.com/autometrics-dev/autometrics-go) | | ✅
40+
[Typescript](https://github.com/autometrics-dev/autometrics-ts) | | ⚠️[^wrapper]
41+
[Go](https://github.com/autometrics-dev/autometrics-go) | ⚠️[^all-functions] | ✅
4242
[Python](https://github.com/autometrics-dev/autometrics-py) | ❌ | ❌
4343
[C#](https://github.com/autometrics-dev/autometrics-cs) | ❌ | ❌
4444

45+
[^wrapper]: For Typescript (and all languages where autometrics is a wrapper
46+
function), static analysis makes it hard to traverse imports to find the
47+
module where an instrumented function is _defined_, so the reported module
48+
is the module where the function has been _instrumented_
4549

46-
### Rust
50+
[^all-functions]: Support list all autometricized functions, but not all
51+
functions without restriction
4752

48-
#### Aliasing issues
53+
### Typescript
4954

50-
`am_list` doesn't track type renaming across files. That means for example that if
51-
- you created a `struct Foo` in `src/foo.rs`,
52-
- and then imported it as
53-
```rust
54-
use crate::foo::Foo as Oof;
55+
#### Module tracking
5556

56-
#[autometrics]
57-
impl Oof {
58-
// implOofBlock
59-
}
57+
This tool cannot track modules "accurately" (meaning "the module label is
58+
exactly what autometrics will report"), because autometrics-ts uses the path of
59+
the source in the JS-compiled bundle to report the module. The compilation and
60+
bundling happens after `am_list` looks at the code so it cannot be accurate.
61+
62+
This means the module reporting for typescript is bound to be a "best effort"
63+
attempt to be useful.
64+
65+
The other difficulty encountered when using a static analysis tool with autometrics-ts is that the
66+
instrumentation can happen anywhere, as the wrapper function call can use an imported symbol as its argument:
67+
68+
``` typescript
69+
import { exec } from 'child_process'
70+
import { autometrics } from '@autometrics/autometrics'
71+
72+
const instrumentedExec = autometrics(exec)
73+
74+
// use instrumentedExec everywhere instead of exec
75+
```
76+
77+
In order to report the locus of _function definition_ as the module, we would
78+
need to include both:
79+
- a complete import resolution step, to figure out the origin module of the
80+
instrumented function (`child_process` in the example), and
81+
- a dependency inspection step, to figure out the path to the instrumented
82+
function definition _within_ the dependency (`lib/child_process.js` in the
83+
[node source code](https://github.com/nodejs/node/blob/main/lib/child_process.js))
84+
85+
This is impractical and error-prone to implement these steps accurately, so
86+
instead we only try to detect imports when they are explicitely imported in the
87+
same file, and we will only report the function module as the imported module
88+
(not the path to the file it is defined in). Practically that means that for
89+
this example:
90+
91+
``` typescript
92+
// in src/router/index.ts
93+
import { exec } from 'child_process'
94+
import { origRoute as myRoute } from '../handlers'
95+
import { autometrics } from '@autometrics/autometrics'
96+
97+
const instrumentedExec = autometrics(exec)
98+
const instrumentedRoute = autometrics(myRoute)
99+
100+
// use instrumentedExec everywhere instead of exec
60101
```
61102

62-
then all the functions in the `implOofBlock` won't be detected by this utility
63-
(_but would still work in autometrics_). This is not planned to be fixed, as it
64-
might not even be legal in Rust, and at the very least is going to be very
65-
rare.
103+
`am_list` will report 2 functions:
104+
- `{"function": "exec", "module": "ext://child_process"}`: using `ext://`
105+
protocol to say the module is non-local
106+
- `{"function": "origRoute", "module": "handlers"}`: even if `myRoute` is
107+
re-exported from `../handlers/my/routes/index.ts`, we do not go look into what
108+
`handlers` did to expose `origRoute`; also, the alias is resolved.
+2-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
(
2-
(impl_item
1+
((impl_item
32
type: (type_identifier) @type.impl
43
body: (declaration_list
54
(function_item
65
name: (identifier) @func.name)))
7-
(#match? @type.impl "({}){{1,1}}")
8-
)
6+
(#match? @type.impl "({}){{1,1}}"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(function_declaration
2+
name: (identifier) @func.name)
3+
4+
(function
5+
name: (identifier) @func.name)
6+
7+
(class_declaration
8+
name: (type_identifier) @type.name
9+
body: (class_body
10+
[(method_signature
11+
name: (property_identifier) @method.name)
12+
(method_definition
13+
name: (property_identifier) @method.name)]))
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
;; TODO: this doesn't work as captures aren't shared between patterns.
2+
;; This means we can't use @wrapper.atname in an #eq expression in the (call_expression) pattern afterwards
3+
;; A recursion algorithm that uses a templated query might be the solution
4+
((import_statement
5+
(import_clause
6+
(named_imports
7+
(import_specifier
8+
.
9+
name: (identifier) @wrapperdirect.name
10+
.)))
11+
source: (string (string_fragment) @lib.atname))
12+
(#eq? @lib.atname "@autometrics/autometrics")
13+
(#eq? @wrapperdirect.name "autometrics"))
14+
15+
((import_statement
16+
(import_clause
17+
(named_imports
18+
(import_specifier
19+
name: (identifier) @real.name
20+
alias: (identifier) @wrapperdirect.name)))
21+
source: (string (string_fragment) @lib.name))
22+
(#eq? @lib.name "@autometrics/autometrics")
23+
(#eq? @real.name "autometrics"))
24+
25+
26+
27+
;; TODO: this doesn't work as captures aren't shared between patterns.
28+
;; This means we can't use @wrapperdirect.name in an #eq expression in the (call_expression) pattern afterwards
29+
;; A recursion algorithm that uses a templated query might be the solution
30+
((import_statement
31+
(import_clause
32+
(named_imports
33+
(import_specifier
34+
.
35+
name: (identifier) @wrapper.name
36+
.)))
37+
source: (string (string_fragment) @lib.name))
38+
(#eq? @lib.name "autometrics")
39+
(#eq? @wrapper.name "autometrics"))
40+
41+
((import_statement
42+
(import_clause
43+
(named_imports
44+
(import_specifier
45+
name: (identifier) @real.name
46+
alias: (identifier) @wrapper.name)))
47+
source: (string (string_fragment) @lib.name))
48+
(#eq? @lib.name "autometrics")
49+
(#eq? @real.name "autometrics"))
50+
51+
((class_declaration
52+
decorator: (decorator (identifier) @decorator.name)
53+
name: (type_identifier) @type.name
54+
body: (class_body
55+
[(method_signature
56+
name: (property_identifier) @method.name)
57+
(method_definition
58+
name: (property_identifier) @method.name)]))
59+
(#eq? @decorator.name "Autometrics"))
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
;; This query extracts all the imports of the current source
2+
;; NOTE: it is impossible to merge "looking for imports" with the
3+
;; "looking for autometricized functions" queries for at least 2 reasons:
4+
;; - The "call_expression" is not necessarily a sibling node to the imports, and it's not possible
5+
;; to match a "call_expression" as an arbitrarily deep "cousin" of the import_clause node.
6+
;; - There is no builtin `#prefix?` operator, which makes checking for namespaced imports
7+
;; impossible to do in 1 query
8+
9+
((import_statement
10+
(import_clause
11+
(named_imports
12+
(import_specifier
13+
name: (identifier) @inst.ident)))
14+
source: (string (string_fragment) @inst.source)))
15+
16+
(import_statement
17+
(import_clause
18+
(named_imports
19+
(import_specifier
20+
name: (identifier) @inst.realname
21+
alias: (identifier) @inst.ident)))
22+
source: (string (string_fragment) @inst.source))
23+
24+
(import_statement
25+
(import_clause
26+
(namespace_import (identifier) @inst.prefix))
27+
source: (string (string_fragment) @inst.source))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
((call_expression
2+
function: (identifier) @wrapper.call
3+
arguments: (arguments
4+
.
5+
(object
6+
(pair
7+
key: (property_identifier) @func.prop
8+
value: (string (string_fragment) @func.name))
9+
(pair
10+
key: (property_identifier) @mod.prop
11+
value: (string (string_fragment) @module.name)))))
12+
(#eq? @wrapper.call "{0}")
13+
(#eq? @func.prop "functionName")
14+
(#eq? @mod.prop "moduleName"))
15+
16+
((call_expression
17+
function: (identifier) @wrapper.call
18+
arguments: (arguments
19+
.
20+
(object
21+
(pair
22+
key: (property_identifier) @mod.prop
23+
value: (string (string_fragment) @module.name))
24+
(pair
25+
key: (property_identifier) @func.prop
26+
value: (string (string_fragment) @func.name)))))
27+
(#eq? @wrapper.call "{0}")
28+
(#eq? @func.prop "functionName")
29+
(#eq? @mod.prop "moduleName"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
((call_expression
2+
function: (identifier) @wrapper.call
3+
arguments: (arguments (identifier) @func.name))
4+
(#eq? @wrapper.call "{0}"))
5+
6+
((call_expression
7+
function: (identifier) @wrapper.call
8+
arguments: (arguments (member_expression) @func.name))
9+
(#eq? @wrapper.call "{0}"))
10+
11+
((call_expression
12+
function: (identifier) @wrapper.call
13+
arguments: (arguments
14+
(function
15+
name: (identifier) @func.name)))
16+
(#eq? @wrapper.call "{0}"))

0 commit comments

Comments
 (0)