Compare commits

...

382 Commits

Author SHA1 Message Date
Anders Starcke Henriksen
53df2bcd87 Merge pull request #2956 from github/v1.9.2
Some checks failed
Release / Release (push) Has been cancelled
Release / Publish to VS Code Marketplace (push) Has been cancelled
Release / Publish to Open VSX Registry (push) Has been cancelled
Release v1.9.2
2023-10-12 10:18:15 +02:00
Koen Vlaswinkel
0835b140e7 Merge pull request #2948 from github/koesie10/sort-usages-panel
Sort methods in usages panel according to model editor sort order
2023-10-12 09:55:00 +02:00
Anders Starcke Henriksen
0e62d2635c v1.9.2 2023-10-12 09:50:13 +02:00
Koen Vlaswinkel
78284cbc7a Merge remote-tracking branch 'origin/main' into koesie10/sort-usages-panel 2023-10-12 09:39:11 +02:00
Koen Vlaswinkel
0d7002273a Merge pull request #2949 from github/koesie10/single-model-editor-per-db
Only allow a single model editor per database
2023-10-12 09:37:56 +02:00
Charis Kyriakou
7041dd7698 Rename 'open database' button and only show if source available (#2945) 2023-10-12 08:09:05 +01:00
Koen Vlaswinkel
a3d41a2afe Merge pull request #2924 from github/koesie10/modeling-panel-multiple-models-add-remove
Add ability to add/remove modelings to method modeling panel
2023-10-11 16:20:39 +02:00
Koen Vlaswinkel
0cbdadb271 Add newline for readability
Co-authored-by: Robert <robertbrignull@github.com>
2023-10-11 15:59:56 +02:00
Henry Mercer
5105187dbd Merge pull request #2951 from github/github-action/bump-cli
Bump CLI Version to v2.15.0 for integration tests
2023-10-11 14:03:49 +01:00
Koen Vlaswinkel
918362f39e Merge pull request #2950 from github/koesie10/variant-analysis-save-before-start
Use `saveBeforeStart` setting when running a variant analysis
2023-10-11 14:34:08 +02:00
github-actions[bot]
0b5d2d86cf Bump CLI version from v2.14.6 to v2.15.0 for integration tests 2023-10-11 12:30:29 +00:00
Koen Vlaswinkel
3a035708c5 Merge pull request #2947 from github/koesie10/update-set-selected-method-message
Convert `SetSelectedMethodMessage` to include multiple modeled methods
2023-10-11 13:59:48 +02:00
Koen Vlaswinkel
84211c63bb Merge remote-tracking branch 'origin/main' into koesie10/update-set-selected-method-message 2023-10-11 13:40:27 +02:00
Koen Vlaswinkel
11218522e7 Update CHANGELOG 2023-10-11 13:35:11 +02:00
Koen Vlaswinkel
0bdd441767 Use saveBeforeStart setting when running a variant analysis
This will respect the user's `saveBeforeStart` setting when running a
variant analysis. This re-uses the existing `saveBeforeStart` function
that is used when running local queries. The default behavior if the
setting is not set is to save all open named files.
2023-10-11 13:33:24 +02:00
Koen Vlaswinkel
667bf19f46 Add methodSignature to SetMultipleModeledMethodsMessage 2023-10-11 13:07:18 +02:00
Robert
c459d0ff65 Merge pull request #2942 from github/robertbrignull/SetModeledMethodsMessage
Convert SetModeledMethodsMessage to include multiple modeled methods
2023-10-11 11:45:57 +01:00
Koen Vlaswinkel
039b28235d Only allow 1 view in the view tracker
This will change the model editor view tracker to only store 1 view per
database item instead of an array of views per database item.
2023-10-11 11:54:47 +02:00
Koen Vlaswinkel
76db520ce7 Only allow a single model editor per database
This will add checks in the appropriate places to ensure that only a
single model editor is opened per database.
2023-10-11 11:51:05 +02:00
Koen Vlaswinkel
0e1afcee64 Sort methods in usages panel according to model editor sort order
This sorts the methods in the methods usages panel the same as in the
model editor. Since this is dependent on the mode, we need to keep track
of the mode in the modeling store, so this also adds a mode field to the
db state.
2023-10-11 11:42:10 +02:00
Robert
62c9e51c25 Avoid unnnecessarily renaming field read from props 2023-10-11 10:41:48 +01:00
Charis Kyriakou
2b47d3d192 Ensure modeled methods are not undefined in usages panel (#2946) 2023-10-11 08:29:39 +00:00
Koen Vlaswinkel
4a62d05af6 Convert SetSelectedMethodMessage to include multiple modeled methods 2023-10-11 10:14:58 +02:00
Koen Vlaswinkel
e77cf28192 Merge pull request #2944 from github/dependabot/npm_and_yarn/extensions/ql-vscode/postcss-8.4.31
Bump postcss from 8.4.24 to 8.4.31 in /extensions/ql-vscode
2023-10-11 09:51:41 +02:00
Koen Vlaswinkel
59e23f35e2 Merge pull request #2943 from github/koesie10/state-confusion
Fix confusion between modeling store and view states
2023-10-11 09:45:58 +02:00
dependabot[bot]
33d55b1f0a Bump postcss from 8.4.24 to 8.4.31 in /extensions/ql-vscode
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.24 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.24...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-10 15:03:37 +00:00
Koen Vlaswinkel
e7ddb4406a Merge pull request #2937 from github/koesie10/remove-gulp-sourcemaps
Remove `gulp-sourcemaps` dependency
2023-10-10 17:02:00 +02:00
Koen Vlaswinkel
9a12a12065 Unexport DbModelingState 2023-10-10 16:57:19 +02:00
Koen Vlaswinkel
073440914d Set instance variables on webview load 2023-10-10 16:55:55 +02:00
Robert
c6a9f23e7e Delete convertToLegacyModeledMethods 2023-10-10 15:50:02 +01:00
Robert
d531bc642d Convert SetModeledMethodsMessage to include multiple modeled methods 2023-10-10 15:50:02 +01:00
Robert
a6625334f0 Merge pull request #2940 from github/robertbrignull/CommonFromViewMessages
Add CommonFromViewMessages to FromModelEditorMessage
2023-10-10 15:49:04 +01:00
Koen Vlaswinkel
77bb9780ec Fix confusion between modeling store an view states
This fixes three bugs related to the modeling store and view states:
- In the model editor view, when `setModeledMethods` was called, it
would do it on the active database, instead of the database that the
view was showing. This should not result in any visible bugs since the
active database is always the one that is being shown (in theory), but
I can imagine that it could cause issues if showing multiple model
editors next to each other.
- In the method modeling panel, the "reveal in editor" button would
always show the already active model editor. Therefore, if you had
multiple open and were still viewing the method of the first one, it
would always show the second one.
- In the method modeling panel, the same bug would cause the incorrect
modeled methods to be updated.
2023-10-10 16:44:40 +02:00
Koen Vlaswinkel
bb88c148aa Merge pull request #2941 from github/koesie10/modeling-store-modeled-methods-bug
Fix bug when selecting method without modeled methods
2023-10-10 16:18:42 +02:00
Robert
6df7ea3ddc Remove unused import 2023-10-10 15:15:29 +01:00
Robert
2beaf9a88c ViewLoadedMsg is already included 2023-10-10 15:11:30 +01:00
Robert
a7dbae02e0 Use ap logger 2023-10-10 15:11:19 +01:00
Robert
28ac019929 Fix typo 2023-10-10 15:11:09 +01:00
Koen Vlaswinkel
2e2ab11e4f Merge pull request #2939 from github/koesie10/unhandled-error-stack
Add stack trace to unhandled error log message
2023-10-10 16:10:01 +02:00
Robert
edf3cad6e4 Add CommonFromViewMessages to FromModelEditorMessage 2023-10-10 15:03:23 +01:00
Robert
246c347b04 Merge pull request #2938 from github/robertbrignull/legacy-conversion
Make use of modeled-methods-legacy.ts in the webview code
2023-10-10 15:00:52 +01:00
Koen Vlaswinkel
e332b26f29 Fix bug when selecting method without modeled methods
When selecting a method that has no modeled methods, the modeling state
would not contain an entry for the method signature. This would cause
the `modeledMethods` to be `undefined`, which is not allowed according
to its type.
2023-10-10 15:51:59 +02:00
Koen Vlaswinkel
22f6ac7974 Add stack trace to unhandled error log message
This change sets the `fullMessage` of the
`showAndLogExceptionWithTelemetry` to include the stack trace. This
makes it possible to find the source of the error rather than only
knowing that a specific error occurred. If the error does not have a
stack trace (which should be rare) the message will be the same as
before.
2023-10-10 15:45:49 +02:00
Robert
e45f4bd0d9 Merge branch 'main' into robertbrignull/legacy-conversion 2023-10-10 14:37:20 +01:00
Charis Kyriakou
f4d74c7d3f Deal with already modeled methods in the modeling panel (#2933) 2023-10-10 14:35:21 +01:00
Robert
7ba58b6298 Use legacy modeled method conversions instead of custom conversions 2023-10-10 14:23:06 +01:00
Robert
2b59c041b8 Make convertFromLegacyModeledMethod handle undefined 2023-10-10 14:23:06 +01:00
Robert
a434fbffbc Move modeled-methods-legacy.ts to /shared directory 2023-10-10 14:23:06 +01:00
Koen Vlaswinkel
649f69234e Remove gulp-sourcemaps 2023-10-10 14:37:20 +02:00
Charis Kyriakou
4ee86c15ad Add some spacing in modeling method empty states (#2934) 2023-10-10 10:52:15 +00:00
Robert
fcfd4f37a6 Merge pull request #2926 from github/robertbrignull/JumpToUsageMessage
Only include method signature in JumpToUsageMessage
2023-10-10 11:12:36 +01:00
Robert
6561bb0543 Merge branch 'main' into robertbrignull/JumpToUsageMessage 2023-10-10 10:50:21 +01:00
Koen Vlaswinkel
319a54c32f Merge pull request #2906 from github/koesie10/convert-remaining-multiple-models
Convert remaining extension host code to handle multiple models
2023-10-10 11:43:09 +02:00
Koen Vlaswinkel
385b0e8d1a Merge remote-tracking branch 'origin/main' into koesie10/convert-remaining-multiple-models 2023-10-10 11:22:08 +02:00
Robert
4dcfa8b679 Merge pull request #2927 from github/robertbrignull/generateMethodsFromLlm
Only include method signatures in generateMethodsFromLlm message
2023-10-10 10:19:20 +01:00
Robert
47509a922a Rename jumpToUsage => jumpToMethod throughout 2023-10-10 10:14:27 +01:00
Charis Kyriakou
7eab7e4e48 Set modeling mode when initialising method modeling panel (#2932) 2023-10-10 10:12:01 +01:00
Charis Kyriakou
f794a19d96 Track db related to modeled method and react when it closes (#2930) 2023-10-10 09:51:50 +01:00
Charis Kyriakou
e98611fd21 Ensure modified methods updated after changing a method from the modeling panel (#2929) 2023-10-10 08:57:36 +01:00
Koen Vlaswinkel
461cf15a47 Merge remote-tracking branch 'origin/main' into koesie10/convert-remaining-multiple-models 2023-10-10 09:30:29 +02:00
Koen Vlaswinkel
54e1b29940 Add explicit return type to convertToLegacyModeledMethod 2023-10-10 09:26:27 +02:00
Dave Bartolomeo
623890adc9 Merge pull request #2922 from github/dbartol/passthru
Add `additionalArgs` option to `launch.json`
2023-10-09 15:04:13 -04:00
Dave Bartolomeo
e7f75ab928 Merge branch 'dbartol/passthru' of https://github.com/github/vscode-codeql into dbartol/passthru 2023-10-09 14:44:49 -04:00
Dave Bartolomeo
df02fecf3c Fix test expectations 2023-10-09 14:44:33 -04:00
Dave Bartolomeo
a861346b10 Merge branch 'main' into dbartol/passthru 2023-10-09 14:06:14 -04:00
Philip Ginsbach
86b2157552 Merge pull request #2792 from github/ginsbach/TextMateInstantiationSyntax
fix syntax highlighting after imports with instantiation arguments
2023-10-09 17:17:55 +01:00
Philip Ginsbach
47fa163cb9 Update compiled grammar 2023-10-09 16:58:00 +01:00
Dave Bartolomeo
c78f01758a Alternate fix for import highlighting with instantiations 2023-10-09 16:58:00 +01:00
Philip Ginsbach
353e22d6e8 link to the PR from the changelog entry 2023-10-09 16:58:00 +01:00
Philip Ginsbach
599d31e5ac add changelog entry for TextMate instantiation argument syntax 2023-10-09 16:57:25 +01:00
Philip Ginsbach
64df792eda TextMate grammar: imports can have instantiation arguments 2023-10-09 16:56:21 +01:00
Robert
feebf7c3fd Only include method signatures in generateMethodsFromLlm 2023-10-09 16:17:46 +01:00
Robert
27c4bd8349 Merge pull request #2910 from github/robertbrignull/multiple-models-method-row
Add ability for MethodRow to render multiple modelings of the same method
2023-10-09 16:17:17 +01:00
Dave Bartolomeo
d4cbfbb70e Rename to additionalRunQueryArgs 2023-10-09 11:04:22 -04:00
Robert
153424ae5a Only include method signature in JumpToUsageMessage 2023-10-09 16:03:41 +01:00
Robert
edd2aa5e8f Merge branch 'main' into robertbrignull/multiple-models-method-row 2023-10-09 15:38:00 +01:00
Robert
0265353370 Use index as react key 2023-10-09 14:44:43 +01:00
Koen Vlaswinkel
623df4c2ee Merge pull request #2919 from github/koesie10/show-multiple-models-listener
Listen to modeling setting changes in the model editor and method modeling panel
2023-10-09 15:38:30 +02:00
Robert
8b0825ab3c Use better name for variable 2023-10-09 14:32:56 +01:00
Robert
c620939895 Merge pull request #2913 from github/robertbrignull/SaveModeledMethods
Remove method and modeledMethods from SaveModeledMethods message
2023-10-09 14:16:30 +01:00
Koen Vlaswinkel
e4a290fe37 Merge pull request #2925 from github/koesie10/method-modeling-codeowner
Set SecExp as codeowner for modeling panel
2023-10-09 15:13:56 +02:00
Koen Vlaswinkel
3230cc9166 Set SecExp as codeowner for modeling panel 2023-10-09 14:56:38 +02:00
Koen Vlaswinkel
4dae9b87d4 Add ability to add/remove modelings 2023-10-09 14:54:32 +02:00
Koen Vlaswinkel
5c368cec8b Merge pull request #2921 from github/koesie10/modeling-panel-multiple-models
Add ability for method modeling panel to render multiple modelings of the same method
2023-10-09 14:27:30 +02:00
Koen Vlaswinkel
b76369330d Convert remaining extension host code to handle multiple models
This converts all remaining extension host code to handle multiple
models per method. The only place where we're using the legacy format
is in the webview and in the boundary between the webview and the
extension host.
2023-10-09 13:56:41 +02:00
Koen Vlaswinkel
323c5368ba Switch from CodiconButton to VSCodeButton 2023-10-09 13:52:55 +02:00
Robert
c3e18910fc Fix typo in comment 2023-10-09 12:26:09 +01:00
Robert
603c799717 Merge pull request #2923 from github/robertbrignull/telemetry-changelog
Update CHANGELOG.md for telemetry changes
2023-10-09 12:24:39 +01:00
Robert
aa045835fd Merge branch 'main' into robertbrignull/SaveModeledMethods 2023-10-09 11:35:47 +01:00
Robert
850c04a289 Clarify method docs 2023-10-09 11:30:35 +01:00
Robert
4d19955740 Update CHANGELOG.md for telemetry changes 2023-10-09 11:08:31 +01:00
Robert
b623f92c4a Merge pull request #2824 from github/robertbrignull/telemetry
Respect "telemetry.telemetryLevel" as well as "telemetry.enableTelemetry"
2023-10-09 10:59:26 +01:00
Koen Vlaswinkel
08342f1960 Merge pull request #2905 from github/koesie10/convert-modeled-method-fs
Convert `modeled-method-fs.ts` to handle multiple models per method
2023-10-09 11:54:31 +02:00
Robert
af334a98e3 Merge pull request #2912 from github/robertbrignull/simplify_messages/RevealMethodMessage
Convert RevealMethodMessage to only include method signature
2023-10-09 10:46:19 +01:00
Dave Bartolomeo
9e26c29ddb Fix tests for older CLIs 2023-10-06 18:35:20 -04:00
Dave Bartolomeo
705a7975c5 Add additionalArgs option to launch.json 2023-10-06 18:03:10 -04:00
Koen Vlaswinkel
6be98f3f8d Fix test 2023-10-06 16:57:31 +02:00
Koen Vlaswinkel
3b6263fb07 Remove size from CodiconButton 2023-10-06 16:48:49 +02:00
Koen Vlaswinkel
29aa4a3f29 Add tests for multiple modeled methods panel 2023-10-06 16:42:41 +02:00
Koen Vlaswinkel
3a494dff36 Add support for unmodeled methods 2023-10-06 16:26:53 +02:00
Koen Vlaswinkel
a01a76cb73 Add initial multiple modelings panel 2023-10-06 16:25:25 +02:00
Koen Vlaswinkel
a29112e045 Add multiple models to MethodModeling story 2023-10-06 15:54:02 +02:00
Koen Vlaswinkel
ee1bf8896e Change MethodModeling to accept multiple models 2023-10-06 15:50:31 +02:00
Koen Vlaswinkel
2410d2bfdd Merge pull request #2918 from github/koesie10/modeling-panel-view-state
Add view state to method modeling panel
2023-10-06 15:49:37 +02:00
Charis Kyriakou
0e5551b650 Make method modeling panel available to canary users (#2920) 2023-10-06 13:25:33 +00:00
Koen Vlaswinkel
951bd13881 Use ModelConfig for all model settings
This switches all places where we're retrieving some model configuration
to use the `ModelConfig` or `ModelConfigListener` types. This makes it
much easier to mock these settings in tests.

This also adds a listener to the `ModelEditorView` to send the new view
state when any of the settings is changed. This should make it easier
to test settings changes in the model editor without having to re-open
the model editor.
2023-10-06 14:29:07 +02:00
Koen Vlaswinkel
08944a292c Update method modeling panel after changes to config
This updates the method modeling panel's view state when the
`codeQL.model.showMultipleModels` setting changes. This will ensure that
the setting updates without needing to restart VS Code since this view
is much harder to restart than the model editor.
2023-10-06 14:15:23 +02:00
Koen Vlaswinkel
cf0057ecd9 Add view state to method modeling panel
This adds a view state to the method modeling panel similar to the
model editor. This will be used to send the state of the show multiple
models feature flag to the webview so this can be used to selectively
show/hide components in the method modeling panel.
2023-10-06 14:04:06 +02:00
Charis Kyriakou
1d691c2b7f UI polish for modeling panel (#2917)
* Don't push content down with unsaved tag

* Adjust spacing between components
2023-10-06 11:57:52 +01:00
Charis Kyriakou
20c63921f7 Wire up modeling panel empty states (#2915) 2023-10-06 11:27:16 +01:00
Koen Vlaswinkel
9c275130a5 Fix duplicate modeled methods
Co-authored-by: Robert <robertbrignull@github.com>
2023-10-06 12:09:00 +02:00
Henry Mercer
b1df4a4f0a Merge pull request #2916 from github/henrymercer/remove-ml-powered-queries
Packaging: Remove ML-powered queries pack from known list
2023-10-06 11:02:27 +01:00
Henry Mercer
08a4457e39 Packaging: Remove ML-powered queries pack from known list
ML-powered queries are [now deprecated](https://github.blog/changelog/2023-09-29-codeql-code-scanning-deprecates-ml-powered-alerts/).
2023-10-06 10:38:14 +01:00
Charis Kyriakou
db3d24236c Add empty states for modeling panel (#2914) 2023-10-06 09:34:54 +01:00
Robert
9d40d9a703 Remove method and modeledMethods from SaveModeledMethods message 2023-10-05 16:56:05 +01:00
Robert
0fabcda49b Convert RevealMethodMessage to only include method signature 2023-10-05 16:41:56 +01:00
Robert
2b0bd840e6 Place setup before initialising listener 2023-10-05 15:25:42 +01:00
Robert
d499ff3cf4 Merge branch 'main' into robertbrignull/telemetry 2023-10-05 15:19:07 +01:00
Charis Kyriakou
1806108166 Remove unnecessary TODOs (#2900) 2023-10-05 08:04:17 +00:00
Andrew Eisenberg
02af432d5f Merge pull request #2885 from github/aeisenberg/multi-token
Adds `MultiCancellationToken`
2023-10-04 13:33:46 -07:00
Andrew Eisenberg
76ec9e2e50 Make DisposableObject a concrete class 2023-10-04 20:10:42 +00:00
Robert
5ba64a1db3 Add test for when there's no modeled method 2023-10-04 18:46:17 +01:00
Robert
f87b1c46de Add some simple tests of rendering multiple models 2023-10-04 18:46:17 +01:00
Robert
8eef4eb4f2 Add story with multiple modelings 2023-10-04 18:46:17 +01:00
Robert
252c7a20c4 Convert MethodRow to display multiple modelings 2023-10-04 18:46:13 +01:00
Robert
86d7d8345c Convert ApiOrMethodCell to ApiOrMethodRow 2023-10-04 18:17:34 +01:00
Robert
a704cd7bae Convert getModelingStatus to take ModeledMethod[] 2023-10-04 18:17:34 +01:00
Robert
e75eccb416 Change MethodRow to accept ModelEditorViewState object instead of just Mode 2023-10-04 18:17:34 +01:00
Charis Kyriakou
17947fb7c2 Use base postMessage instead of webview one (#2909) 2023-10-04 17:23:49 +01:00
Charis Kyriakou
c00207cccc Move method modeling panel out of explorer (#2908) 2023-10-04 15:38:07 +01:00
Andrew Eisenberg
801df7b14a Merge branch 'main' into aeisenberg/multi-token 2023-10-04 07:09:21 -07:00
Koen Vlaswinkel
a6c7af09d4 Merge pull request #2907 from github/koesie10/eslint-memory
Increase memory limit when running ESLint
2023-10-04 15:11:26 +02:00
Koen Vlaswinkel
96668928aa Increase memory limit when running ESLint 2023-10-04 14:46:32 +02:00
Koen Vlaswinkel
a6c97077fb Merge pull request #2904 from github/koesie10/convert-yaml-modeled-methods
Convert `yaml.ts` to handle multiple models per method
2023-10-04 14:01:02 +02:00
Robert
6d7fbfc4f8 Merge pull request #2903 from github/robertbrignull/multiple-models-feature-flag
Add feature flag for showing multiple modelings
2023-10-04 12:55:42 +01:00
Koen Vlaswinkel
c5175e040e Convert modeled-method-fs.ts to handle multiple models per method
This will change the input/output types for modeled methods in the
`modeled-method-fs.ts` file to take in multiple models per method. This
removes the need for conversion functions between this file and
`yaml.ts` files. Instead, the conversion functions are done when calling
any functions defined in `modeled-method-fs.ts` files.
2023-10-04 13:47:30 +02:00
Anders Starcke Henriksen
55af9bc47c Merge pull request #2894 from github/starcke/lang-context-history
Add support for filtering history panel.
2023-10-04 13:38:38 +02:00
Koen Vlaswinkel
ada62ffd33 Convert yaml.ts to handle multiple models per method
This changes YAML parsing/creating functions for the model editor to
handle multiple models per method. The changes in the actual YAML
handling are fairly small because the format itself already supports
multiple models per method.

I've introduced a few helper functions to convert between the old and
new types. This should only be necessary while we're in the middle of
the transition to the new types and can be removed later. For now,
we'll just take the first model in the array when converting from the
new to the old type. This is a change in the behavior since currently
we always take the last model in the array but this behavior is
undocumented and unsupported, so it should be fine to change it.
2023-10-04 13:23:54 +02:00
Robert
e10e3adc14 Add feature flag for showing multiple modelings 2023-10-04 12:21:47 +01:00
Koen Vlaswinkel
16af4c49ea Merge pull request #2902 from github/koesie10/actions-nvmrc
Use .nvmrc file for setting up Node version in Actions
2023-10-04 11:36:22 +02:00
Koen Vlaswinkel
c970c3bc19 Use .nvmrc file for setting up Node version in Actions 2023-10-04 10:00:56 +02:00
Andrew Eisenberg
3699f0b3b3 Add BasicDisposableObject
Use it for `MultiCancellationToken`. And ensure that adding a
cancellation requested listener to the `MultiCancellationToken` will
forward any cancellation requests to all constituent tokens.
2023-10-03 21:42:00 +00:00
Andrew Eisenberg
ccbe4ee974 Merge branch 'main' into aeisenberg/multi-token 2023-10-03 07:52:54 -07:00
Anders Starcke Henriksen
486180d149 Fix and add tests. 2023-10-03 13:57:16 +02:00
Anders Starcke Henriksen
9362447338 Add support for filtering history panel. 2023-10-03 13:55:01 +02:00
Anders Starcke Henriksen
ff34079247 Merge pull request #2892 from github/starcke/local-query-lang-dto
Add language to local query history items
2023-10-03 13:53:32 +02:00
Koen Vlaswinkel
7cd99cb000 Merge pull request #2897 from github/dependabot/npm_and_yarn/extensions/ql-vscode/p-queue-7.4.1
Bump p-queue from 6.6.2 to 7.4.1 in /extensions/ql-vscode
2023-10-03 12:53:55 +02:00
Koen Vlaswinkel
21fd0cfd29 Transform p-queue and p-timeout modules in Jest tests 2023-10-03 11:52:51 +02:00
dependabot[bot]
db3337cc1b Bump p-queue from 6.6.2 to 7.4.1 in /extensions/ql-vscode
Bumps [p-queue](https://github.com/sindresorhus/p-queue) from 6.6.2 to 7.4.1.
- [Release notes](https://github.com/sindresorhus/p-queue/releases)
- [Commits](https://github.com/sindresorhus/p-queue/compare/v6.6.2...v7.4.1)

---
updated-dependencies:
- dependency-name: p-queue
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-03 09:29:01 +00:00
Koen Vlaswinkel
a525499c2a Merge pull request #2818 from github/dependabot/npm_and_yarn/extensions/ql-vscode/nanoid-5.0.1
Bump nanoid from 3.3.6 to 5.0.1 in /extensions/ql-vscode
2023-10-03 11:26:00 +02:00
Anders Starcke Henriksen
6d2859cee5 Fix typo. 2023-10-03 11:23:32 +02:00
Koen Vlaswinkel
ac9128735f Merge pull request #2896 from github/koesie10/reveal-in-editor
Add reveal in editor button to method modeling panel
2023-10-03 11:12:44 +02:00
Anders Starcke Henriksen
2c0838e393 Merge branch 'main' into starcke/local-query-lang-dto 2023-10-03 11:01:46 +02:00
Anders Starcke Henriksen
b294e16c44 Add to changelog. 2023-10-03 11:01:03 +02:00
Koen Vlaswinkel
f678f49bc9 Transform nanoid module in Jest tests 2023-10-03 10:48:08 +02:00
dependabot[bot]
99dabb0779 Bump nanoid from 3.3.6 to 5.0.1 in /extensions/ql-vscode
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.6 to 5.0.1.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.6...5.0.1)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-03 08:13:33 +00:00
Koen Vlaswinkel
add7a25578 Merge pull request #2877 from github/robertbrignull/upgrade_msw
Upgrade MSW
2023-10-03 10:08:34 +02:00
Koen Vlaswinkel
487a753ede Reveal method in editor view
This will reveal a method for which "Review in editor" is clicked in the
model editor view: it will expand the group (library/package) in which
the method is located, scroll to the method, and highlight the method.

If the user clicks anywhere on the page, the highlight will be removed,
but the group will remain expanded.
2023-10-03 09:49:00 +02:00
Koen Vlaswinkel
79ea901a52 Send message to model editor view on revealing method
This will call a method on the correct model editor view when the user
clicks on "Review in editor". This does not yet do anything to the view;
this will be added in a follow-up commit.
2023-10-03 09:48:59 +02:00
Koen Vlaswinkel
2872a2dcaf Add model editor view tracker
This is used for registering which model editor views are currently
active. This will be used to determine which view to send the "reveal
method" command to. It can also be used in the future to limit the
number of instances of the model editor that can be opened for a
database.

This uses the same pattern as variant analyses with a separate interface
for the view to avoid having circular dependencies.
2023-10-03 09:48:17 +02:00
Koen Vlaswinkel
8b8bacb718 Add button for reveal method in editor 2023-10-03 09:47:52 +02:00
Koen Vlaswinkel
83e38c811f Update CHANGELOG 2023-10-03 09:36:59 +02:00
Koen Vlaswinkel
8107bf7cb1 Merge remote-tracking branch 'origin/main' into robertbrignull/upgrade_msw 2023-10-03 09:36:03 +02:00
Andrew Eisenberg
625b3a5c37 Merge branch 'main' into aeisenberg/multi-token 2023-10-02 20:07:31 -07:00
Andrew Eisenberg
0fa3cf5d8e Use EventEmitter in MultiCancellationToken 2023-10-02 10:21:17 -07:00
Charis Kyriakou
558b9329c5 Extract base functionality for WebviewViewProviders into an abstract class (#2895) 2023-10-02 16:31:52 +01:00
Koen Vlaswinkel
e15b7681db Bump @types/vscode to 1.82.0 2023-10-02 11:14:42 +02:00
Koen Vlaswinkel
6539417d2d Fix recording of binary response bodies 2023-10-02 11:13:18 +02:00
Koen Vlaswinkel
095d56ee37 Fix typo in BasicErrorResponse 2023-10-02 11:02:44 +02:00
Koen Vlaswinkel
7578697e92 Fix response bodies when recording 2023-10-02 11:02:20 +02:00
Anders Starcke Henriksen
6e06e7934b Merge pull request #2889 from github/starcke/lang-context-queries
Apply language context to queries panel
2023-10-02 10:27:00 +02:00
Charis Kyriakou
58249e3efe Dispose event subscriptions in method modeling view (#2882) 2023-10-02 08:57:33 +01:00
Andrew Eisenberg
75540b449f Merge pull request #2884 from github/aeisenberg/avoid-double-restart
Avoid double restarts of the query server
2023-09-29 11:04:37 -07:00
Andrew Eisenberg
04dfc4e647 Move location of multi-cancellation-token
This avoids a code-scanning warning.
2023-09-29 08:31:20 -07:00
Andrew Eisenberg
3ba1712be0 Update Changelog 2023-09-29 08:30:51 -07:00
Andrew Eisenberg
e8f68c1b5f Adds MultiCancellationToken
This is a cancellation token that cancels when any of its constituent
cancellation tokens are cancelled.

This token is used to fix a bug in Find Definitions. Previously, when
clicking `CTRL` (or `CMD` on macs) inside a source file in an archive
and hovering over a token, this will automatically invoke the
definitions finder (in preparation for navigating to the definition).
The only way to cancel is to move down to the message popup and click
cancel there.

However, this is a bug. What _should_ happen is that if a user moves
their mouse away from the token, the operation should cancel.

The underlying problem is that the extension was only listening to the
cancellation token from inside `getLocationsForUriString` the
cancellation token used by the Language Server protocol to cancel
operations in flight was being ignored.

This fix will ensure we are listening to _both_ cancellation tokens
and cancel the query if either are cancelled.
2023-09-29 08:28:35 -07:00
Andrew Eisenberg
a9edb36242 Merge branch 'main' into aeisenberg/avoid-double-restart 2023-09-29 08:26:53 -07:00
Koen Vlaswinkel
40b79f2e61 Improve scenario recording 2023-09-29 14:34:12 +02:00
Charis Kyriakou
3489c26ef6 Styling updates to method modeling panel (#2888) 2023-09-29 13:02:29 +01:00
Koen Vlaswinkel
6b522819fd Merge pull request #2893 from github/version/bump-to-v1.9.2
Bump version to v1.9.2
2023-09-29 13:20:59 +02:00
Koen Vlaswinkel
15b8d2bdb4 Merge pull request #2890 from github/koesie10/add-missing-feature-flag-to-test-plan
Add missing feature flag to test plan
2023-09-29 13:10:27 +02:00
github-actions[bot]
213a03555a Bump version to v1.9.2 2023-09-29 10:27:33 +00:00
Koen Vlaswinkel
0a9a9792ad Merge pull request #2891 from github/koesie10/dispose-webview
Some checks failed
Release / Release (push) Has been cancelled
Release / Publish to VS Code Marketplace (push) Has been cancelled
Release / Publish to Open VSX Registry (push) Has been cancelled
Dispose tracked objects when panel is disposed
2023-09-29 12:19:56 +02:00
Anders Starcke Henriksen
c4fe868826 Add a way to show language for qury history. 2023-09-29 12:15:55 +02:00
Koen Vlaswinkel
4cf67ef799 Fix disposing in ResultsView 2023-09-29 12:00:26 +02:00
Anders Starcke Henriksen
c43d0fa805 Add language to local queries. 2023-09-29 11:54:45 +02:00
Koen Vlaswinkel
0a27c0538d Dispose tracked objects when panel is disposed
This will change the `AbstractWebview` to dispose its tracked objects
(using `this.push`) when the panel is disposed rather than when the
view is disposed. This makes `this.push` actually useful in a view.
Before, the objects would only get disposed when the extension itself
was disposed.
2023-09-29 11:34:59 +02:00
Koen Vlaswinkel
e798663bbb Add missing feature flag to test plan 2023-09-29 11:12:04 +02:00
Anders Starcke Henriksen
169a425e0b Update and add tests. 2023-09-29 11:07:06 +02:00
Koen Vlaswinkel
66fdabf4c8 Merge pull request #2887 from github/v1.9.1
Release v1.9.1
2023-09-29 10:30:40 +02:00
Koen Vlaswinkel
f249b36660 v1.9.1 2023-09-29 10:05:43 +02:00
Anders Starcke Henriksen
1e6b7a6619 Add support for filtering queries panel. 2023-09-29 09:28:44 +02:00
Anders Starcke Henriksen
552a4f6eb3 Merge pull request #2873 from github/starcke/language-context-store
Add language context store.
2023-09-29 09:24:31 +02:00
Andrew Eisenberg
2b17979b6c Update changelog 2023-09-28 14:29:40 -07:00
Andrew Eisenberg
3999ae3728 Avoid double restarts of the query server
Previously, if there was an explicit restart of the query server (eg-
by changing a configuration setting), then the query server process
would be started twice: once by the `close` handler and once by the
restart command.

By adding the `removeAllListeners` to the dispose method, we ensure that
when the query server shuts down gracefully, there won't be a `close`
listener that is going to restart it a second time if there is a
different way of restarting it.
2023-09-28 14:25:35 -07:00
Shati Patel
493e8d915e Hide the language filter behind an extra flag (#2881) 2023-09-28 14:47:25 +00:00
Anders Starcke Henriksen
dc632d5c3d Change to use QueryLanguage | undefined. 2023-09-28 16:43:33 +02:00
Koen Vlaswinkel
ae2d6ce16e Upgrade Node version to 18.15.0 2023-09-28 16:38:54 +02:00
Charis Kyriakou
27a7474f2b Add modeling indicator to method usages panel (#2876) 2023-09-28 15:33:46 +01:00
Koen Vlaswinkel
8b1c52886a Fix typing error for Octokit 2023-09-28 16:09:16 +02:00
Koen Vlaswinkel
f9ddb4080c Switch to stable VS Code version for tests 2023-09-28 16:07:34 +02:00
Koen Vlaswinkel
3afa15a0ce Remove redundant query params 2023-09-28 16:07:05 +02:00
Koen Vlaswinkel
f22714777c Use AppOctokit in tests 2023-09-28 16:06:34 +02:00
Koen Vlaswinkel
469f65a392 Merge pull request #2854 from github/koesie10/bump-min-version
Bump minimum VS Code version to 1.82
2023-09-28 16:04:57 +02:00
Koen Vlaswinkel
9b10a09727 Fix MSW JSON responses 2023-09-28 14:32:33 +02:00
Koen Vlaswinkel
3c10e87529 Switch Octokit to use node-fetch
It seems like Node's native `fetch` implementation isn't quite working
right with Octokit and MSW. This switches to using `node-fetch` like
we're already doing for all other requests (e.g. downloading databases).
2023-09-28 14:32:32 +02:00
Koen Vlaswinkel
a1ea1f8135 Upgrade Octokit and MSW 2023-09-28 14:32:16 +02:00
Robert
ffc90a0c30 Remove startServer method as it now does nothing 2023-09-28 14:32:15 +02:00
Robert
e34f4ed485 Only start server when loading a scenario 2023-09-28 14:32:15 +02:00
Robert
7c1b6e2ff6 Use new types 2023-09-28 14:32:15 +02:00
Robert
e719df711a Upgrade msw to the @next tag 2023-09-28 14:32:15 +02:00
Anders Starcke Henriksen
6accba66fe Rename state to languageFilter. 2023-09-28 14:28:31 +02:00
Anders Starcke Henriksen
a657df4468 Move context manipulation into store and fix initial state. 2023-09-28 14:19:07 +02:00
Koen Vlaswinkel
6aab4b4090 Merge pull request #2875 from github/koesie10/update-csharp-query
Update model editor C# query to return method parameters with parentheses
2023-09-28 14:10:50 +02:00
Anders Starcke Henriksen
c7e5922bd5 Merge branch 'main' into starcke/language-context-store 2023-09-28 14:08:26 +02:00
Anders Starcke Henriksen
bb25874299 Remove TODO. 2023-09-28 14:05:31 +02:00
Shati Patel
d5c78fd67b Indicate which language is selected in the databases view (#2871) 2023-09-28 12:30:11 +02:00
Koen Vlaswinkel
6c5f160eee Update model editor C# query to return method parameters with parentheses 2023-09-28 12:17:06 +02:00
Nora
ecbc458106 Merge pull request #2802 from github/nora/minor-integration-test-improvement
Minor test updates for `db-panel.test`
2023-09-28 12:16:54 +02:00
Koen Vlaswinkel
93652fc75f Merge pull request #2872 from github/koesie10/resolve-queries-from-ql
Resolve model editor queries from CodeQL packs if present
2023-09-28 12:14:41 +02:00
Nora
859eca0195 Merge pull request #2812 from github/nora/remove-rate-limit-code-search
Code Search: don't show rate limit warnings to users
2023-09-28 11:57:15 +02:00
Koen Vlaswinkel
905eaf66aa Implement PR feedback 2023-09-28 11:27:42 +02:00
Nora
7af8b7a274 Use BaseLogger instead of ExtLogger 2023-09-28 08:50:35 +00:00
Nora
a6b6b5a7d6 Show rate limit messages in extension output 2023-09-28 08:24:58 +00:00
Nora
9aff9891d3 Fix comment 2023-09-28 08:18:44 +00:00
Nora
681a15ce45 Extract dbConfigFilePath 2023-09-28 08:17:38 +00:00
Nora
f82b51f7c5 Add comment 2023-09-28 08:17:38 +00:00
Nora
5b7124683a Replace void with await 2023-09-28 08:17:38 +00:00
Charis Kyriakou
031b5076db Update modeling panel when clicking 'view' on model editor (#2874) 2023-09-27 16:59:39 +01:00
Dave Bartolomeo
f1533dde2d Merge pull request #2858 from github/dbartol/long-strings
Use streaming when creating log symbols file.
2023-09-27 09:55:25 -04:00
Koen Vlaswinkel
e9b67dd90c Resolve model editor queries from ql if present 2023-09-27 15:32:01 +02:00
Anders Starcke Henriksen
7bfe0df901 Add language context store. 2023-09-27 15:28:11 +02:00
Koen Vlaswinkel
b1debee244 Merge pull request #2868 from github/koesie10/new-model-editor-queries
Update model editor queries
2023-09-27 12:03:48 +02:00
Charis Kyriakou
3b00d74f47 Hook method modeling view to modeling store (#2870) 2023-09-27 09:57:27 +00:00
Koen Vlaswinkel
4efd3f8fe8 Merge pull request #2865 from github/github-action/bump-cli
Bump CLI Version to v2.14.6 for integration tests
2023-09-27 11:23:40 +02:00
Koen Vlaswinkel
95c512e3e8 Fix compilation errors when using submodule with updated queries 2023-09-27 11:20:57 +02:00
Koen Vlaswinkel
7501f9b81e Merge pull request #2864 from github/koesie10/right-align-format-numbers
Right align and format raw result numbers
2023-09-27 11:03:12 +02:00
Charis Kyriakou
a98b998e5f Add selected method and usage state to modeling store (#2869) 2023-09-27 08:15:51 +01:00
Koen Vlaswinkel
e03d106bc2 Update model editor queries
This updates the model editor queries to the version that will be merged
into the CodeQL repository. There are some slight changes to the output
format, so we slightly need to change the BQRS decoding of those
queries.

The queries themselves were copied from the two PRs with some minor
additions at the end since these were changes in core CodeQL library
files.
2023-09-27 09:05:15 +02:00
Dave Bartolomeo
3c63df2221 Unit tests for SplitBuffer 2023-09-26 17:54:47 -04:00
Charis Kyriakou
c6996771ab Add unsaved tag on method modeling panel (#2867) 2023-09-26 20:17:16 +01:00
Koen Vlaswinkel
e475036721 Right align and format raw result numbers
This changes the formatting for both the local raw results table and the
variant analysis raw results table to right align and format numbers.
2023-09-26 16:18:42 +02:00
Charis Kyriakou
42192fa922 Move modeling status code to its own module (#2866)
* Move ModelingStatus to its own file

* Move getModelingStatus to modeling status module
2023-09-26 13:52:04 +00:00
Koen Vlaswinkel
947084d792 Merge pull request #2863 from github/koesie10/refactor-raw-results-table
Split out components in variant analysis raw results table
2023-09-26 15:41:47 +02:00
Charis Kyriakou
6c1cd71743 Move modeled and modified method state to store (#2861) 2023-09-26 14:23:12 +01:00
github-actions[bot]
c4b890597d Bump CLI version from v2.14.5 to v2.14.6 for integration tests 2023-09-26 13:19:05 +00:00
Charis Kyriakou
93251f8d57 Make MethodModelingViewProvider a disposable object (#2862) 2023-09-26 13:53:23 +01:00
Koen Vlaswinkel
452329b07a Extract RawResultRow to separate file 2023-09-26 14:46:42 +02:00
Koen Vlaswinkel
1afee02e78 Extract RawResultCell to separate file 2023-09-26 14:46:39 +02:00
Koen Vlaswinkel
cbb1de4faf Merge pull request #2847 from github/dependabot/npm_and_yarn/extensions/ql-vscode/types/js-yaml-4.0.6
Bump @types/js-yaml from 3.12.5 to 4.0.6 in /extensions/ql-vscode
2023-09-26 12:41:11 +02:00
Charis Kyriakou
9e92c6c304 Merge pull request #2857 from github/charisk/initial-modeling-store
Introduce modeling store and move some state there
2023-09-26 11:36:46 +01:00
Koen Vlaswinkel
7864844ddd Fix missing string type on dataExtensions 2023-09-26 11:58:33 +02:00
Charis Kyriakou
c77a300f24 Move methods, hideModeledMethods and active editor state to the modeling store 2023-09-26 09:52:59 +00:00
Koen Vlaswinkel
f5fbd7f4cc Switch tests to new types 2023-09-26 11:32:25 +02:00
Koen Vlaswinkel
405292ecd4 Introduce type for QlPackFile 2023-09-26 11:29:19 +02:00
Shati Patel
3be7eb9e15 Add ability to filter DB view by language (#2856) 2023-09-26 09:20:40 +00:00
Dave Bartolomeo
c972a5c0de Use streaming when creating log symbols file. 2023-09-25 14:24:10 -04:00
Koen Vlaswinkel
5ae67fecda Do not export unused types 2023-09-25 15:54:26 +02:00
Charis Kyriakou
d33c26798d Introduce modeling store 2023-09-25 13:42:57 +00:00
Koen Vlaswinkel
9392fb75c8 Format generated schema files with Prettier 2023-09-25 15:35:02 +02:00
Koen Vlaswinkel
e0509f684b Merge pull request #2845 from github/dependabot/npm_and_yarn/extensions/ql-vscode/zip-a-folder-3.1.2
Bump zip-a-folder from 2.0.0 to 3.1.2 in /extensions/ql-vscode
2023-09-25 15:26:01 +02:00
Koen Vlaswinkel
c55e87c64b Generate schema for extension pack file 2023-09-25 15:25:40 +02:00
Koen Vlaswinkel
bc01d73ba5 Fix some type errors in tests 2023-09-25 15:25:39 +02:00
Koen Vlaswinkel
db55e9cd42 Generate schema for extension pack metadata
After the upgrade to the correct types for js-yaml, the return type
of `load` is correctly typed as `unknown`. This means that we can't
use the return value directly, but need to validate it first.

This adds such validation by generating a JSON schema for a newly
created typed. The JSON schema generation is very similar to how we do
it in https://github.com/github/codeql-variant-analysis-action.
2023-09-25 15:25:37 +02:00
Charis Kyriakou
19890b8591 Merge pull request #2855 from github/charisk/tidy-castings
Don't use 'as any' when checking open view
2023-09-25 13:52:14 +01:00
Shati Patel
df1c12f2ba Rearrange DB panel UI commands (#2853) 2023-09-25 14:10:34 +02:00
Charis Kyriakou
bd67afe799 Extract logic into reusable function 2023-09-25 10:08:52 +00:00
Koen Vlaswinkel
7a2876faad Bump zip-a-folder to v3.1.3
This fixes an issue with TypeScript declarations.

See: https://redirect.github.com/maugenst/zip-a-folder/issues/48
2023-09-25 12:07:01 +02:00
Charis Kyriakou
4c9ce2d537 Don't use 'as any' when checking open view 2023-09-25 09:48:57 +00:00
Charis Kyriakou
868ffd79a5 Hide modeling panel when modeling editor is active (#2851) 2023-09-25 10:39:43 +01:00
Koen Vlaswinkel
eb3900f642 Bump minimum VS Code version for warnings 2023-09-25 11:38:39 +02:00
Charis Kyriakou
3934ba7e69 Remove redundant argument from onChange (#2852) 2023-09-25 09:41:36 +01:00
Charis Kyriakou
3e259f14c9 Add modeling inputs to method modeling panel (#2849) 2023-09-22 15:13:40 +00:00
Charis Kyriakou
4323aad254 Fix MethodRow stories to not all show as modeled (#2850) 2023-09-22 11:38:36 +00:00
Charis Kyriakou
cd7c26f2ff Merge pull request #2848 from github/dependabot/npm_and_yarn/extensions/ql-vscode/graphql-16.8.1
Bump graphql from 16.6.0 to 16.8.1 in /extensions/ql-vscode
2023-09-22 11:43:04 +01:00
dependabot[bot]
6e9280b97e Bump graphql from 16.6.0 to 16.8.1 in /extensions/ql-vscode
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.6.0 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.6.0...v16.8.1)

---
updated-dependencies:
- dependency-name: graphql
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-21 17:49:46 +00:00
dependabot[bot]
e43adb6424 Bump @types/js-yaml from 3.12.5 to 4.0.6 in /extensions/ql-vscode
Bumps [@types/js-yaml](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/js-yaml) from 3.12.5 to 4.0.6.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/js-yaml)

---
updated-dependencies:
- dependency-name: "@types/js-yaml"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-21 13:06:11 +00:00
dependabot[bot]
54435d78cf Bump zip-a-folder from 2.0.0 to 3.1.2 in /extensions/ql-vscode
Bumps [zip-a-folder](https://github.com/maugenst/zip-a-folder) from 2.0.0 to 3.1.2.
- [Commits](https://github.com/maugenst/zip-a-folder/commits)

---
updated-dependencies:
- dependency-name: zip-a-folder
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-21 13:04:12 +00:00
Charis Kyriakou
3a1431ca31 Merge pull request #2843 from github/charisk/model-kind-dropdown
Update KindInput component to bring it inline with others
2023-09-21 08:24:54 +01:00
Charis Kyriakou
73f161cdac Clean up model editor onChange callback (#2844) 2023-09-21 08:16:11 +01:00
Robert
ef008a1659 Merge pull request #2840 from github/robertbrignull/node-version
Add docs about the Node.js version
2023-09-20 17:27:18 +01:00
Charis Kyriakou
dc33784dbc Update ModelKindDropdown to be more self-contained 2023-09-20 14:59:11 +00:00
Charis Kyriakou
c4396b764c Rename KindInput to ModelKindDropdown 2023-09-20 14:59:10 +00:00
Charis Kyriakou
4e096400db Extract model output dropdown to its own component (#2839) 2023-09-20 15:58:24 +01:00
Robert
f99177ac21 Merge pull request #2842 from github/robertbrignull/revert-node18
Switch back to Node.js version 16
2023-09-20 14:41:04 +01:00
Robert
f95bf6889b Revert "Disable unit tests using MockGitHubApiServer"
This reverts commit 26450e9236.
2023-09-20 13:48:21 +01:00
Robert
ed48f4ac76 Revert "Change node version to 18.15.0"
This reverts commit 8136328ad6.
2023-09-20 13:48:12 +01:00
Charis Kyriakou
d2f4f33bea Extract type for modeled method kind (#2835) 2023-09-20 13:36:41 +01:00
Robert
a68d5df13b Apply suggestions from code review 2023-09-20 12:25:30 +01:00
Robert
495f632ae2 Update node-version.md 2023-09-20 11:57:16 +01:00
Robert
929f54333b Create vscode-version.md 2023-09-20 11:55:13 +01:00
Charis Kyriakou
4f1a92d09c Extract model input dropdown to its own component (#2837) 2023-09-20 11:25:26 +01:00
Robert
91866971dd Update node-version.md 2023-09-20 11:17:14 +01:00
Robert
9b15b35274 Add files via upload 2023-09-20 11:15:55 +01:00
Robert
b3544b461a Update releasing.md 2023-09-20 11:15:35 +01:00
Robert
53fccdfb2e Create node-version.md 2023-09-20 11:14:06 +01:00
Charis Kyriakou
606bfd7f87 Remove unnecessary spread of modeled method (#2836) 2023-09-20 10:56:47 +01:00
Charis Kyriakou
7d088b749b Update import to fix build (#2838) 2023-09-20 09:53:44 +00:00
Charis Kyriakou
315021ef35 Minor redesign of method modeling title (#2832) 2023-09-20 10:10:40 +01:00
Charis Kyriakou
1dc70fe625 Extract model type dropdown to its own component (#2833) 2023-09-20 10:02:12 +01:00
Charis Kyriakou
36f6531fc4 Move method factories to factories/model-editor (#2834) 2023-09-20 08:55:02 +00:00
Robert
4227ff6338 Mock env.itTelemetryEnable 2023-09-19 17:24:53 +01:00
Robert
ca96cdf879 Mock sendTelemetryErrorEvent and check appropriately 2023-09-19 17:23:47 +01:00
Robert
aad1fee787 Stop mocking sendTelemetryException because we don't use that anywhere in the program 2023-09-19 17:18:38 +01:00
Koen Vlaswinkel
fc735cb83b Merge pull request #2829 from github/koesie10/fix-return-value
Fix incorrect `ReturnType` instead of `ReturnValue`
2023-09-19 17:52:20 +02:00
Robert
48399a9aeb Merge branch 'main' into robertbrignull/telemetry 2023-09-19 16:40:07 +01:00
Robert
c514575bc8 Merge pull request #2831 from github/version/bump-to-v1.9.1
Bump version to v1.9.1
2023-09-19 15:22:21 +01:00
Charis Kyriakou
651bc51ed6 Only download automodel query pack when relevant (#2830) 2023-09-19 15:11:15 +01:00
github-actions[bot]
dea6426c0b Bump version to v1.9.1 2023-09-19 13:49:34 +00:00
Koen Vlaswinkel
96a8bea50a Fix incorrect ReturnType instead of ReturnValue 2023-09-19 15:29:45 +02:00
Robert
297260af88 Merge pull request #2828 from github/v1.9.0
Some checks failed
Release / Release (push) Has been cancelled
Release / Publish to VS Code Marketplace (push) Has been cancelled
Release / Publish to Open VSX Registry (push) Has been cancelled
Release v1.9.0
2023-09-19 13:43:40 +01:00
Robert
26450e9236 Disable unit tests using MockGitHubApiServer 2023-09-19 11:37:25 +01:00
Robert
8d74933c70 v1.9.0 2023-09-19 10:13:20 +01:00
Robert
8136328ad6 Change node version to 18.15.0 2023-09-19 10:11:55 +01:00
Robert
d3df14f860 Merge pull request #2827 from github/robertbrignull/any/stderr
Avoid use of "as any" during error handling
2023-09-19 09:52:51 +01:00
Robert
31e233cc59 Add getChildProcessErrorMessage 2023-09-18 16:34:20 +01:00
Robert
23641a01d9 Avoid checking err.stderr when it's not necessary 2023-09-18 16:33:54 +01:00
Robert
209edf86e7 Merge pull request #2825 from github/robertbrignull/any/preview.ts
Avoid using "as any" in .storybook/preview.ts
2023-09-18 16:32:42 +01:00
Robert
3d410c9ce2 Merge pull request #2826 from github/robertbrignull/any/deploy.ts
Avoid use of "as any" in gulpfile.ts/deploy.ts
2023-09-18 16:27:28 +01:00
Robert
ef38ffaa6d Move resolution of package.json path to inside deployPackage 2023-09-18 15:49:06 +01:00
Robert
f195e93d91 Import type of package.json 2023-09-18 15:47:37 +01:00
Robert
26c301603d Include .storybook in deadcode detection 2023-09-18 15:36:56 +01:00
Robert
b9367095cf Avoid use of "as any" in gulpfile.ts/deploy.ts 2023-09-18 15:26:18 +01:00
Robert
3d415d2852 Avoid using "as any" in preview.ts 2023-09-18 15:14:28 +01:00
Robert
177c770f4d Delete config values that we on longer read directly 2023-09-18 11:15:34 +01:00
Robert
d4033615c8 Use onDidChangeTelemetryEnabled instead of listening for config value changes 2023-09-18 11:14:11 +01:00
Robert
9ec6590548 Use env.isTelemetryEnabled instead of checking configuration values ourselves 2023-09-18 11:14:11 +01:00
Robert
5fc9a73e20 Add telemtry tag to settings 2023-09-18 11:14:11 +01:00
Robert
af5547fb77 Use correct values of telemtetry level 2023-09-18 11:14:03 +01:00
Robert
1b0f0f4566 Merge pull request #2823 from github/robertbrignull/model-editor-changelog
Add changelog entry for model editor
2023-09-18 09:56:45 +01:00
Robert
1c81eb1b07 Merge pull request #2822 from github/robertbrignull/model-editor-feature-flag
Remove codeQL.model.editor feature flag
2023-09-18 09:56:32 +01:00
Robert
a506276635 Update custom telemetry setting description 2023-09-15 16:14:34 +01:00
Robert
f3e72a0ab8 Delete unused relevantSettings field 2023-09-15 16:04:58 +01:00
Robert
c7a9337ac0 Use .qualifiedName instead of writing out manually 2023-09-15 16:04:58 +01:00
Robert
c7bd343f54 Use sendTelemetryErrorEvent when sending errors 2023-09-15 16:04:58 +01:00
Robert
9c76ba35f1 Check both telemetry.telemetryLevel and telemetry.enableTelemetry 2023-09-15 16:04:13 +01:00
Robert
90093fb9f5 Rearrange settings so the telemetry settings are clearer 2023-09-15 15:01:32 +01:00
Robert
f183703b34 Add link to PR 2023-09-15 14:20:35 +01:00
Robert
49a3b534ae Add changelog entry for model editor 2023-09-15 14:19:38 +01:00
Koen Vlaswinkel
d83ca35f0c Merge pull request #2817 from github/koesie10/model-editor-row-tests
Add tests for model editor row and grid components
2023-09-15 14:04:53 +02:00
Koen Vlaswinkel
8bc8ffe1a1 Merge pull request #2820 from github/koesie10/cleanup-temp-directories
Cleanup temporary directories in model editor
2023-09-15 14:04:42 +02:00
Robert
ea2454742c Remove codeQL.model.editor feature flag 2023-09-15 12:22:22 +01:00
Koen Vlaswinkel
d970b51a7c Merge pull request #2814 from github/koesie10/test-model-editor-view
Add simple test for ModelEditorView
2023-09-15 13:19:13 +02:00
Koen Vlaswinkel
69f5d2c134 Cleanup model editor query dir after use 2023-09-15 11:59:11 +02:00
Koen Vlaswinkel
4d4a72bddb Cleanup automodel temporary pack after use 2023-09-15 11:59:11 +02:00
Koen Vlaswinkel
ac9355edd1 Rename restoreView test and add comment 2023-09-15 11:26:25 +02:00
Koen Vlaswinkel
2a9c8ef9dd Merge pull request #2816 from github/koesie10/simple-model-editor-react-tests
Add some simple model editor React unit tests
2023-09-15 11:22:37 +02:00
Koen Vlaswinkel
48f0b92d67 Merge pull request #2819 from github/github-action/bump-cli
Bump CLI Version to v2.14.5 for integration tests
2023-09-15 10:16:46 +02:00
Koen Vlaswinkel
57dcf58518 Merge pull request #2813 from github/koesie10/use-app-in-webview
Use `App` instead of `ExtensionContext` for webviews
2023-09-15 10:12:34 +02:00
github-actions[bot]
43efc9cc4c Bump CLI version from v2.14.4 to v2.14.5 for integration tests 2023-09-14 15:17:49 +00:00
Koen Vlaswinkel
17e0291bb5 Add tests for MethodRow 2023-09-14 14:41:03 +02:00
Koen Vlaswinkel
499060d549 Add tests for ModeledMethodDataGrid 2023-09-14 14:24:24 +02:00
Koen Vlaswinkel
a359fd7054 Add tests for ModeledMethodsList 2023-09-14 14:11:40 +02:00
Koen Vlaswinkel
d3a9426411 Add story and tests for LibraryRow 2023-09-14 14:06:36 +02:00
Koen Vlaswinkel
72faf86678 Add some simple model editor React unit tests 2023-09-14 13:45:45 +02:00
Koen Vlaswinkel
7f20921984 Add simple test for ModelEditorView 2023-09-14 12:20:01 +02:00
Koen Vlaswinkel
d7565fb849 Use App instead of ExtensionContext for webviews 2023-09-14 12:03:18 +02:00
Robert
9175449323 Merge pull request #2811 from github/robertbrignull/fix-jump-to-usage
Fix jumping to usage and updating method modeling panel
2023-09-13 13:07:24 +01:00
Robert
c8487c9431 Fix jumping to usage and updating method modeling panel 2023-09-13 12:41:53 +01:00
Robert
93fc90fcd0 Merge pull request #2790 from github/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2023-09-12 16:53:57 +01:00
Robert
0a705a6bfa Merge pull request #2808 from github/robertbrignull/rename_hideModeledMethods_code
Rename hideModeledApis => hideModeledMethods throughout the code
2023-09-12 16:48:46 +01:00
Robert
b424157ab0 Merge pull request #2806 from github/robertbrignull/rename_externalApiUsages
Renames remaining references to "external API usage" in the code
2023-09-12 16:45:51 +01:00
github-actions[bot]
8c13124dfd Bump CLI version from v2.14.3 to v2.14.4 for integration tests (#2809)
Co-authored-by: github-actions[bot] <github-actions@github.com>
2023-09-12 15:13:34 +00:00
Robert
cbf0f279b9 Update extensions/ql-vscode/src/model-editor/model-editor-view.ts 2023-09-12 15:53:24 +01:00
Robert
a5b22fd0b9 Update extensions/ql-vscode/test/unit-tests/model-editor/bqrs.test.ts 2023-09-12 15:50:48 +01:00
Robert
c6736e8576 Merge pull request #2807 from github/robertbrignull/rename_hideModeledMethods_checkbox
Rename checkbox for hide modeled APIs => methods
2023-09-12 15:47:45 +01:00
Nick Rolfe
acf7270e39 Relax 'resolve ram' test (#2805) 2023-09-12 14:35:42 +01:00
Robert
3ae55e92b8 Rename hideModeledApis => hideModeledMethods throughout 2023-09-12 14:30:22 +01:00
Robert
c3fa83f301 Rename checkbox for hide modeled APIs => methods 2023-09-12 14:18:28 +01:00
Robert
9018cac91a Rename decodeBqrsToExternalApiUsages => decodeBqrsToMethods 2023-09-12 14:02:23 +01:00
Robert
aec742a508 Rename loadExternalApiUsages => loadMethods 2023-09-12 13:59:29 +01:00
Robert
64f6a770c3 Merge pull request #2800 from github/robertbrignull/num-hidden-methods
Add a row to the model editor tables showing how many methods have been hidden
2023-09-12 11:38:48 +01:00
Koen Vlaswinkel
7a2b61432c Merge pull request #2804 from github/koesie10/source-archive-model-editor
Do not add source archive folder for "Model dependency"
2023-09-12 12:10:58 +02:00
Robert
b04ecc959a Use pluralize 2023-09-12 10:18:26 +01:00
Robert
3ee16f88f2 Pull out props to separate variables instead of referencing props.foo 2023-09-12 10:14:10 +01:00
github-actions[bot]
ca079e085d Bump version to v1.8.13 (#2803)
Co-authored-by: github-actions[bot] <github-actions@github.com>
2023-09-11 14:52:33 +00:00
Koen Vlaswinkel
c93dd7a25b Do not add source archive folder for "Model dependency" 2023-09-11 15:45:46 +02:00
Robert
e379320015 Add row showing how many methods are hidden 2023-09-11 11:57:18 +01:00
Robert
bbacb147d2 Hide table entirely if there aren't any non-hidden methods 2023-09-11 11:56:33 +01:00
Robert
58d38ff867 Move calculation of hidden/modelable rows into ModeledMethodDataGrid 2023-09-11 11:52:37 +01:00
Robert
0897cdd96d Merge branch 'main' into dependabot/github_actions/actions/checkout-4 2023-09-07 16:37:03 +01:00
dependabot[bot]
709c49542e Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-07 13:19:53 +00:00
228 changed files with 10568 additions and 4508 deletions

View File

@@ -33,7 +33,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Bump CLI

View File

@@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set the variables
id: set-variables
run: echo "cli-versions=$(cat ./extensions/ql-vscode/supported_cli_versions.json | jq -rc)" >> $GITHUB_OUTPUT
@@ -58,11 +58,11 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: '16.17.1'
node-version-file: extensions/ql-vscode/.nvmrc
cache: 'npm'
cache-dependency-path: extensions/ql-vscode/package-lock.json
@@ -91,7 +91,7 @@ jobs:
echo "ref=$REF" >> "$GITHUB_OUTPUT"
- name: Checkout QL
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: github/codeql
ref: ${{ steps.choose-ref.outputs.ref }}

View File

@@ -19,7 +19,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@main

View File

@@ -11,6 +11,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3

View File

@@ -16,13 +16,13 @@ jobs:
os: [ubuntu-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version: '16.17.1'
node-version-file: extensions/ql-vscode/.nvmrc
cache: 'npm'
cache-dependency-path: extensions/ql-vscode/package-lock.json
@@ -58,13 +58,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version: '16.17.1'
node-version-file: extensions/ql-vscode/.nvmrc
cache: 'npm'
cache-dependency-path: extensions/ql-vscode/package-lock.json
@@ -81,6 +81,8 @@ jobs:
- name: Lint
working-directory: extensions/ql-vscode
env:
NODE_OPTIONS: '--max-old-space-size=4096'
run: |
npm run lint
@@ -99,6 +101,42 @@ jobs:
run: |
npm run find-deadcode
generated:
name: Check generated code
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version-file: extensions/ql-vscode/.nvmrc
cache: 'npm'
cache-dependency-path: extensions/ql-vscode/package-lock.json
- name: Install dependencies
working-directory: extensions/ql-vscode
run: |
npm ci
shell: bash
- name: Check that repo is clean
run: |
git diff --exit-code
git diff --exit-code --cached
- name: Generate code
working-directory: extensions/ql-vscode
run: |
npm run generate
- name: Check for changes
run: |
git diff --exit-code
git diff --exit-code --cached
unit-test:
name: Unit Test
runs-on: ${{ matrix.os }}
@@ -107,13 +145,13 @@ jobs:
os: [ubuntu-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version: '16.17.1'
node-version-file: extensions/ql-vscode/.nvmrc
cache: 'npm'
cache-dependency-path: extensions/ql-vscode/package-lock.json
@@ -141,13 +179,13 @@ jobs:
os: [ubuntu-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-node@v3
with:
node-version: '16.17.1'
node-version-file: extensions/ql-vscode/.nvmrc
cache: 'npm'
cache-dependency-path: extensions/ql-vscode/package-lock.json
@@ -187,7 +225,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set the variable
id: set-variable
run: |
@@ -211,11 +249,11 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: '16.17.1'
node-version-file: extensions/ql-vscode/.nvmrc
cache: 'npm'
cache-dependency-path: extensions/ql-vscode/package-lock.json
@@ -232,7 +270,7 @@ jobs:
shell: bash
- name: Checkout QL
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: github/codeql
ref: 'codeql-cli/${{ needs.get-latest-cli-version.outputs.cli-version }}'

View File

@@ -18,11 +18,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: '16.17.1'
node-version-file: extensions/ql-vscode/.nvmrc
- name: Install dependencies
run: |

View File

@@ -1,5 +1,6 @@
**/* @github/codeql-vscode-reviewers
**/variant-analysis/ @github/code-scanning-secexp-reviewers
**/databases/ @github/code-scanning-secexp-reviewers
**/method-modeling/ @github/code-scanning-secexp-reviewers
**/model-editor/ @github/code-scanning-secexp-reviewers
**/queries-panel/ @github/code-scanning-secexp-reviewers

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

30
docs/node-version.md Normal file
View File

@@ -0,0 +1,30 @@
# Node version
The CodeQL for VS Code extension defines the version of Node.js that it is intended to run with. This Node.js version is used when running most CI and unit tests.
When running in production (i.e. as an extension for a VS Code application) it will use the Node.js version provided by VS Code. This can mean a different Node.js version is used by different users with different versions of VS Code.
We should make sure the CodeQL for VS Code extension works with the Node.js version supplied by all versions of VS Code that we support.
## Checking the version of Node.js supplied by VS Code
You can find this info by seleting "About Visual Studio Code" from the top menu.
![about-vscode](images/about-vscode.png)
## Updating the Node.js version
The following files will need to be updated:
- `extensions/ql-vscode/.nvmrc` - this will enable nvm to automatically switch to the correct Node
version when you're in the project folder. It will also change the Node version the GitHub Actions
workflows use.
- `extensions/ql-vscode/package.json` - the "engines.node: '[VERSION]'" setting
- `extensions/ql-vscode/package.json` - the "@types/node: '[VERSION]'" dependency
Then run `npm install` to update the `extensions/ql-vscode/package-lock.json` file.
## Node.js version used in tests
Unit tests will use whatever version of Node.js is installed locally. In CI this will be the version specified in the workflow.
Integration tests download a copy of VS Code and then will use whatever version of Node.js is provided by VS Code. Our integration tests are currently pinned to an older version of VS Code. See [VS Code version used in tests](./vscode-version.md#vs-code-version-used-in-tests) for more information.

View File

@@ -11,10 +11,7 @@
* New telemetry events are added.
* Deprecation or removal of commands.
* Accumulation of many changes, none of which are individually big enough to warrant a minor bump, but which together are. This does not include changes which are purely internal to the extension, such as refactoring, or which are only available behind a feature flag.
1. Double-check that the node version we're using matches the one used for VS Code. You can find this info by seleting "About Visual Studio Code" from the top menu. If it doesn't match, you will then need to update the node version in the following files:
* `.nvmrc` - this will enable `nvm` to automatically switch to the correct node version when you're in the project folder
* `.github/workflows/main.yml` - all the "node-version: '[VERSION]'" settings
* `.github/workflows/release.yml` - the "node-version: '[VERSION]'" setting
1. Double-check that the node version we're using matches the one used for VS Code. See the [Node.js version instructions](./node-version.md) for more information.
1. Double-check that the extension `package.json` and `package-lock.json` have the version you intend to release. If you are doing a patch release (as opposed to minor or major version) this should already be correct.
1. Create a PR for this release:
* This PR will contain any missing bits from steps 1, 2 and 3. Most of the time, this will just be updating `CHANGELOG.md` with today's date.

View File

@@ -145,8 +145,6 @@ Run one of the above MRVAs, but cancel it from within VS Code:
### CodeQL Model Editor
Note the tests here require the feature flag: `codeQL.model.editor`
#### Test Case 1: Opening the model editor
1. Download the `sofastack/sofa-jraft` java database from GitHub.
@@ -175,6 +173,8 @@ Note that this test requires the feature flag: `codeQL.model.llmGeneration`
#### Test Case 4: Model as dependency
Note that this test requires the feature flag: `codeQL.model.flowGeneration`
1. Click "Model as dependency"
- Check that grouping are now per package (e.g. `com.alipay.sofa.rraft.option` or `com.google.protobuf`)
2. Click "Generate".

33
docs/vscode-version.md Normal file
View File

@@ -0,0 +1,33 @@
# VS Code version
The CodeQL for VS Code extension specifies the versions of VS Code that it is compatible with. VS Code will only offer to install and upgrade the extension when this version range is satisfied.
## Where is the VS Code version specified
1. Hard limit in [`package.json`](https://github.com/github/vscode-codeql/blob/606bfd7f877d9fffe4ff83b78015ab15f8840b12/extensions/ql-vscode/package.json#L16)
This is the value that VS Code understands and respects. If a user does not meet this version requirement then VS Code will not offer to install the CodeQL for VS Code extension, and if the extension is already installed then it will silently refuse to upgrade the extension.
1. Soft limit in [`extension.ts`](https://github.com/github/vscode-codeql/blob/606bfd7f877d9fffe4ff83b78015ab15f8840b12/extensions/ql-vscode/src/extension.ts#L307)
This value is used internally by the CodeQL for VS Code extension and is used to provide a warning to users without blocking them from installing or upgrading. If the extension detects that this version range is not met it will output a warning message to the user prompting them to upgrade their VS Code version to ge the latest features of CodeQL.
## When to update the VS Code version
Generally we should aim to support as wide a range of VS Code versions as we can, so unless there is a reason to do so we do not update the minimum VS Code version requirement.
Reasons for updating the minimum VS Code version include:
- A new feature is included in VS Code. We may want to ensure that it is available to use so we do not have to provide an alternative code path.
- A breaking change has happened in VS Code, and it is not possible to support both new and old versions.
Also consider what percentage of our users are using each VS Code version. This information is available in our telemetry.
## How to update the VS Code version
To provide a good experience to users, it is recommented to update the `MIN_VERSION` in `extension.ts` first and release, and then update the `vscode` version in `package.json` and release again. By stagging this update across two releases it gives users on older VS Code versions a chance to upgrade before it silently refuses to upgrade them.
## VS Code version used in tests
Our integration tests are currently pinned to use an older version of VS Code due to <https://github.com/github/vscode-codeql/issues/2402>.
This version is specified in [`jest-runner-vscode.config.base.js`](https://github.com/github/vscode-codeql/blob/d93f2b67c84e79737b0ce4bb74e31558b5f5166e/extensions/ql-vscode/test/vscode-tests/jest-runner-vscode.config.base.js#L17).
Until this is resolved this will limit us updating our minimum supported version of VS Code.

View File

@@ -1 +1 @@
v16.17.1
v18.15.0

View File

@@ -5,7 +5,15 @@ import { action } from "@storybook/addon-actions";
// Allow all stories/components to use Codicons
import "@vscode/codicons/dist/codicon.css";
(window as any).acquireVsCodeApi = () => ({
import type { VsCodeApi } from "../src/view/vscode-api";
declare global {
interface Window {
acquireVsCodeApi: () => VsCodeApi;
}
}
window.acquireVsCodeApi = () => ({
postMessage: action("post-vscode-message"),
setState: action("set-vscode-state"),
});

View File

@@ -1,5 +1,27 @@
# CodeQL for Visual Studio Code: Changelog
## 1.9.2 - 12 October 2023
- Fix a bug where the query to Find Definitions in database source files would not be cancelled appropriately. [#2885](https://github.com/github/vscode-codeql/pull/2885)
- It is now possible to show the language of query history items using the `%l` specifier in the `codeQL.queryHistory.format` setting. Note that this only works for queries run after this upgrade, and older items will show `unknown` as a language. [#2892](https://github.com/github/vscode-codeql/pull/2892)
- Increase the required version of VS Code to 1.82.0. [#2877](https://github.com/github/vscode-codeql/pull/2877)
- Fix a bug where the query server was restarted twice after configuration changes. [#2884](https://github.com/github/vscode-codeql/pull/2884).
- Add support for the `telemetry.telemetryLevel` setting. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code). [#2824](https://github.com/github/vscode-codeql/pull/2824).
- Fix syntax highlighting directly after import statements with instantiation arguments. [#2792](https://github.com/github/vscode-codeql/pull/2792)
- The `debug.saveBeforeStart` setting is now respected when running variant analyses. [#2950](https://github.com/github/vscode-codeql/pull/2950)
- The 'open database' button of the model editor was renamed to 'open source'. Also, it's now only available if the source archive is available as a workspace folder. [#2945](https://github.com/github/vscode-codeql/pull/2945)
## 1.9.1 - 29 September 2023
- Add warning when using a VS Code version older than 1.82.0. [#2854](https://github.com/github/vscode-codeql/pull/2854)
- Fix a bug when parsing large evaluation log summaries. [#2858](https://github.com/github/vscode-codeql/pull/2858)
- Right-align and format numbers in raw result tables. [#2864](https://github.com/github/vscode-codeql/pull/2864)
- Remove rate limit warning notifications when using Code Search to add repositories to a variant analysis list. [#2812](https://github.com/github/vscode-codeql/pull/2812)
## 1.9.0 - 19 September 2023
- Release the [CodeQL model editor](https://codeql.github.com/docs/codeql/codeql-for-visual-studio-code/using-the-codeql-model-editor) to create CodeQL model packs for Java frameworks. Open the editor using the "CodeQL: Open CodeQL Model Editor (Beta)" command. [#2823](https://github.com/github/vscode-codeql/pull/2823)
## 1.8.12 - 11 September 2023
- Fix a bug where variant analysis queries would fail for queries in the `codeql/java-queries` query pack. [#2786](https://github.com/github/vscode-codeql/pull/2786)

View File

@@ -9,6 +9,7 @@ import {
} from "fs-extra";
import { resolve, join } from "path";
import { isDevBuild } from "./dev";
import type * as packageJsonType from "../package.json";
export interface DeployedPackage {
distPath: string;
@@ -46,12 +47,10 @@ async function copyPackage(
);
}
export async function deployPackage(
packageJsonPath: string,
): Promise<DeployedPackage> {
export async function deployPackage(): Promise<DeployedPackage> {
try {
const packageJson: any = JSON.parse(
await readFile(packageJsonPath, "utf8"),
const packageJson: typeof packageJsonType = JSON.parse(
await readFile(resolve(__dirname, "../package.json"), "utf8"),
);
const distDir = join(__dirname, "../../../dist");

View File

@@ -3,9 +3,7 @@ import { deployPackage } from "./deploy";
import { spawn } from "child-process-promise";
export async function packageExtension(): Promise<void> {
const deployedPackage = await deployPackage(
resolve(__dirname, "../package.json"),
);
const deployedPackage = await deployPackage();
console.log(
`Packaging extension '${deployedPackage.name}@${deployedPackage.version}'...`,
);

View File

@@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "."
"rootDir": ".."
},
"include": ["*.ts"]
}

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" fill="none"
viewBox="0 0 432 432" style="enable-background:new 0 0 432 432;" xml:space="preserve">
<g>
<g>
<g>
<polygon points="234.24,9.067 183.893,59.413 284.587,59.413" fill="#C5C5C5"/>
<polygon points="301.44,304.32 427.947,120.853 427.947,93.973 250.88,93.973 250.88,128.107 376.32,128.107 250.027,310.72
250.027,338.24 432,338.24 432,304.32" fill="#C5C5C5"/>
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227" fill="#C5C5C5"/>
<path d="M226.773,338.24L130.987,93.76H96L0,338.24h39.253l19.627-52.267h109.013l19.627,52.267H226.773z M71.893,250.987
L113.28,140.48l41.387,110.507H71.893z" fill="#C5C5C5"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 953 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2L6 3V6H7V3H14V5.45306L14.2071 5.29286L15 6.08576V3L14 2H7ZM8 4H10V6H8V4ZM5 9H3V11H5V9ZM2 7L1 8V13L2 14H9L10 13V8L9 7H2ZM2 13V8H9V13H2ZM8 10H6V12H8V10ZM13 4H12V7.86388L10.818 6.68192L10.1109 7.38903L12.1465 9.42454L12.8536 9.42454L14.889 7.38908L14.1819 6.68197L13 7.86388V4Z" fill="#C5C5C5"/>
</svg>

Before

Width:  |  Height:  |  Size: 449 B

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 432 432" style="enable-background:new 0 0 432 432;" xml:space="preserve">
<g>
<g>
<g>
<polygon points="234.24,9.067 183.893,59.413 284.587,59.413 "/>
<polygon points="301.44,304.32 427.947,120.853 427.947,93.973 250.88,93.973 250.88,128.107 376.32,128.107 250.027,310.72
250.027,338.24 432,338.24 432,304.32 "/>
<polygon points="234.24,422.933 283.947,373.227 184.533,373.227 "/>
<path d="M226.773,338.24L130.987,93.76H96L0,338.24h39.253l19.627-52.267h109.013l19.627,52.267H226.773z M71.893,250.987
L113.28,140.48l41.387,110.507H71.893z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 894 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2L6 3V6H7V3H14V5.45306L14.2071 5.29286L15 6.08576V3L14 2H7ZM8 4H10V6H8V4ZM5 9H3V11H5V9ZM2 7L1 8V13L2 14H9L10 13V8L9 7H2ZM2 13V8H9V13H2ZM8 10H6V12H8V10ZM13 4H12V7.86388L10.818 6.68192L10.1109 7.38903L12.1465 9.42454L12.8536 9.42454L14.889 7.38908L14.1819 6.68197L13 7.86388V4Z" fill="#424242"/>
</svg>

Before

Width:  |  Height:  |  Size: 449 B

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"description": "CodeQL for Visual Studio Code",
"author": "GitHub",
"private": true,
"version": "1.8.12",
"version": "1.9.2",
"publisher": "GitHub",
"license": "MIT",
"icon": "media/VS-marketplace-CodeQL-icon.png",
@@ -13,8 +13,8 @@
"url": "https://github.com/github/vscode-codeql"
},
"engines": {
"vscode": "^1.67.0",
"node": "^16.17.1",
"vscode": "^1.82.0",
"node": "^18.15.0",
"npm": ">=7.20.6"
},
"categories": [
@@ -110,6 +110,10 @@
"string"
],
"description": "Names of extension packs to include in the evaluation. These are resolved from the locations specified in `additionalPacks`."
},
"additionalRunQueryArgs": {
"type": "object",
"description": "**Internal use only**. Additional arguments to pass to the `runQuery` command of the query server, without validation."
}
}
}
@@ -446,13 +450,20 @@
"type": "boolean",
"default": false,
"scope": "application",
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent to GitHub. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code)"
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the one of the global telemetry settings (`#telemetry.enableTelemetry#` or `#telemetry.telemetryLevel#`) must be enabled for telemetry to be sent to GitHub. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code)",
"tags": [
"telemetry",
"usesOnlineServices"
]
},
"codeQL.telemetry.logTelemetry": {
"type": "boolean",
"default": false,
"scope": "application",
"description": "Specifies whether or not to write telemetry events to the extension log."
"description": "Specifies whether or not to write telemetry events to the extension log.",
"tags": [
"telemetry"
]
}
}
}
@@ -753,6 +764,78 @@
"command": "codeQLDatabases.addDatabaseSource",
"title": "Add Database Source to Workspace"
},
{
"command": "codeQLDatabases.displayAllLanguages",
"title": "All languages"
},
{
"command": "codeQLDatabases.displayAllLanguagesSelected",
"title": "All languages (selected)"
},
{
"command": "codeQLDatabases.displayCpp",
"title": "C/C++"
},
{
"command": "codeQLDatabases.displayCppSelected",
"title": "C/C++ (selected)"
},
{
"command": "codeQLDatabases.displayCsharp",
"title": "C#"
},
{
"command": "codeQLDatabases.displayCsharpSelected",
"title": "C# (selected)"
},
{
"command": "codeQLDatabases.displayGo",
"title": "Go"
},
{
"command": "codeQLDatabases.displayGoSelected",
"title": "Go (selected)"
},
{
"command": "codeQLDatabases.displayJava",
"title": "Java/Kotlin"
},
{
"command": "codeQLDatabases.displayJavaSelected",
"title": "Java/Kotlin (selected)"
},
{
"command": "codeQLDatabases.displayJavascript",
"title": "JavaScript/TypeScript"
},
{
"command": "codeQLDatabases.displayJavascriptSelected",
"title": "JavaScript/TypeScript (selected)"
},
{
"command": "codeQLDatabases.displayPython",
"title": "Python"
},
{
"command": "codeQLDatabases.displayPythonSelected",
"title": "Python (selected)"
},
{
"command": "codeQLDatabases.displayRuby",
"title": "Ruby"
},
{
"command": "codeQLDatabases.displayRubySelected",
"title": "Ruby (selected)"
},
{
"command": "codeQLDatabases.displaySwift",
"title": "Swift"
},
{
"command": "codeQLDatabases.displaySwiftSelected",
"title": "Swift (selected)"
},
{
"command": "codeQL.chooseDatabaseFolder",
"title": "CodeQL: Choose Database from Folder"
@@ -771,19 +854,11 @@
},
{
"command": "codeQLDatabases.sortByName",
"title": "Sort by Name",
"icon": {
"light": "media/light/sort-alpha.svg",
"dark": "media/dark/sort-alpha.svg"
}
"title": "Sort by Name"
},
{
"command": "codeQLDatabases.sortByDateAdded",
"title": "Sort by Date Added",
"icon": {
"light": "media/light/sort-date.svg",
"dark": "media/dark/sort-date.svg"
}
"title": "Sort by Date Added"
},
{
"command": "codeQL.checkForUpdatesToCLI",
@@ -981,16 +1056,6 @@
}
],
"view/title": [
{
"command": "codeQLDatabases.sortByName",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQLDatabases.sortByDateAdded",
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQLDatabases.chooseDatabaseFolder",
"when": "view == codeQLDatabases",
@@ -1011,6 +1076,21 @@
"when": "view == codeQLDatabases",
"group": "navigation"
},
{
"command": "codeQLDatabases.sortByName",
"when": "view == codeQLDatabases",
"group": "1_databases@0"
},
{
"command": "codeQLDatabases.sortByDateAdded",
"when": "view == codeQLDatabases",
"group": "1_databases@1"
},
{
"submenu": "codeQLDatabases.languages",
"when": "view == codeQLDatabases && config.codeQL.canary && config.codeQL.showLanguageFilter",
"group": "2_databases@0"
},
{
"command": "codeQLQueries.createQuery",
"when": "view == codeQLQueries",
@@ -1429,8 +1509,7 @@
"when": "false"
},
{
"command": "codeQL.openModelEditor",
"when": "config.codeQL.canary && config.codeQL.model.editor"
"command": "codeQL.openModelEditor"
},
{
"command": "codeQLQueries.runLocalQueryContextMenu",
@@ -1532,6 +1611,78 @@
"command": "codeQLDatabases.upgradeDatabase",
"when": "false"
},
{
"command": "codeQLDatabases.displayAllLanguages",
"when": "false"
},
{
"command": "codeQLDatabases.displayAllLanguagesSelected",
"when": "false"
},
{
"command": "codeQLDatabases.displayCpp",
"when": "false"
},
{
"command": "codeQLDatabases.displayCppSelected",
"when": "false"
},
{
"command": "codeQLDatabases.displayCsharp",
"when": "false"
},
{
"command": "codeQLDatabases.displayCsharpSelected",
"when": "false"
},
{
"command": "codeQLDatabases.displayGo",
"when": "false"
},
{
"command": "codeQLDatabases.displayGoSelected",
"when": "false"
},
{
"command": "codeQLDatabases.displayJava",
"when": "false"
},
{
"command": "codeQLDatabases.displayJavaSelected",
"when": "false"
},
{
"command": "codeQLDatabases.displayJavascript",
"when": "false"
},
{
"command": "codeQLDatabases.displayJavascriptSelected",
"when": "false"
},
{
"command": "codeQLDatabases.displayPython",
"when": "false"
},
{
"command": "codeQLDatabases.displayPythonSelected",
"when": "false"
},
{
"command": "codeQLDatabases.displayRuby",
"when": "false"
},
{
"command": "codeQLDatabases.displayRubySelected",
"when": "false"
},
{
"command": "codeQLDatabases.displaySwift",
"when": "false"
},
{
"command": "codeQLDatabases.displaySwiftSelected",
"when": "false"
},
{
"command": "codeQLQueryHistory.openQueryContextMenu",
"when": "false"
@@ -1726,8 +1877,88 @@
"command": "codeQL.gotoQLContextEditor",
"when": "editorLangId == ql-summary && config.codeQL.canary"
}
],
"codeQLDatabases.languages": [
{
"command": "codeQLDatabases.displayAllLanguages",
"when": "codeQLDatabases.languageFilter"
},
{
"command": "codeQLDatabases.displayAllLanguagesSelected",
"when": "!codeQLDatabases.languageFilter"
},
{
"command": "codeQLDatabases.displayCpp",
"when": "codeQLDatabases.languageFilter != cpp"
},
{
"command": "codeQLDatabases.displayCppSelected",
"when": "codeQLDatabases.languageFilter == cpp"
},
{
"command": "codeQLDatabases.displayCsharp",
"when": "codeQLDatabases.languageFilter != csharp"
},
{
"command": "codeQLDatabases.displayCsharpSelected",
"when": "codeQLDatabases.languageFilter == csharp"
},
{
"command": "codeQLDatabases.displayGo",
"when": "codeQLDatabases.languageFilter != go"
},
{
"command": "codeQLDatabases.displayGoSelected",
"when": "codeQLDatabases.languageFilter == go"
},
{
"command": "codeQLDatabases.displayJava",
"when": "codeQLDatabases.languageFilter != java"
},
{
"command": "codeQLDatabases.displayJavaSelected",
"when": "codeQLDatabases.languageFilter == java"
},
{
"command": "codeQLDatabases.displayJavascript",
"when": "codeQLDatabases.languageFilter != javascript"
},
{
"command": "codeQLDatabases.displayJavascriptSelected",
"when": "codeQLDatabases.languageFilter == javascript"
},
{
"command": "codeQLDatabases.displayPython",
"when": "codeQLDatabases.languageFilter != python"
},
{
"command": "codeQLDatabases.displayPythonSelected",
"when": "codeQLDatabases.languageFilter == python"
},
{
"command": "codeQLDatabases.displayRuby",
"when": "codeQLDatabases.languageFilter != ruby"
},
{
"command": "codeQLDatabases.displayRubySelected",
"when": "codeQLDatabases.languageFilter == ruby"
},
{
"command": "codeQLDatabases.displaySwift",
"when": "codeQLDatabases.languageFilter != swift"
},
{
"command": "codeQLDatabases.displaySwiftSelected",
"when": "codeQLDatabases.languageFilter == swift"
}
]
},
"submenus": [
{
"id": "codeQLDatabases.languages",
"label": "Languages"
}
],
"viewsContainers": {
"activitybar": [
{
@@ -1771,6 +2002,12 @@
"id": "codeQLEvalLogViewer",
"name": "Evaluator Log Viewer",
"when": "config.codeQL.canary"
},
{
"id": "codeQLMethodModeling",
"type": "webview",
"name": "CodeQL Method Modeling",
"when": "config.codeQL.canary"
}
],
"codeql-methods-usage": [
@@ -1779,14 +2016,6 @@
"name": "CodeQL Methods Usage",
"when": "config.codeQL.canary && codeql.modelEditorOpen"
}
],
"explorer": [
{
"type": "webview",
"id": "codeQLMethodModeling",
"name": "CodeQL Method Modeling",
"when": "config.codeQL.canary && config.codeQL.model.methodModelingView && codeql.modelEditorOpen"
}
]
},
"viewsWelcome": [
@@ -1843,13 +2072,15 @@
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"lint:scenarios": "ts-node scripts/lint-scenarios.ts",
"generate": "npm-run-all -p generate:*",
"generate:schemas": "ts-node scripts/generate-schemas.ts",
"check-types": "find . -type f -name \"tsconfig.json\" -not -path \"./node_modules/*\" | sed -r 's|/[^/]+$||' | sort | uniq | xargs -I {} sh -c \"echo Checking types in {} && cd {} && npx tsc --noEmit\"",
"postinstall": "patch-package",
"prepare": "cd ../.. && husky install"
},
"dependencies": {
"@octokit/plugin-retry": "^4.1.6",
"@octokit/rest": "^19.0.4",
"@octokit/plugin-retry": "^6.0.1",
"@octokit/rest": "^20.0.2",
"@vscode/codicons": "^0.0.31",
"@vscode/debugadapter": "^1.59.0",
"@vscode/debugprotocol": "^1.59.0",
@@ -1863,10 +2094,10 @@
"fs-extra": "^11.1.1",
"immutable": "^4.0.0",
"js-yaml": "^4.1.0",
"msw": "^1.2.0",
"nanoid": "^3.2.0",
"msw": "^0.0.0-fetch.rc-20",
"nanoid": "^5.0.1",
"node-fetch": "^2.6.7",
"p-queue": "^6.0.0",
"p-queue": "^7.4.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"semver": "^7.5.2",
@@ -1883,7 +2114,7 @@
"vscode-languageclient": "^8.0.2",
"vscode-test-adapter-api": "^1.7.0",
"vscode-test-adapter-util": "^0.7.0",
"zip-a-folder": "^2.0.0"
"zip-a-folder": "^3.1.3"
},
"devDependencies": {
"@babel/core": "^7.18.13",
@@ -1893,7 +2124,7 @@
"@babel/preset-typescript": "^7.21.4",
"@faker-js/faker": "^8.0.2",
"@github/markdownlint-github": "^0.3.0",
"@octokit/plugin-throttling": "^5.0.1",
"@octokit/plugin-throttling": "^8.0.0",
"@storybook/addon-actions": "^7.1.0",
"@storybook/addon-essentials": "^7.1.0",
"@storybook/addon-interactions": "^7.1.0",
@@ -1917,9 +2148,9 @@
"@types/gulp": "^4.0.9",
"@types/gulp-replace": "^1.1.0",
"@types/jest": "^29.0.2",
"@types/js-yaml": "^3.12.5",
"@types/js-yaml": "^4.0.6",
"@types/nanoid": "^3.0.0",
"@types/node": "^16.11.25",
"@types/node": "18.15.0",
"@types/node-fetch": "^2.5.2",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
@@ -1931,7 +2162,7 @@
"@types/through2": "^2.0.36",
"@types/tmp": "^0.1.0",
"@types/unzipper": "^0.10.1",
"@types/vscode": "^1.67.0",
"@types/vscode": "^1.82.0",
"@types/webpack": "^5.28.0",
"@types/webpack-env": "^1.18.0",
"@typescript-eslint/eslint-plugin": "^6.2.1",
@@ -1959,7 +2190,6 @@
"gulp": "^4.0.2",
"gulp-esbuild": "^0.10.5",
"gulp-replace": "^1.1.3",
"gulp-sourcemaps": "^3.0.0",
"gulp-typescript": "^5.0.1",
"husky": "^8.0.0",
"jest": "^29.0.3",

View File

@@ -14,7 +14,8 @@
import { pathExists, readJson, writeJson } from "fs-extra";
import { resolve, relative } from "path";
import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest";
import { Octokit } from "@octokit/core";
import { type RestEndpointMethodTypes } from "@octokit/rest";
import { throttling } from "@octokit/plugin-throttling";
import { getFiles } from "./util/files";
@@ -22,6 +23,7 @@ import type { GitHubApiRequest } from "../src/common/mock-gh-api/gh-api-request"
import { isGetVariantAnalysisRequest } from "../src/common/mock-gh-api/gh-api-request";
import { VariantAnalysis } from "../src/variant-analysis/gh-api/variant-analysis";
import { RepositoryWithMetadata } from "../src/variant-analysis/gh-api/repository";
import { AppOctokit } from "../src/common/octokit";
const extensionDirectory = resolve(__dirname, "..");
const scenariosDirectory = resolve(
@@ -31,7 +33,7 @@ const scenariosDirectory = resolve(
// Make sure we don't run into rate limits by automatically waiting until we can
// make another request.
const MyOctokit = Octokit.plugin(throttling);
const MyOctokit = AppOctokit.plugin(throttling);
const auth = process.env.GITHUB_TOKEN;

View File

@@ -6,6 +6,7 @@ import { exit } from "process";
function ignoreFile(file: string): boolean {
return (
containsPath("gulpfile.ts", file) ||
containsPath(".storybook", file) ||
containsPath(join("src", "stories"), file) ||
pathsEqual(
join("test", "vscode-tests", "jest-runner-installed-extensions.ts"),

View File

@@ -0,0 +1,72 @@
import { createGenerator } from "ts-json-schema-generator";
import { join, resolve } from "path";
import { outputFile } from "fs-extra";
import { format, resolveConfig } from "prettier";
const extensionDirectory = resolve(__dirname, "..");
const schemas = [
{
path: join(
extensionDirectory,
"src",
"model-editor",
"extension-pack-metadata.ts",
),
type: "ExtensionPackMetadata",
schemaPath: join(
extensionDirectory,
"src",
"model-editor",
"extension-pack-metadata.schema.json",
),
},
{
path: join(
extensionDirectory,
"src",
"model-editor",
"model-extension-file.ts",
),
type: "ModelExtensionFile",
schemaPath: join(
extensionDirectory,
"src",
"model-editor",
"model-extension-file.schema.json",
),
},
];
async function generateSchema(
schemaDefinition: (typeof schemas)[number],
): Promise<void> {
const schema = createGenerator({
path: schemaDefinition.path,
tsconfig: resolve(extensionDirectory, "tsconfig.json"),
type: schemaDefinition.type,
skipTypeCheck: true,
topRef: true,
additionalProperties: true,
}).createSchema(schemaDefinition.type);
const schemaJson = JSON.stringify(schema, null, 2);
const prettierOptions = await resolveConfig(schemaDefinition.schemaPath);
const formattedSchemaJson = await format(schemaJson, {
...prettierOptions,
filepath: schemaDefinition.schemaPath,
});
await outputFile(schemaDefinition.schemaPath, formattedSchemaJson);
}
async function generateSchemas() {
await Promise.all(schemas.map(generateSchema));
}
generateSchemas().catch((e: unknown) => {
console.error(e);
process.exit(2);
});

View File

@@ -6,7 +6,6 @@ import { dirname, join, delimiter } from "path";
import * as sarif from "sarif";
import { SemVer } from "semver";
import { Readable } from "stream";
import { StringDecoder } from "string_decoder";
import tk from "tree-kill";
import { promisify } from "util";
import { CancellationToken, Disposable, Uri } from "vscode";
@@ -19,6 +18,7 @@ import {
} from "./distribution";
import {
assertNever,
getChildProcessErrorMessage,
getErrorMessage,
getErrorStack,
} from "../common/helpers-pure";
@@ -30,6 +30,7 @@ import { CompilationMessage } from "../query-server/legacy-messages";
import { sarifParser } from "../common/sarif-parser";
import { App } from "../common/app";
import { QueryLanguage } from "../common/query-language";
import { LINE_ENDINGS, splitStreamAtSeparators } from "../common/split-stream";
/**
* The version of the SARIF format that we are using.
@@ -547,9 +548,7 @@ export class CodeQLCliServer implements Disposable {
yield JSON.parse(event) as EventType;
} catch (err) {
throw new Error(
`Parsing output of ${description} failed: ${
(err as any).stderr || getErrorMessage(err)
}`,
`Parsing output of ${description} failed: ${getErrorMessage(err)}`,
);
}
}
@@ -647,9 +646,7 @@ export class CodeQLCliServer implements Disposable {
return JSON.parse(result) as OutputType;
} catch (err) {
throw new Error(
`Parsing output of ${description} failed: ${
(err as any).stderr || getErrorMessage(err)
}`,
`Parsing output of ${description} failed: ${getErrorMessage(err)}`,
);
}
}
@@ -1647,125 +1644,18 @@ export async function runCodeQlCliCommand(
return result.stdout;
} catch (err) {
throw new Error(
`${description} failed: ${(err as any).stderr || getErrorMessage(err)}`,
`${description} failed: ${getChildProcessErrorMessage(err)}`,
);
}
}
/**
* Buffer to hold state used when splitting a text stream into lines.
*/
class SplitBuffer {
private readonly decoder = new StringDecoder("utf8");
private readonly maxSeparatorLength: number;
private buffer = "";
private searchIndex = 0;
constructor(private readonly separators: readonly string[]) {
this.maxSeparatorLength = separators
.map((s) => s.length)
.reduce((a, b) => Math.max(a, b), 0);
}
/**
* Append new text data to the buffer.
* @param chunk The chunk of data to append.
*/
public addChunk(chunk: Buffer): void {
this.buffer += this.decoder.write(chunk);
}
/**
* Signal that the end of the input stream has been reached.
*/
public end(): void {
this.buffer += this.decoder.end();
this.buffer += this.separators[0]; // Append a separator to the end to ensure the last line is returned.
}
/**
* A version of startsWith that isn't overriden by a broken version of ms-python.
*
* The definition comes from
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
* which is CC0/public domain
*
* See https://github.com/github/vscode-codeql/issues/802 for more context as to why we need it.
*/
private static startsWith(
s: string,
searchString: string,
position: number,
): boolean {
const pos = position > 0 ? position | 0 : 0;
return s.substring(pos, pos + searchString.length) === searchString;
}
/**
* Extract the next full line from the buffer, if one is available.
* @returns The text of the next available full line (without the separator), or `undefined` if no
* line is available.
*/
public getNextLine(): string | undefined {
while (this.searchIndex <= this.buffer.length - this.maxSeparatorLength) {
for (const separator of this.separators) {
if (SplitBuffer.startsWith(this.buffer, separator, this.searchIndex)) {
const line = this.buffer.slice(0, this.searchIndex);
this.buffer = this.buffer.slice(this.searchIndex + separator.length);
this.searchIndex = 0;
return line;
}
}
this.searchIndex++;
}
return undefined;
}
}
/**
* Splits a text stream into lines based on a list of valid line separators.
* @param stream The text stream to split. This stream will be fully consumed.
* @param separators The list of strings that act as line separators.
* @returns A sequence of lines (not including separators).
*/
async function* splitStreamAtSeparators(
stream: Readable,
separators: string[],
): AsyncGenerator<string, void, unknown> {
const buffer = new SplitBuffer(separators);
for await (const chunk of stream) {
buffer.addChunk(chunk);
let line: string | undefined;
do {
line = buffer.getNextLine();
if (line !== undefined) {
yield line;
}
} while (line !== undefined);
}
buffer.end();
let line: string | undefined;
do {
line = buffer.getNextLine();
if (line !== undefined) {
yield line;
}
} while (line !== undefined);
}
/**
* Standard line endings for splitting human-readable text.
*/
const lineEndings = ["\r\n", "\r", "\n"];
/**
* Log a text stream to a `Logger` interface.
* @param stream The stream to log.
* @param logger The logger that will consume the stream output.
*/
async function logStream(stream: Readable, logger: BaseLogger): Promise<void> {
for await (const line of splitStreamAtSeparators(stream, lineEndings)) {
for await (const line of splitStreamAtSeparators(stream, LINE_ENDINGS)) {
// Await the result of log here in order to ensure the logs are written in the correct order.
await logger.log(line);
}

View File

@@ -12,7 +12,6 @@ import type {
} from "../variant-analysis/shared/variant-analysis";
import type { QLDebugConfiguration } from "../debugger/debug-configuration";
import type { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
import type { Usage } from "../model-editor/method";
// A command function matching the signature that VS Code calls when
// a command is invoked from a context menu on a TreeView with
@@ -219,6 +218,24 @@ export type LocalDatabasesCommands = {
"codeQLDatabases.chooseDatabaseGithub": () => Promise<void>;
"codeQLDatabases.sortByName": () => Promise<void>;
"codeQLDatabases.sortByDateAdded": () => Promise<void>;
"codeQLDatabases.displayAllLanguages": () => Promise<void>;
"codeQLDatabases.displayCpp": () => Promise<void>;
"codeQLDatabases.displayCsharp": () => Promise<void>;
"codeQLDatabases.displayGo": () => Promise<void>;
"codeQLDatabases.displayJava": () => Promise<void>;
"codeQLDatabases.displayJavascript": () => Promise<void>;
"codeQLDatabases.displayPython": () => Promise<void>;
"codeQLDatabases.displayRuby": () => Promise<void>;
"codeQLDatabases.displaySwift": () => Promise<void>;
"codeQLDatabases.displayAllLanguagesSelected": () => Promise<void>;
"codeQLDatabases.displayCppSelected": () => Promise<void>;
"codeQLDatabases.displayCsharpSelected": () => Promise<void>;
"codeQLDatabases.displayGoSelected": () => Promise<void>;
"codeQLDatabases.displayJavaSelected": () => Promise<void>;
"codeQLDatabases.displayJavascriptSelected": () => Promise<void>;
"codeQLDatabases.displayPythonSelected": () => Promise<void>;
"codeQLDatabases.displayRubySelected": () => Promise<void>;
"codeQLDatabases.displaySwiftSelected": () => Promise<void>;
// Database panel context menu
"codeQLDatabases.setCurrentDatabase": (
@@ -305,8 +322,9 @@ export type PackagingCommands = {
export type ModelEditorCommands = {
"codeQL.openModelEditor": () => Promise<void>;
"codeQLModelEditor.jumpToUsageLocation": (
usage: Usage,
"codeQL.openModelEditorFromModelingPanel": () => Promise<void>;
"codeQLModelEditor.jumpToMethod": (
methodSignature: string,
databaseItem: DatabaseItem,
) => Promise<void>;
};

View File

@@ -9,10 +9,16 @@ export type DisposeHandler = (disposable: Disposable) => void;
/**
* Base class to make it easier to implement a `Disposable` that owns other disposable object.
*/
export abstract class DisposableObject implements Disposable {
export class DisposableObject implements Disposable {
private disposables: Disposable[] = [];
private tracked?: Set<Disposable> = undefined;
constructor(...dispoables: Disposable[]) {
for (const d of dispoables) {
this.push(d);
}
}
/**
* Adds `obj` to a list of objects to dispose when `this` is disposed. Objects added by `push` are
* disposed in reverse order of being added.

View File

@@ -67,3 +67,26 @@ export function asError(e: unknown): Error {
return e instanceof Error ? e : new Error(String(e));
}
/**
* Get error message when the error may have come from a method from the `child_process` module.
*/
export function getChildProcessErrorMessage(e: unknown): string {
return isChildProcessError(e) ? e.stderr : getErrorMessage(e);
}
/**
* Error thrown from methods from the `child_process` module.
*/
interface ChildProcessError {
readonly stderr: string;
}
function isChildProcessError(e: unknown): e is ChildProcessError {
return (
typeof e === "object" &&
e !== null &&
"stderr" in e &&
typeof e.stderr === "string"
);
}

View File

@@ -17,10 +17,14 @@ import {
} from "../variant-analysis/shared/variant-analysis-filter-sort";
import { ErrorLike } from "../common/errors";
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
import { Method, Usage } from "../model-editor/method";
import { Method } from "../model-editor/method";
import { ModeledMethod } from "../model-editor/modeled-method";
import { ModelEditorViewState } from "../model-editor/shared/view-state";
import {
MethodModelingPanelViewState,
ModelEditorViewState,
} from "../model-editor/shared/view-state";
import { Mode } from "../model-editor/shared/mode";
import { QueryLanguage } from "./query-language";
/**
* This module contains types and code that are shared between
@@ -51,6 +55,7 @@ export const RAW_RESULTS_LIMIT = 10000;
export interface DatabaseInfo {
name: string;
databaseUri: string;
language?: QueryLanguage;
}
/** Arbitrary query metadata */
@@ -500,14 +505,14 @@ interface SetMethodsMessage {
methods: Method[];
}
interface LoadModeledMethodsMessage {
t: "loadModeledMethods";
modeledMethods: Record<string, ModeledMethod>;
interface SetModeledMethodsMessage {
t: "setModeledMethods";
methods: Record<string, ModeledMethod[]>;
}
interface AddModeledMethodsMessage {
t: "addModeledMethods";
modeledMethods: Record<string, ModeledMethod>;
interface SetModifiedMethodsMessage {
t: "setModifiedMethods";
methodSignatures: string[];
}
interface SetInProgressMethodsMessage {
@@ -521,10 +526,9 @@ interface SwitchModeMessage {
mode: Mode;
}
interface JumpToUsageMessage {
t: "jumpToUsage";
method: Method;
usage: Usage;
interface JumpToMethodMessage {
t: "jumpToMethod";
methodSignature: string;
}
interface OpenDatabaseMessage {
@@ -541,8 +545,7 @@ interface RefreshMethods {
interface SaveModeledMethods {
t: "saveModeledMethods";
methods: Method[];
modeledMethods: Record<string, ModeledMethod>;
methodSignatures?: string[];
}
interface GenerateMethodMessage {
@@ -552,8 +555,7 @@ interface GenerateMethodMessage {
interface GenerateMethodsFromLlmMessage {
t: "generateMethodsFromLlm";
packageName: string;
methods: Method[];
modeledMethods: Record<string, ModeledMethod>;
methodSignatures: string[];
}
interface StopGeneratingMethodsFromLlmMessage {
@@ -565,39 +567,96 @@ interface ModelDependencyMessage {
t: "modelDependency";
}
interface HideModeledApisMessage {
t: "hideModeledApis";
hideModeledApis: boolean;
interface HideModeledMethodsMessage {
t: "hideModeledMethods";
hideModeledMethods: boolean;
}
interface SetModeledMethodMessage {
t: "setModeledMethod";
method: ModeledMethod;
}
interface SetMultipleModeledMethodsMessage {
t: "setMultipleModeledMethods";
methodSignature: string;
modeledMethods: ModeledMethod[];
}
interface SetInModelingModeMessage {
t: "setInModelingMode";
inModelingMode: boolean;
}
interface RevealMethodMessage {
t: "revealMethod";
methodSignature: string;
}
export type ToModelEditorMessage =
| SetExtensionPackStateMessage
| SetMethodsMessage
| LoadModeledMethodsMessage
| AddModeledMethodsMessage
| SetInProgressMethodsMessage;
| SetModeledMethodsMessage
| SetModifiedMethodsMessage
| SetInProgressMethodsMessage
| RevealMethodMessage;
export type FromModelEditorMessage =
| ViewLoadedMsg
| CommonFromViewMessages
| SwitchModeMessage
| RefreshMethods
| OpenDatabaseMessage
| OpenExtensionPackMessage
| JumpToUsageMessage
| JumpToMethodMessage
| SaveModeledMethods
| GenerateMethodMessage
| GenerateMethodsFromLlmMessage
| StopGeneratingMethodsFromLlmMessage
| ModelDependencyMessage
| HideModeledApisMessage;
| HideModeledMethodsMessage
| SetModeledMethodMessage;
export type FromMethodModelingMessage =
| TelemetryMessage
| UnhandledErrorMessage;
interface SetMethodMessage {
t: "setMethod";
interface RevealInEditorMessage {
t: "revealInModelEditor";
method: Method;
}
export type ToMethodModelingMessage = SetMethodMessage;
interface StartModelingMessage {
t: "startModeling";
}
export type FromMethodModelingMessage =
| CommonFromViewMessages
| SetModeledMethodMessage
| RevealInEditorMessage
| StartModelingMessage;
interface SetMethodModelingPanelViewStateMessage {
t: "setMethodModelingPanelViewState";
viewState: MethodModelingPanelViewState;
}
interface SetMethodMessage {
t: "setMethod";
method: Method | undefined;
}
interface SetMethodModifiedMessage {
t: "setMethodModified";
isModified: boolean;
}
interface SetSelectedMethodMessage {
t: "setSelectedMethod";
method: Method;
modeledMethods: ModeledMethod[];
isModified: boolean;
}
export type ToMethodModelingMessage =
| SetMethodModelingPanelViewStateMessage
| SetMethodMessage
| SetMultipleModeledMethodsMessage
| SetMethodModifiedMessage
| SetSelectedMethodMessage
| SetInModelingModeMessage;

View File

@@ -17,7 +17,7 @@ export enum RequestKind {
AutoModel = "autoModel",
}
interface BasicErorResponse {
export interface BasicErrorResponse {
message: string;
}
@@ -27,7 +27,7 @@ interface GetRepoRequest {
};
response: {
status: number;
body: Repository | BasicErorResponse | undefined;
body: Repository | BasicErrorResponse | undefined;
};
}
@@ -37,7 +37,7 @@ interface SubmitVariantAnalysisRequest {
};
response: {
status: number;
body?: VariantAnalysis | BasicErorResponse;
body?: VariantAnalysis | BasicErrorResponse;
};
}
@@ -47,7 +47,7 @@ interface GetVariantAnalysisRequest {
};
response: {
status: number;
body?: VariantAnalysis | BasicErorResponse;
body?: VariantAnalysis | BasicErrorResponse;
};
}
@@ -58,7 +58,7 @@ interface GetVariantAnalysisRepoRequest {
};
response: {
status: number;
body?: VariantAnalysisRepoTask | BasicErorResponse;
body?: VariantAnalysisRepoTask | BasicErrorResponse;
};
}
@@ -74,6 +74,13 @@ export interface GetVariantAnalysisRepoResultRequest {
};
}
export interface CodeSearchResponse {
total_count: number;
items: Array<{
repository: Repository;
}>;
}
interface CodeSearchRequest {
request: {
kind: RequestKind.CodeSearch;
@@ -81,16 +88,14 @@ interface CodeSearchRequest {
};
response: {
status: number;
body?: {
total_count?: number;
items?: Array<{
repository: Repository;
}>;
};
message?: string;
body?: CodeSearchResponse | BasicErrorResponse;
};
}
export interface AutoModelResponse {
models: string;
}
interface AutoModelRequest {
request: {
kind: RequestKind.AutoModel;
@@ -100,10 +105,7 @@ interface AutoModelRequest {
};
response: {
status: number;
body?: {
models: string;
};
message?: string;
body?: AutoModelResponse | BasicErrorResponse;
};
}

View File

@@ -1,30 +1,33 @@
import { ensureDir, writeFile } from "fs-extra";
import { join } from "path";
import { MockedRequest } from "msw";
import { SetupServer } from "msw/node";
import { IsomorphicResponse } from "@mswjs/interceptors";
import { Headers } from "headers-polyfill";
import fetch from "node-fetch";
import { SetupServer } from "msw/node";
import { DisposableObject } from "../disposable-object";
import { gzipDecode } from "../zlib";
import {
AutoModelResponse,
BasicErrorResponse,
CodeSearchResponse,
GetVariantAnalysisRepoResultRequest,
GitHubApiRequest,
RequestKind,
} from "./gh-api-request";
import {
VariantAnalysis,
VariantAnalysisRepoTask,
} from "../../variant-analysis/gh-api/variant-analysis";
import { Repository } from "../../variant-analysis/gh-api/repository";
export class Recorder extends DisposableObject {
private readonly allRequests = new Map<string, MockedRequest>();
private currentRecordedScenario: GitHubApiRequest[] = [];
private _isRecording = false;
constructor(private readonly server: SetupServer) {
super();
this.onRequestStart = this.onRequestStart.bind(this);
this.onResponseBypass = this.onResponseBypass.bind(this);
}
@@ -45,7 +48,6 @@ export class Recorder extends DisposableObject {
this.clear();
this.server.events.on("request:start", this.onRequestStart);
this.server.events.on("response:bypass", this.onResponseBypass);
}
@@ -56,13 +58,11 @@ export class Recorder extends DisposableObject {
this._isRecording = false;
this.server.events.removeListener("request:start", this.onRequestStart);
this.server.events.removeListener("response:bypass", this.onResponseBypass);
}
public clear() {
this.currentRecordedScenario = [];
this.allRequests.clear();
}
public async save(scenariosPath: string, name: string): Promise<string> {
@@ -91,7 +91,7 @@ export class Recorder extends DisposableObject {
let bodyFileLink = undefined;
if (writtenRequest.response.body) {
await writeFile(bodyFilePath, writtenRequest.response.body || "");
await writeFile(bodyFilePath, writtenRequest.response.body);
bodyFileLink = `file:${bodyFileName}`;
}
@@ -112,33 +112,18 @@ export class Recorder extends DisposableObject {
return scenarioDirectory;
}
private onRequestStart(request: MockedRequest): void {
private async onResponseBypass(
response: Response,
request: Request,
_requestId: string,
): Promise<void> {
if (request.headers.has("x-vscode-codeql-msw-bypass")) {
return;
}
this.allRequests.set(request.id, request);
}
private async onResponseBypass(
response: IsomorphicResponse,
requestId: string,
): Promise<void> {
const request = this.allRequests.get(requestId);
this.allRequests.delete(requestId);
if (!request) {
return;
}
if (response.body === undefined) {
return;
}
const gitHubApiRequest = await createGitHubApiRequest(
request.url.toString(),
response.status,
response.body,
response.headers,
request.url,
response,
);
if (!gitHubApiRequest) {
return;
@@ -150,14 +135,14 @@ export class Recorder extends DisposableObject {
async function createGitHubApiRequest(
url: string,
status: number,
body: string,
headers: Headers,
response: Response,
): Promise<GitHubApiRequest | undefined> {
if (!url) {
return undefined;
}
const status = response.status;
if (url.match(/\/repos\/[a-zA-Z0-9-_.]+\/[a-zA-Z0-9-_.]+$/)) {
return {
request: {
@@ -165,7 +150,9 @@ async function createGitHubApiRequest(
},
response: {
status,
body: JSON.parse(body),
body: await jsonResponseBody<
Repository | BasicErrorResponse | undefined
>(response),
},
};
}
@@ -179,7 +166,9 @@ async function createGitHubApiRequest(
},
response: {
status,
body: JSON.parse(body),
body: await jsonResponseBody<
VariantAnalysis | BasicErrorResponse | undefined
>(response),
},
};
}
@@ -195,7 +184,9 @@ async function createGitHubApiRequest(
},
response: {
status,
body: JSON.parse(body),
body: await jsonResponseBody<
VariantAnalysis | BasicErrorResponse | undefined
>(response),
},
};
}
@@ -211,7 +202,9 @@ async function createGitHubApiRequest(
},
response: {
status,
body: JSON.parse(body),
body: await jsonResponseBody<
VariantAnalysisRepoTask | BasicErrorResponse | undefined
>(response),
},
};
}
@@ -238,9 +231,10 @@ async function createGitHubApiRequest(
repositoryId: parseInt(repoDownloadMatch.groups.repositoryId, 10),
},
response: {
status,
status: response.status,
body: responseBuffer,
contentType: headers.get("content-type") ?? "application/octet-stream",
contentType:
response.headers.get("content-type") ?? "application/octet-stream",
},
};
}
@@ -254,7 +248,9 @@ async function createGitHubApiRequest(
},
response: {
status,
body: JSON.parse(body),
body: await jsonResponseBody<
CodeSearchResponse | BasicErrorResponse | undefined
>(response),
},
};
}
@@ -269,7 +265,9 @@ async function createGitHubApiRequest(
},
response: {
status,
body: JSON.parse(body),
body: await jsonResponseBody<
BasicErrorResponse | AutoModelResponse | undefined
>(response),
},
};
}
@@ -277,6 +275,26 @@ async function createGitHubApiRequest(
return undefined;
}
async function responseBody(response: Response): Promise<Uint8Array> {
const body = await response.arrayBuffer();
const view = new Uint8Array(body);
if (view[0] === 0x1f && view[1] === 0x8b) {
// Response body is gzipped, so we need to un-gzip it.
return await gzipDecode(view);
} else {
return view;
}
}
async function jsonResponseBody<T>(response: Response): Promise<T> {
const body = await responseBody(response);
const text = new TextDecoder("utf-8").decode(body);
return JSON.parse(text);
}
function shouldWriteBodyToFile(
request: GitHubApiRequest,
): request is GetVariantAnalysisRepoResultRequest {

View File

@@ -1,6 +1,6 @@
import { join } from "path";
import { readdir, readJson, readFile } from "fs-extra";
import { DefaultBodyType, MockedRequest, rest, RestHandler } from "msw";
import { RequestHandler, rest } from "msw";
import {
GitHubApiRequest,
isAutoModelRequest,
@@ -14,7 +14,19 @@ import {
const baseUrl = "https://api.github.com";
type RequestHandler = RestHandler<MockedRequest<DefaultBodyType>>;
const jsonResponse = <T>(
body: T,
init?: ResponseInit,
contentType = "application/json",
): Response => {
return new Response(JSON.stringify(body), {
...init,
headers: {
"Content-Type": contentType,
...init?.headers,
},
});
};
export async function createRequestHandlers(
scenarioDirPath: string,
@@ -82,11 +94,10 @@ function createGetRepoRequestHandler(
const getRepoRequest = getRepoRequests[0];
return rest.get(`${baseUrl}/repos/:owner/:name`, (_req, res, ctx) => {
return res(
ctx.status(getRepoRequest.response.status),
ctx.json(getRepoRequest.response.body),
);
return rest.get(`${baseUrl}/repos/:owner/:name`, () => {
return jsonResponse(getRepoRequest.response.body, {
status: getRepoRequest.response.status,
});
});
}
@@ -105,11 +116,10 @@ function createSubmitVariantAnalysisRequestHandler(
return rest.post(
`${baseUrl}/repositories/:controllerRepoId/code-scanning/codeql/variant-analyses`,
(_req, res, ctx) => {
return res(
ctx.status(getRepoRequest.response.status),
ctx.json(getRepoRequest.response.body),
);
() => {
return jsonResponse(getRepoRequest.response.body, {
status: getRepoRequest.response.status,
});
},
);
}
@@ -127,7 +137,7 @@ function createGetVariantAnalysisRequestHandler(
// request, so keep an index of the request and return the appropriate response.
return rest.get(
`${baseUrl}/repositories/:controllerRepoId/code-scanning/codeql/variant-analyses/:variantAnalysisId`,
(_req, res, ctx) => {
() => {
const request = getVariantAnalysisRequests[requestIndex];
if (requestIndex < getVariantAnalysisRequests.length - 1) {
@@ -135,10 +145,9 @@ function createGetVariantAnalysisRequestHandler(
requestIndex++;
}
return res(
ctx.status(request.response.status),
ctx.json(request.response.body),
);
return jsonResponse(request.response.body, {
status: request.response.status,
});
},
);
}
@@ -152,18 +161,17 @@ function createGetVariantAnalysisRepoRequestHandler(
return rest.get(
`${baseUrl}/repositories/:controllerRepoId/code-scanning/codeql/variant-analyses/:variantAnalysisId/repositories/:repoId`,
(req, res, ctx) => {
({ request, params }) => {
const scenarioRequest = getVariantAnalysisRepoRequests.find(
(r) => r.request.repositoryId.toString() === req.params.repoId,
(r) => r.request.repositoryId.toString() === params.repoId,
);
if (!scenarioRequest) {
throw Error(`No scenario request found for ${req.url}`);
throw Error(`No scenario request found for ${request.url}`);
}
return res(
ctx.status(scenarioRequest.response.status),
ctx.json(scenarioRequest.response.body),
);
return jsonResponse(scenarioRequest.response.body, {
status: scenarioRequest.response.status,
});
},
);
}
@@ -177,22 +185,23 @@ function createGetVariantAnalysisRepoResultRequestHandler(
return rest.get(
"https://objects-origin.githubusercontent.com/codeql-query-console/codeql-variant-analysis-repo-tasks/:variantAnalysisId/:repoId/*",
(req, res, ctx) => {
({ request, params }) => {
const scenarioRequest = getVariantAnalysisRepoResultRequests.find(
(r) => r.request.repositoryId.toString() === req.params.repoId,
(r) => r.request.repositoryId.toString() === params.repoId,
);
if (!scenarioRequest) {
throw Error(`No scenario request found for ${req.url}`);
throw Error(`No scenario request found for ${request.url}`);
}
if (scenarioRequest.response.body) {
return res(
ctx.status(scenarioRequest.response.status),
ctx.set("Content-Type", scenarioRequest.response.contentType),
ctx.body(scenarioRequest.response.body),
);
return new Response(scenarioRequest.response.body, {
status: scenarioRequest.response.status,
headers: {
"Content-Type": scenarioRequest.response.contentType,
},
});
} else {
return res(ctx.status(scenarioRequest.response.status));
return new Response(null, { status: scenarioRequest.response.status });
}
},
);
@@ -207,7 +216,7 @@ function createCodeSearchRequestHandler(
// During a code search, there are multiple request to get pages of results. We
// need to return different responses for each request, so keep an index of the
// request and return the appropriate response.
return rest.get(`${baseUrl}/search/code?q=*`, (_req, res, ctx) => {
return rest.get(`${baseUrl}/search/code`, () => {
const request = codeSearchRequests[requestIndex];
if (requestIndex < codeSearchRequests.length - 1) {
@@ -215,10 +224,9 @@ function createCodeSearchRequestHandler(
requestIndex++;
}
return res(
ctx.status(request.response.status),
ctx.json(request.response.body),
);
return jsonResponse(request.response.body, {
status: request.response.status,
});
});
}
@@ -233,7 +241,7 @@ function createAutoModelRequestHandler(
// so keep an index of the request and return the appropriate response.
return rest.post(
`${baseUrl}/repos/github/codeql/code-scanning/codeql/auto-model`,
(_req, res, ctx) => {
() => {
const request = autoModelRequests[requestIndex];
if (requestIndex < autoModelRequests.length - 1) {
@@ -241,10 +249,9 @@ function createAutoModelRequestHandler(
requestIndex++;
}
return res(
ctx.status(request.response.status),
ctx.json(request.response.body),
);
return jsonResponse(request.response.body, {
status: request.response.status,
});
},
);
}

View File

@@ -0,0 +1,10 @@
import * as Octokit from "@octokit/rest";
import { retry } from "@octokit/plugin-retry";
import fetch from "node-fetch";
export const AppOctokit = Octokit.Octokit.defaults({
request: {
fetch,
},
retry,
});

View File

@@ -40,10 +40,7 @@ export const PACKS_BY_QUERY_LANGUAGE = {
],
[QueryLanguage.Go]: ["codeql/go-queries"],
[QueryLanguage.Java]: ["codeql/java-queries"],
[QueryLanguage.Javascript]: [
"codeql/javascript-queries",
"codeql/javascript-experimental-atm-queries",
],
[QueryLanguage.Javascript]: ["codeql/javascript-queries"],
[QueryLanguage.Python]: ["codeql/python-queries"],
[QueryLanguage.Ruby]: ["codeql/ruby-queries"],
};
@@ -62,3 +59,9 @@ export const dbSchemeToLanguage: Record<string, QueryLanguage> = {
export function isQueryLanguage(language: string): language is QueryLanguage {
return Object.values(QueryLanguage).includes(language as QueryLanguage);
}
export function tryGetQueryLanguage(
language: string,
): QueryLanguage | undefined {
return isQueryLanguage(language) ? language : undefined;
}

View File

@@ -54,9 +54,7 @@ export async function sarifParser(
});
} catch (e) {
throw new Error(
`Parsing output of interpretation failed: ${
(e as any).stderr || getErrorMessage(e)
}`,
`Parsing output of interpretation failed: ${getErrorMessage(e)}`,
);
}
}

View File

@@ -0,0 +1,125 @@
import { Readable } from "stream";
import { StringDecoder } from "string_decoder";
/**
* Buffer to hold state used when splitting a text stream into lines.
*/
export class SplitBuffer {
private readonly decoder = new StringDecoder("utf8");
private readonly maxSeparatorLength: number;
private buffer = "";
private searchIndex = 0;
private ended = false;
constructor(private readonly separators: readonly string[]) {
this.maxSeparatorLength = separators
.map((s) => s.length)
.reduce((a, b) => Math.max(a, b), 0);
}
/**
* Append new text data to the buffer.
* @param chunk The chunk of data to append.
*/
public addChunk(chunk: Buffer): void {
this.buffer += this.decoder.write(chunk);
}
/**
* Signal that the end of the input stream has been reached.
*/
public end(): void {
this.buffer += this.decoder.end();
this.ended = true;
}
/**
* A version of startsWith that isn't overriden by a broken version of ms-python.
*
* The definition comes from
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
* which is CC0/public domain
*
* See https://github.com/github/vscode-codeql/issues/802 for more context as to why we need it.
*/
private static startsWith(
s: string,
searchString: string,
position: number,
): boolean {
const pos = position > 0 ? position | 0 : 0;
return s.substring(pos, pos + searchString.length) === searchString;
}
/**
* Extract the next full line from the buffer, if one is available.
* @returns The text of the next available full line (without the separator), or `undefined` if no
* line is available.
*/
public getNextLine(): string | undefined {
// If we haven't received all of the input yet, don't search too close to the end of the buffer,
// or we could match a separator that's split across two chunks. For example, we could see "\r"
// at the end of the buffer and match that, even though we were about to receive a "\n" right
// after it.
const maxSearchIndex = this.ended
? this.buffer.length - 1
: this.buffer.length - this.maxSeparatorLength;
while (this.searchIndex <= maxSearchIndex) {
for (const separator of this.separators) {
if (SplitBuffer.startsWith(this.buffer, separator, this.searchIndex)) {
const line = this.buffer.slice(0, this.searchIndex);
this.buffer = this.buffer.slice(this.searchIndex + separator.length);
this.searchIndex = 0;
return line;
}
}
this.searchIndex++;
}
if (this.ended && this.buffer.length > 0) {
// If we still have some text left in the buffer, return it as the last line.
const line = this.buffer;
this.buffer = "";
this.searchIndex = 0;
return line;
} else {
return undefined;
}
}
}
/**
* Splits a text stream into lines based on a list of valid line separators.
* @param stream The text stream to split. This stream will be fully consumed.
* @param separators The list of strings that act as line separators.
* @returns A sequence of lines (not including separators).
*/
export async function* splitStreamAtSeparators(
stream: Readable,
separators: string[],
): AsyncGenerator<string, void, unknown> {
const buffer = new SplitBuffer(separators);
for await (const chunk of stream) {
buffer.addChunk(chunk);
let line: string | undefined;
do {
line = buffer.getNextLine();
if (line !== undefined) {
yield line;
}
} while (line !== undefined);
}
buffer.end();
let line: string | undefined;
do {
line = buffer.getNextLine();
if (line !== undefined) {
yield line;
}
} while (line !== undefined);
}
/**
* Standard line endings for splitting human-readable text.
*/
export const LINE_ENDINGS = ["\r\n", "\r", "\n"];

View File

@@ -0,0 +1,85 @@
import * as vscode from "vscode";
import { Uri, WebviewViewProvider } from "vscode";
import { WebviewKind, WebviewMessage, getHtmlForWebview } from "./webview-html";
import { Disposable } from "../disposable-object";
import { App } from "../app";
export abstract class AbstractWebviewViewProvider<
ToMessage extends WebviewMessage,
FromMessage extends WebviewMessage,
> implements WebviewViewProvider
{
protected webviewView: vscode.WebviewView | undefined = undefined;
private disposables: Disposable[] = [];
constructor(
protected readonly app: App,
private readonly webviewKind: WebviewKind,
) {}
/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
public resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
) {
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [Uri.file(this.app.extensionPath)],
};
const html = getHtmlForWebview(
this.app,
webviewView.webview,
this.webviewKind,
{
allowInlineStyles: true,
allowWasmEval: false,
},
);
webviewView.webview.html = html;
this.webviewView = webviewView;
webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));
webviewView.onDidDispose(() => this.dispose());
}
protected get isShowingView() {
return this.webviewView?.visible ?? false;
}
protected async postMessage(msg: ToMessage): Promise<void> {
await this.webviewView?.webview.postMessage(msg);
}
protected dispose() {
while (this.disposables.length > 0) {
const disposable = this.disposables.pop()!;
disposable.dispose();
}
this.webviewView = undefined;
}
protected push<T extends Disposable>(obj: T): T {
if (obj !== undefined) {
this.disposables.push(obj);
}
return obj;
}
protected abstract onMessage(msg: FromMessage): Promise<void>;
/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
protected onWebViewLoaded(): void {
// Do nothing by default.
}
}

View File

@@ -1,6 +1,5 @@
import {
WebviewPanel,
ExtensionContext,
window as Window,
ViewColumn,
Uri,
@@ -9,7 +8,8 @@ import {
} from "vscode";
import { join } from "path";
import { DisposableObject, DisposeHandler } from "../disposable-object";
import { App } from "../app";
import { Disposable } from "../disposable-object";
import { tmpDir } from "../../tmp-dir";
import { getHtmlForWebview, WebviewMessage, WebviewKind } from "./webview-html";
@@ -27,16 +27,16 @@ export type WebviewPanelConfig = {
export abstract class AbstractWebview<
ToMessage extends WebviewMessage,
FromMessage extends WebviewMessage,
> extends DisposableObject {
> {
protected panel: WebviewPanel | undefined;
protected panelLoaded = false;
protected panelLoadedCallBacks: Array<() => void> = [];
private panelResolves?: Array<(panel: WebviewPanel) => void>;
constructor(protected readonly ctx: ExtensionContext) {
super();
}
private disposables: Disposable[] = [];
constructor(protected readonly app: App) {}
public async restoreView(panel: WebviewPanel): Promise<void> {
this.panel = panel;
@@ -50,8 +50,6 @@ export abstract class AbstractWebview<
protected async getPanel(): Promise<WebviewPanel> {
if (this.panel === undefined) {
const { ctx } = this;
// This is an async method, so in theory this method can be called concurrently. To ensure that we don't
// create two panels, we use a promise that resolves when the panel is created. This way, if the panel is
// being created, the promise will resolve when it is done.
@@ -81,7 +79,7 @@ export abstract class AbstractWebview<
localResourceRoots: [
...(config.additionalOptions?.localResourceRoots ?? []),
Uri.file(tmpDir.name),
Uri.file(join(ctx.extensionPath, "out")),
Uri.file(join(this.app.extensionPath, "out")),
],
},
);
@@ -99,19 +97,16 @@ export abstract class AbstractWebview<
protected setupPanel(panel: WebviewPanel, config: WebviewPanelConfig): void {
this.push(
panel.onDidDispose(
() => {
this.panel = undefined;
this.panelLoaded = false;
this.onPanelDispose();
},
null,
this.ctx.subscriptions,
),
panel.onDidDispose(() => {
this.panel = undefined;
this.panelLoaded = false;
this.onPanelDispose();
this.disposeAll();
}, null),
);
panel.webview.html = getHtmlForWebview(
this.ctx,
this.app,
panel.webview,
config.view,
{
@@ -123,7 +118,6 @@ export abstract class AbstractWebview<
panel.webview.onDidReceiveMessage(
async (e) => this.onMessage(e),
undefined,
this.ctx.subscriptions,
),
);
}
@@ -157,8 +151,27 @@ export abstract class AbstractWebview<
return panel.webview.postMessage(msg);
}
public dispose(disposeHandler?: DisposeHandler) {
public dispose() {
this.panel?.dispose();
super.dispose(disposeHandler);
this.disposeAll();
}
private disposeAll() {
while (this.disposables.length > 0) {
const disposable = this.disposables.pop()!;
disposable.dispose();
}
}
/**
* Adds `obj` to a list of objects to dispose when the panel is disposed. Objects added by `push` are
* disposed in reverse order of being added.
* @param obj The object to take ownership of.
*/
protected push<T extends Disposable>(obj: T): T {
if (obj !== undefined) {
this.disposables.push(obj);
}
return obj;
}
}

View File

@@ -1,7 +1,7 @@
import * as vscode from "vscode";
import * as Octokit from "@octokit/rest";
import { retry } from "@octokit/plugin-retry";
import { Credentials } from "../authentication";
import { AppOctokit } from "../octokit";
export const GITHUB_AUTH_PROVIDER_ID = "github";
@@ -32,9 +32,8 @@ export class VSCodeCredentials implements Credentials {
const accessToken = await this.getAccessToken();
return new Octokit.Octokit({
return new AppOctokit({
auth: accessToken,
retry,
});
}

View File

@@ -0,0 +1,24 @@
import { CancellationToken, Disposable } from "vscode";
import { DisposableObject } from "../disposable-object";
/**
* A cancellation token that cancels when any of its constituent
* cancellation tokens are cancelled.
*/
export class MultiCancellationToken implements CancellationToken {
private readonly tokens: CancellationToken[];
constructor(...tokens: CancellationToken[]) {
this.tokens = tokens;
}
get isCancellationRequested(): boolean {
return this.tokens.some((t) => t.isCancellationRequested);
}
onCancellationRequested<T>(listener: (e: T) => any): Disposable {
return new DisposableObject(
...this.tokens.map((t) => t.onCancellationRequested(listener)),
);
}
}

View File

@@ -3,13 +3,13 @@ import {
Extension,
ExtensionContext,
ConfigurationChangeEvent,
env,
} from "vscode";
import TelemetryReporter from "vscode-extension-telemetry";
import {
ConfigListener,
CANARY_FEATURES,
ENABLE_TELEMETRY,
GLOBAL_ENABLE_TELEMETRY,
LOG_TELEMETRY,
isIntegrationTestMode,
isCanary,
@@ -59,8 +59,6 @@ export class ExtensionTelemetryListener
extends ConfigListener
implements AppTelemetry
{
static relevantSettings = [ENABLE_TELEMETRY, CANARY_FEATURES];
private reporter?: TelemetryReporter;
private cliVersionStr = NOT_SET_CLI_VERSION;
@@ -72,6 +70,10 @@ export class ExtensionTelemetryListener
private readonly ctx: ExtensionContext,
) {
super();
env.onDidChangeTelemetryEnabled(async () => {
await this.initialize();
});
}
/**
@@ -91,10 +93,7 @@ export class ExtensionTelemetryListener
async handleDidChangeConfiguration(
e: ConfigurationChangeEvent,
): Promise<void> {
if (
e.affectsConfiguration("codeQL.telemetry.enableTelemetry") ||
e.affectsConfiguration("telemetry.enableTelemetry")
) {
if (e.affectsConfiguration(ENABLE_TELEMETRY.qualifiedName)) {
await this.initialize();
}
@@ -102,7 +101,7 @@ export class ExtensionTelemetryListener
// Re-request if codeQL.canary is being set to `true` and telemetry
// is not currently enabled.
if (
e.affectsConfiguration("codeQL.canary") &&
e.affectsConfiguration(CANARY_FEATURES.qualifiedName) &&
CANARY_FEATURES.getValue() &&
!ENABLE_TELEMETRY.getValue()
) {
@@ -212,7 +211,7 @@ export class ExtensionTelemetryListener
properties.stack = error.stack;
}
this.reporter.sendTelemetryEvent("error", properties, {});
this.reporter.sendTelemetryErrorEvent("error", properties, {});
}
/**
@@ -224,7 +223,7 @@ export class ExtensionTelemetryListener
// if global telemetry is disabled, avoid showing the dialog or making any changes
let result = undefined;
if (
GLOBAL_ENABLE_TELEMETRY.getValue() &&
env.isTelemetryEnabled &&
// Avoid showing the dialog if we are in integration test mode.
!isIntegrationTestMode()
) {

View File

@@ -1,6 +1,7 @@
import { ExtensionContext, Uri, Webview } from "vscode";
import { Uri, Webview } from "vscode";
import { randomBytes } from "crypto";
import { EOL } from "os";
import { App } from "../app";
export type WebviewKind =
| "results"
@@ -19,7 +20,7 @@ export interface WebviewMessage {
* Uses a content security policy that only loads the given script.
*/
export function getHtmlForWebview(
ctx: ExtensionContext,
app: App,
webview: Webview,
view: WebviewKind,
{
@@ -33,10 +34,13 @@ export function getHtmlForWebview(
allowWasmEval: false,
},
): string {
const scriptUriOnDisk = Uri.file(ctx.asAbsolutePath("out/webview.js"));
const scriptUriOnDisk = Uri.joinPath(
Uri.file(app.extensionPath),
"out/webview.js",
);
const stylesheetUrisOnDisk = [
Uri.file(ctx.asAbsolutePath("out/webview.css")),
Uri.joinPath(Uri.file(app.extensionPath), "out/webview.css"),
];
// Convert the on-disk URIs into webview URIs.

View File

@@ -1,4 +1,4 @@
import { ExtensionContext, ViewColumn } from "vscode";
import { ViewColumn } from "vscode";
import {
FromCompareViewMessage,
@@ -25,6 +25,7 @@ import {
} from "../common/vscode/abstract-webview";
import { telemetryListener } from "../common/vscode/telemetry";
import { redactableError } from "../common/errors";
import { App } from "../common/app";
interface ComparePair {
from: CompletedLocalQueryInfo;
@@ -38,7 +39,7 @@ export class CompareView extends AbstractWebview<
private comparePair: ComparePair | undefined;
constructor(
ctx: ExtensionContext,
app: App,
private databaseManager: DatabaseManager,
private cliServer: CodeQLCliServer,
private logger: Logger,
@@ -47,7 +48,7 @@ export class CompareView extends AbstractWebview<
item: CompletedLocalQueryInfo,
) => Promise<void>,
) {
super(ctx);
super(app);
}
async showResults(

View File

@@ -72,15 +72,8 @@ export const VSCODE_SAVE_BEFORE_START_SETTING = new Setting(
const ROOT_SETTING = new Setting("codeQL");
// Global configuration
// Telemetry configuration
const TELEMETRY_SETTING = new Setting("telemetry", ROOT_SETTING);
const AST_VIEWER_SETTING = new Setting("astViewer", ROOT_SETTING);
const CONTEXTUAL_QUERIES_SETTINGS = new Setting(
"contextualQueries",
ROOT_SETTING,
);
const GLOBAL_TELEMETRY_SETTING = new Setting("telemetry");
const LOG_INSIGHTS_SETTING = new Setting("logInsights", ROOT_SETTING);
export const LOG_TELEMETRY = new Setting("logTelemetry", TELEMETRY_SETTING);
export const ENABLE_TELEMETRY = new Setting(
@@ -88,11 +81,6 @@ export const ENABLE_TELEMETRY = new Setting(
TELEMETRY_SETTING,
);
export const GLOBAL_ENABLE_TELEMETRY = new Setting(
"enableTelemetry",
GLOBAL_TELEMETRY_SETTING,
);
// Distribution configuration
const DISTRIBUTION_SETTING = new Setting("cli", ROOT_SETTING);
export const CUSTOM_CODEQL_PATH_SETTING = new Setting(
@@ -475,6 +463,7 @@ export function allowCanaryQueryServer() {
return value === undefined ? true : !!value;
}
const LOG_INSIGHTS_SETTING = new Setting("logInsights", ROOT_SETTING);
export const JOIN_ORDER_WARNING_THRESHOLD = new Setting(
"joinOrderWarningThreshold",
LOG_INSIGHTS_SETTING,
@@ -484,6 +473,7 @@ export function joinOrderWarningThreshold(): number {
return JOIN_ORDER_WARNING_THRESHOLD.getValue<number>();
}
const AST_VIEWER_SETTING = new Setting("astViewer", ROOT_SETTING);
/**
* Hidden setting: Avoids caching in the AST viewer if the user is also a canary user.
*/
@@ -492,6 +482,10 @@ export const NO_CACHE_AST_VIEWER = new Setting(
AST_VIEWER_SETTING,
);
const CONTEXTUAL_QUERIES_SETTINGS = new Setting(
"contextualQueries",
ROOT_SETTING,
);
/**
* Hidden setting: Avoids caching in jump to def and find refs contextual queries if the user is also a canary user.
*/
@@ -709,17 +703,35 @@ const MODEL_SETTING = new Setting("model", ROOT_SETTING);
const FLOW_GENERATION = new Setting("flowGeneration", MODEL_SETTING);
const LLM_GENERATION = new Setting("llmGeneration", MODEL_SETTING);
const EXTENSIONS_DIRECTORY = new Setting("extensionsDirectory", MODEL_SETTING);
const SHOW_MULTIPLE_MODELS = new Setting("showMultipleModels", MODEL_SETTING);
export function showFlowGeneration(): boolean {
return !!FLOW_GENERATION.getValue<boolean>();
export interface ModelConfig {
flowGeneration: boolean;
llmGeneration: boolean;
getExtensionsDirectory(languageId: string): string | undefined;
showMultipleModels: boolean;
}
export function showLlmGeneration(): boolean {
return !!LLM_GENERATION.getValue<boolean>();
}
export class ModelConfigListener extends ConfigListener implements ModelConfig {
protected handleDidChangeConfiguration(e: ConfigurationChangeEvent): void {
this.handleDidChangeConfigurationForRelevantSettings([MODEL_SETTING], e);
}
export function getExtensionsDirectory(languageId: string): string | undefined {
return EXTENSIONS_DIRECTORY.getValue<string>({
languageId,
});
public get flowGeneration(): boolean {
return !!FLOW_GENERATION.getValue<boolean>();
}
public get llmGeneration(): boolean {
return !!LLM_GENERATION.getValue<boolean>();
}
public getExtensionsDirectory(languageId: string): string | undefined {
return EXTENSIONS_DIRECTORY.getValue<string>({
languageId,
});
}
public get showMultipleModels(): boolean {
return !!SHOW_MULTIPLE_MODELS.getValue<boolean>();
}
}

View File

@@ -1,12 +1,9 @@
import { retry } from "@octokit/plugin-retry";
import { throttling } from "@octokit/plugin-throttling";
import { Octokit } from "@octokit/rest";
import { Progress, CancellationToken } from "vscode";
import { Credentials } from "../common/authentication";
import {
NotificationLogger,
showAndLogWarningMessage,
} from "../common/logging";
import { BaseLogger } from "../common/logging";
import { AppOctokit } from "../common/octokit";
export async function getCodeSearchRepositories(
query: string,
@@ -16,7 +13,7 @@ export async function getCodeSearchRepositories(
}>,
token: CancellationToken,
credentials: Credentials,
logger: NotificationLogger,
logger: BaseLogger,
): Promise<string[]> {
let nwos: string[] = [];
const octokit = await provideOctokitWithThrottling(credentials, logger);
@@ -47,26 +44,23 @@ export async function getCodeSearchRepositories(
async function provideOctokitWithThrottling(
credentials: Credentials,
logger: NotificationLogger,
logger: BaseLogger,
): Promise<Octokit> {
const MyOctokit = Octokit.plugin(throttling);
const MyOctokit = AppOctokit.plugin(throttling);
const auth = await credentials.getAccessToken();
const octokit = new MyOctokit({
auth,
retry,
throttle: {
onRateLimit: (retryAfter: number, options: any): boolean => {
void showAndLogWarningMessage(
logger,
void logger.log(
`Rate Limit detected for request ${options.method} ${options.url}. Retrying after ${retryAfter} seconds!`,
);
return true;
},
onSecondaryRateLimit: (_retryAfter: number, options: any): void => {
void showAndLogWarningMessage(
logger,
void logger.log(
`Secondary Rate Limit detected for request ${options.method} ${options.url}`,
);
},

View File

@@ -14,7 +14,6 @@ import {
} from "fs-extra";
import { basename, join } from "path";
import * as Octokit from "@octokit/rest";
import { retry } from "@octokit/plugin-retry";
import { DatabaseManager, DatabaseItem } from "./local-databases";
import { tmpDir } from "../tmp-dir";
@@ -32,6 +31,7 @@ import { Credentials } from "../common/authentication";
import { AppCommandManager } from "../common/commands";
import { allowHttp } from "../config";
import { showAndLogInformationMessage } from "../common/logging";
import { AppOctokit } from "../common/octokit";
/**
* Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.
@@ -87,6 +87,7 @@ export async function promptImportInternetDatabase(
* @param cli the CodeQL CLI server
* @param language the language to download. If undefined, the user will be prompted to choose a language.
* @param makeSelected make the new database selected in the databases panel (default: true)
* @param addSourceArchiveFolder whether to add a workspace folder containing the source archive to the workspace
*/
export async function promptImportGithubDatabase(
commandManager: AppCommandManager,
@@ -97,6 +98,7 @@ export async function promptImportGithubDatabase(
cli?: CodeQLCliServer,
language?: string,
makeSelected = true,
addSourceArchiveFolder = true,
): Promise<DatabaseItem | undefined> {
const githubRepo = await askForGitHubRepo(progress);
if (!githubRepo) {
@@ -112,6 +114,7 @@ export async function promptImportGithubDatabase(
cli,
language,
makeSelected,
addSourceArchiveFolder,
);
if (databaseItem) {
@@ -163,6 +166,7 @@ export async function askForGitHubRepo(
* @param cli the CodeQL CLI server
* @param language the language to download. If undefined, the user will be prompted to choose a language.
* @param makeSelected make the new database selected in the databases panel (default: true)
* @param addSourceArchiveFolder whether to add a workspace folder containing the source archive to the workspace
**/
export async function downloadGitHubDatabase(
githubRepo: string,
@@ -173,6 +177,7 @@ export async function downloadGitHubDatabase(
cli?: CodeQLCliServer,
language?: string,
makeSelected = true,
addSourceArchiveFolder = true,
): Promise<DatabaseItem | undefined> {
const nwo = getNwoFromGitHubUrl(githubRepo) || githubRepo;
if (!isValidGitHubNwo(nwo)) {
@@ -181,7 +186,7 @@ export async function downloadGitHubDatabase(
const octokit = credentials
? await credentials.getOctokit()
: new Octokit.Octokit({ retry });
: new AppOctokit();
const result = await convertGithubNwoToDatabaseUrl(
nwo,
@@ -218,6 +223,7 @@ export async function downloadGitHubDatabase(
progress,
cli,
makeSelected,
addSourceArchiveFolder,
);
}
@@ -277,6 +283,7 @@ export async function importArchiveDatabase(
* @param nameOverride a name for the database that overrides the default
* @param progress callback to send progress messages to
* @param makeSelected make the new database selected in the databases panel (default: true)
* @param addSourceArchiveFolder whether to add a workspace folder containing the source archive to the workspace
*/
async function databaseArchiveFetcher(
databaseUrl: string,
@@ -287,6 +294,7 @@ async function databaseArchiveFetcher(
progress: ProgressCallback,
cli?: CodeQLCliServer,
makeSelected = true,
addSourceArchiveFolder = true,
): Promise<DatabaseItem> {
progress({
message: "Getting database",
@@ -329,6 +337,9 @@ async function databaseArchiveFetcher(
Uri.file(dbPath),
makeSelected,
nameOverride,
{
addSourceArchiveFolder,
},
);
return item;
} else {

View File

@@ -51,6 +51,8 @@ import {
createMultiSelectionCommand,
createSingleSelectionCommand,
} from "../common/vscode/selection-commands";
import { QueryLanguage, tryGetQueryLanguage } from "../common/query-language";
import { LanguageContextStore } from "../language-context-store";
enum SortOrder {
NameAsc = "NameAsc",
@@ -73,7 +75,10 @@ class DatabaseTreeDataProvider
);
private currentDatabaseItem: DatabaseItem | undefined;
constructor(private databaseManager: DatabaseManager) {
constructor(
private databaseManager: DatabaseManager,
private languageContext: LanguageContextStore,
) {
super();
this.currentDatabaseItem = databaseManager.currentDatabaseItem;
@@ -88,6 +93,11 @@ class DatabaseTreeDataProvider
this.handleDidChangeCurrentDatabaseItem.bind(this),
),
);
this.push(
this.languageContext.onLanguageContextChanged(async () => {
this._onDidChangeTreeData.fire(undefined);
}),
);
}
public get onDidChangeTreeData(): Event<DatabaseItem | undefined> {
@@ -131,7 +141,15 @@ class DatabaseTreeDataProvider
public getChildren(element?: DatabaseItem): ProviderResult<DatabaseItem[]> {
if (element === undefined) {
return this.databaseManager.databaseItems.slice(0).sort((db1, db2) => {
// Filter items by language
const displayItems = this.databaseManager.databaseItems.filter((item) => {
return this.languageContext.shouldInclude(
tryGetQueryLanguage(item.language),
);
});
// Sort items
return displayItems.slice(0).sort((db1, db2) => {
switch (this.sortOrder) {
case SortOrder.NameAsc:
return db1.name.localeCompare(db2.name, env.language);
@@ -200,6 +218,7 @@ export class DatabaseUI extends DisposableObject {
public constructor(
private app: App,
private databaseManager: DatabaseManager,
private languageContext: LanguageContextStore,
private readonly queryServer: QueryRunner | undefined,
private readonly storagePath: string,
readonly extensionPath: string,
@@ -207,7 +226,7 @@ export class DatabaseUI extends DisposableObject {
super();
this.treeDataProvider = this.push(
new DatabaseTreeDataProvider(databaseManager),
new DatabaseTreeDataProvider(databaseManager, languageContext),
);
this.push(
window.createTreeView("codeQLDatabases", {
@@ -245,6 +264,60 @@ export class DatabaseUI extends DisposableObject {
this.handleMakeCurrentDatabase.bind(this),
"codeQLDatabases.sortByName": this.handleSortByName.bind(this),
"codeQLDatabases.sortByDateAdded": this.handleSortByDateAdded.bind(this),
"codeQLDatabases.displayAllLanguages":
this.handleClearLanguageFilter.bind(this),
"codeQLDatabases.displayCpp": this.handleChangeLanguageFilter.bind(
this,
QueryLanguage.Cpp,
),
"codeQLDatabases.displayCsharp": this.handleChangeLanguageFilter.bind(
this,
QueryLanguage.CSharp,
),
"codeQLDatabases.displayGo": this.handleChangeLanguageFilter.bind(
this,
QueryLanguage.Go,
),
"codeQLDatabases.displayJava": this.handleChangeLanguageFilter.bind(
this,
QueryLanguage.Java,
),
"codeQLDatabases.displayJavascript": this.handleChangeLanguageFilter.bind(
this,
QueryLanguage.Javascript,
),
"codeQLDatabases.displayPython": this.handleChangeLanguageFilter.bind(
this,
QueryLanguage.Python,
),
"codeQLDatabases.displayRuby": this.handleChangeLanguageFilter.bind(
this,
QueryLanguage.Ruby,
),
"codeQLDatabases.displaySwift": this.handleChangeLanguageFilter.bind(
this,
QueryLanguage.Swift,
),
"codeQLDatabases.displayAllLanguagesSelected":
this.handleClearLanguageFilter.bind(this),
"codeQLDatabases.displayCppSelected":
this.handleChangeLanguageFilter.bind(this, QueryLanguage.Cpp),
"codeQLDatabases.displayCsharpSelected":
this.handleChangeLanguageFilter.bind(this, QueryLanguage.CSharp),
"codeQLDatabases.displayGoSelected": this.handleChangeLanguageFilter.bind(
this,
QueryLanguage.Go,
),
"codeQLDatabases.displayJavaSelected":
this.handleChangeLanguageFilter.bind(this, QueryLanguage.Java),
"codeQLDatabases.displayJavascriptSelected":
this.handleChangeLanguageFilter.bind(this, QueryLanguage.Javascript),
"codeQLDatabases.displayPythonSelected":
this.handleChangeLanguageFilter.bind(this, QueryLanguage.Python),
"codeQLDatabases.displayRubySelected":
this.handleChangeLanguageFilter.bind(this, QueryLanguage.Ruby),
"codeQLDatabases.displaySwiftSelected":
this.handleChangeLanguageFilter.bind(this, QueryLanguage.Swift),
"codeQLDatabases.removeDatabase": createMultiSelectionCommand(
this.handleRemoveDatabase.bind(this),
),
@@ -329,13 +402,14 @@ export class DatabaseUI extends DisposableObject {
if (databaseItem === undefined) {
const makeSelected = true;
const nameOverride = "CodeQL Tutorial Database";
const isTutorialDatabase = true;
await this.databaseManager.openDatabase(
uri,
makeSelected,
nameOverride,
isTutorialDatabase,
{
isTutorialDatabase: true,
},
);
}
await this.handleTourDependencies();
@@ -534,6 +608,14 @@ export class DatabaseUI extends DisposableObject {
}
}
private async handleClearLanguageFilter() {
await this.languageContext.clearLanguageContext();
}
private async handleChangeLanguageFilter(languageFilter: QueryLanguage) {
await this.languageContext.setLanguageContext(languageFilter);
}
private async handleUpgradeCurrentDatabase(): Promise<void> {
return withProgress(
async (progress, token) => {

View File

@@ -167,6 +167,15 @@ export class DatabaseItemImpl implements DatabaseItem {
return encodeArchiveBasePath(sourceArchive.fsPath);
}
/**
* Returns true if the database's source archive is in the workspace.
*/
public hasSourceArchiveInExplorer(): boolean {
return (vscode.workspace.workspaceFolders || []).some((folder) =>
this.belongsToSourceArchiveExplorerUri(folder.uri),
);
}
public verifyZippedSources(): string | undefined {
const sourceArchive = this.sourceArchive;
if (sourceArchive === undefined) {

View File

@@ -56,6 +56,11 @@ export interface DatabaseItem {
*/
getSourceArchiveExplorerUri(): vscode.Uri;
/**
* Returns true if the database's source archive is in the workspace.
*/
hasSourceArchiveInExplorer(): boolean;
/**
* Holds if `uri` belongs to this database's source archive.
*/

View File

@@ -71,6 +71,14 @@ function eventFired<T>(
});
}
type OpenDatabaseOptions = {
isTutorialDatabase?: boolean;
/**
* Whether to add a workspace folder containing the source archive to the workspace. Default is true.
*/
addSourceArchiveFolder?: boolean;
};
export class DatabaseManager extends DisposableObject {
private readonly _onDidChangeDatabaseItem = this.push(
new vscode.EventEmitter<DatabaseChangedEvent>(),
@@ -107,7 +115,10 @@ export class DatabaseManager extends DisposableObject {
uri: vscode.Uri,
makeSelected = true,
displayName?: string,
isTutorialDatabase?: boolean,
{
isTutorialDatabase = false,
addSourceArchiveFolder = true,
}: OpenDatabaseOptions = {},
): Promise<DatabaseItem> {
const databaseItem = await this.createDatabaseItem(uri, displayName);
@@ -115,6 +126,7 @@ export class DatabaseManager extends DisposableObject {
databaseItem,
makeSelected,
isTutorialDatabase,
addSourceArchiveFolder,
);
}
@@ -128,6 +140,7 @@ export class DatabaseManager extends DisposableObject {
databaseItem: DatabaseItemImpl,
makeSelected: boolean,
isTutorialDatabase?: boolean,
addSourceArchiveFolder = true,
): Promise<DatabaseItem> {
const existingItem = this.findDatabaseItem(databaseItem.databaseUri);
if (existingItem !== undefined) {
@@ -141,7 +154,9 @@ export class DatabaseManager extends DisposableObject {
if (makeSelected) {
await this.setCurrentDatabaseItem(databaseItem);
}
await this.addDatabaseSourceArchiveFolder(databaseItem);
if (addSourceArchiveFolder) {
await this.addDatabaseSourceArchiveFolder(databaseItem);
}
if (isCodespacesTemplate() && !isTutorialDatabase) {
await this.createSkeletonPacks(databaseItem);

View File

@@ -409,7 +409,7 @@ export class DbPanel extends DisposableObject {
return;
}
void window.withProgress(
await window.withProgress(
{
location: ProgressLocation.Notification,
title: "Searching for repositories... This might take a while",

View File

@@ -22,6 +22,7 @@ export interface QLDebugArgs {
extensionPacks?: string[] | string;
quickEval?: boolean;
noDebug?: boolean;
additionalRunQueryArgs?: Record<string, any>;
}
/**
@@ -120,6 +121,7 @@ export class QLDebugConfigurationProvider
extensionPacks,
quickEvalContext,
noDebug: qlConfiguration.noDebug ?? false,
additionalRunQueryArgs: qlConfiguration.additionalRunQueryArgs ?? {},
};
return resultConfiguration;

View File

@@ -70,6 +70,8 @@ export interface LaunchConfig {
quickEvalContext: QuickEvalContext | undefined;
/** Run the query without debugging it. */
noDebug: boolean;
/** Undocumented: Additional arguments to be passed to the `runQuery` API on the query server. */
additionalRunQueryArgs: Record<string, any>;
}
export interface LaunchRequest extends Request, DebugProtocol.LaunchRequest {

View File

@@ -161,6 +161,7 @@ class RunningQuery extends DisposableObject {
true,
config.additionalPacks,
config.extensionPacks,
config.additionalRunQueryArgs,
queryStorageDir,
undefined,
undefined,

View File

@@ -135,6 +135,7 @@ import { TestManagerBase } from "./query-testing/test-manager-base";
import { NewQueryRunner, QueryRunner, QueryServerClient } from "./query-server";
import { QueriesModule } from "./queries-panel/queries-module";
import { OpenReferencedFileCodeLensProvider } from "./local-queries/open-referenced-file-code-lens-provider";
import { LanguageContextStore } from "./language-context-store";
/**
* extension.ts
@@ -299,12 +300,12 @@ const shouldUpdateOnNextActivationKey = "shouldUpdateOnNextActivation";
const codeQlVersionRange = DEFAULT_DISTRIBUTION_VERSION_RANGE;
// This is the minimum version of vscode that we _want_ to support. We want to update the language server library, but that
// requires 1.67 or later. If we change the minimum version in the package.json, then anyone on an older version of vscode
// This is the minimum version of vscode that we _want_ to support. We want to update to Node 18, but that
// requires 1.82 or later. If we change the minimum version in the package.json, then anyone on an older version of vscode will
// silently be unable to upgrade. So, the solution is to first bump the minimum version here and release. Then
// bump the version in the package.json and release again. This way, anyone on an older version of vscode will get a warning
// before silently being refused to upgrade.
const MIN_VERSION = "1.67.0";
const MIN_VERSION = "1.82.0";
/**
* Returns the CodeQLExtensionInterface, or an empty object if the interface is not
@@ -396,10 +397,7 @@ export async function activate(
),
);
const variantAnalysisViewSerializer = new VariantAnalysisViewSerializer(
ctx,
app,
);
const variantAnalysisViewSerializer = new VariantAnalysisViewSerializer(app);
Window.registerWebviewPanelSerializer(
VariantAnalysisView.viewType,
variantAnalysisViewSerializer,
@@ -777,17 +775,22 @@ async function activateWithInstalledDistribution(
void dbm.loadPersistedState();
ctx.subscriptions.push(dbm);
void extLogger.log("Initializing language context.");
const languageContext = new LanguageContextStore(app);
void extLogger.log("Initializing database panel.");
const databaseUI = new DatabaseUI(
app,
dbm,
languageContext,
qs,
getContextStoragePath(ctx),
ctx.extensionPath,
);
ctx.subscriptions.push(databaseUI);
QueriesModule.initialize(app, cliServer);
QueriesModule.initialize(app, languageContext, cliServer);
void extLogger.log("Initializing evaluator log viewer.");
const evalLogViewer = new EvalLogViewer();
@@ -813,7 +816,7 @@ async function activateWithInstalledDistribution(
void extLogger.log("Initializing results panel interface.");
const localQueryResultsView = new ResultsView(
ctx,
app,
dbm,
cliServer,
queryServerLogger,
@@ -836,7 +839,6 @@ async function activateWithInstalledDistribution(
);
const variantAnalysisManager = new VariantAnalysisManager(
ctx,
app,
cliServer,
variantAnalysisStorageDir,
@@ -869,6 +871,7 @@ async function activateWithInstalledDistribution(
ctx,
queryHistoryConfigurationListener,
labelProvider,
languageContext,
async (
from: CompletedLocalQueryInfo,
to: CompletedLocalQueryInfo,
@@ -888,7 +891,7 @@ async function activateWithInstalledDistribution(
void extLogger.log("Initializing compare view.");
const compareView = new CompareView(
ctx,
app,
dbm,
cliServer,
queryServerLogger,
@@ -935,7 +938,6 @@ async function activateWithInstalledDistribution(
ctx.subscriptions.push(debuggerUI);
const modelEditorModule = await ModelEditorModule.initialize(
ctx,
app,
dbm,
cliServer,
@@ -1162,13 +1164,16 @@ function addUnhandledRejectionListener() {
const message = redactableError(
asError(error),
)`Unhandled error: ${getErrorMessage(error)}`;
const stack = getErrorStack(error);
const fullMessage = stack
? `Unhandled error: ${stack}`
: message.fullMessage;
// Add a catch so that showAndLogExceptionWithTelemetry fails, we avoid
// triggering "unhandledRejection" and avoid an infinite loop
showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
message,
).catch((telemetryError: unknown) => {
showAndLogExceptionWithTelemetry(extLogger, telemetryListener, message, {
fullMessage,
}).catch((telemetryError: unknown) => {
void extLogger.log(
`Failed to send error telemetry: ${getErrorMessage(telemetryError)}`,
);

View File

@@ -0,0 +1,49 @@
import { App } from "./common/app";
import { DisposableObject } from "./common/disposable-object";
import { AppEvent, AppEventEmitter } from "./common/events";
import { QueryLanguage } from "./common/query-language";
type LanguageFilter = QueryLanguage | "All";
export class LanguageContextStore extends DisposableObject {
public readonly onLanguageContextChanged: AppEvent<void>;
private readonly onLanguageContextChangedEmitter: AppEventEmitter<void>;
private languageFilter: LanguageFilter;
constructor(private readonly app: App) {
super();
// State initialization
this.languageFilter = "All";
// Set up event emitters
this.onLanguageContextChangedEmitter = this.push(
app.createEventEmitter<void>(),
);
this.onLanguageContextChanged = this.onLanguageContextChangedEmitter.event;
}
public async clearLanguageContext() {
this.languageFilter = "All";
this.onLanguageContextChangedEmitter.fire();
await this.app.commands.execute(
"setContext",
"codeQLDatabases.languageFilter",
"",
);
}
public async setLanguageContext(language: QueryLanguage) {
this.languageFilter = language;
this.onLanguageContextChangedEmitter.fire();
await this.app.commands.execute(
"setContext",
"codeQLDatabases.languageFilter",
language,
);
}
public shouldInclude(language: QueryLanguage | undefined): boolean {
return this.languageFilter === "All" || this.languageFilter === language;
}
}

View File

@@ -44,6 +44,7 @@ export async function runContextualQuery(
false,
getOnDiskWorkspaceFolders(),
undefined,
{},
queryStorageDir,
undefined,
templates,

View File

@@ -36,6 +36,7 @@ import {
import { CoreCompletedQuery, QueryRunner } from "../../query-server";
import { AstBuilder } from "../ast-viewer/ast-builder";
import { qlpackOfDatabase } from "../../local-queries";
import { MultiCancellationToken } from "../../common/vscode/multi-cancellation-token";
/**
* Runs templated CodeQL queries to find definitions in
@@ -43,6 +44,7 @@ import { qlpackOfDatabase } from "../../local-queries";
* generalize this to other custom queries, e.g. showing dataflow to
* or from a selected identifier.
*/
export class TemplateQueryDefinitionProvider implements DefinitionProvider {
private cache: CachedOperation<LocationLink[]>;
@@ -60,11 +62,11 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider {
async provideDefinition(
document: TextDocument,
position: Position,
_token: CancellationToken,
token: CancellationToken,
): Promise<LocationLink[]> {
const fileLinks = this.shouldUseCache()
? await this.cache.get(document.uri.toString())
: await this.getDefinitions(document.uri.toString());
? await this.cache.get(document.uri.toString(), token)
: await this.getDefinitions(document.uri.toString(), token);
const locLinks: LocationLink[] = [];
for (const link of fileLinks) {
@@ -79,9 +81,13 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider {
return !(isCanary() && NO_CACHE_CONTEXTUAL_QUERIES.getValue<boolean>());
}
private async getDefinitions(uriString: string): Promise<LocationLink[]> {
private async getDefinitions(
uriString: string,
token: CancellationToken,
): Promise<LocationLink[]> {
return withProgress(
async (progress, token) => {
async (progress, tokenInner) => {
const multiToken = new MultiCancellationToken(token, tokenInner);
return getLocationsForUriString(
this.cli,
this.qs,
@@ -90,7 +96,7 @@ export class TemplateQueryDefinitionProvider implements DefinitionProvider {
KeyType.DefinitionQuery,
this.queryStorageDir,
progress,
token,
multiToken,
(src, _dest) => src === uriString,
);
},
@@ -126,11 +132,11 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
document: TextDocument,
position: Position,
_context: ReferenceContext,
_token: CancellationToken,
token: CancellationToken,
): Promise<Location[]> {
const fileLinks = this.shouldUseCache()
? await this.cache.get(document.uri.toString())
: await this.getReferences(document.uri.toString());
? await this.cache.get(document.uri.toString(), token)
: await this.getReferences(document.uri.toString(), token);
const locLinks: Location[] = [];
for (const link of fileLinks) {
@@ -148,9 +154,14 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
return !(isCanary() && NO_CACHE_CONTEXTUAL_QUERIES.getValue<boolean>());
}
private async getReferences(uriString: string): Promise<FullLocationLink[]> {
private async getReferences(
uriString: string,
token: CancellationToken,
): Promise<FullLocationLink[]> {
return withProgress(
async (progress, token) => {
async (progress, tokenInner) => {
const multiToken = new MultiCancellationToken(token, tokenInner);
return getLocationsForUriString(
this.cli,
this.qs,
@@ -159,7 +170,7 @@ export class TemplateQueryReferenceProvider implements ReferenceProvider {
KeyType.DefinitionQuery,
this.queryStorageDir,
progress,
token,
multiToken,
(src, _dest) => src === uriString,
);
},

View File

@@ -49,6 +49,7 @@ import { LocalQueryRun } from "./local-query-run";
import { createMultiSelectionCommand } from "../common/vscode/selection-commands";
import { findLanguage } from "../codeql-cli/query-language";
import type { QueryTreeViewItem } from "../queries-panel/query-tree-view-item";
import { tryGetQueryLanguage } from "../common/query-language";
interface DatabaseQuickPickItem extends QuickPickItem {
databaseItem: DatabaseItem;
@@ -364,6 +365,7 @@ export class LocalQueries extends DisposableObject {
const initialInfo = await createInitialQueryInfo(selectedQuery, {
databaseUri: dbItem.databaseUri.toString(),
name: dbItem.name,
language: tryGetQueryLanguage(dbItem.language),
});
// When cancellation is requested from the query history view, we just stop the debug session.
@@ -454,6 +456,7 @@ export class LocalQueries extends DisposableObject {
true,
additionalPacks,
extensionPacks,
{},
this.queryStorageDir,
undefined,
templates,

View File

@@ -13,6 +13,7 @@ import { redactableError } from "../common/errors";
import { showAndLogExceptionWithTelemetry } from "../common/logging";
import { extLogger } from "../common/logging/vscode";
import { telemetryListener } from "../common/vscode/telemetry";
import { SuiteInstruction } from "../packaging/suite-instruction";
export async function qlpackOfDatabase(
cli: Pick<CodeQLCliServer, "resolveQlpacks">,
@@ -38,24 +39,26 @@ export interface QueryConstraints {
* @param cli The CLI instance to use.
* @param qlpacks The list of packs to search.
* @param constraints Constraints on the queries to search for.
* @param additionalPacks Additional pack paths to search.
* @returns The found queries from the first pack in which any matching queries were found.
*/
async function resolveQueriesFromPacks(
export async function resolveQueriesFromPacks(
cli: CodeQLCliServer,
qlpacks: string[],
constraints: QueryConstraints,
additionalPacks: string[] = [],
): Promise<string[]> {
const suiteFile = (
await file({
postfix: ".qls",
})
).path;
const suiteYaml = [];
const suiteYaml: SuiteInstruction[] = [];
for (const qlpack of qlpacks) {
suiteYaml.push({
from: qlpack,
queries: ".",
include: constraints,
include: constraints as Record<string, string[]>,
});
}
await writeFile(
@@ -66,10 +69,10 @@ async function resolveQueriesFromPacks(
"utf8",
);
return await cli.resolveQueriesInSuite(
suiteFile,
getOnDiskWorkspaceFolders(),
);
return await cli.resolveQueriesInSuite(suiteFile, [
...getOnDiskWorkspaceFolders(),
...additionalPacks,
]);
}
export async function resolveQueriesByLanguagePack(
@@ -96,6 +99,7 @@ export async function resolveQueriesByLanguagePack(
* @param packsToSearch The list of packs to search.
* @param name The name of the query to use in error messages.
* @param constraints Constraints on the queries to search for.
* @param additionalPacks Additional pack paths to search.
* @returns The found queries from the first pack in which any matching queries were found.
*/
export async function resolveQueries(
@@ -103,11 +107,13 @@ export async function resolveQueries(
packsToSearch: string[],
name: string,
constraints: QueryConstraints,
additionalPacks: string[] = [],
): Promise<string[]> {
const queries = await resolveQueriesFromPacks(
cli,
packsToSearch,
constraints,
additionalPacks,
);
if (queries.length > 0) {
return queries;

View File

@@ -74,6 +74,8 @@ import { HistoryItemLabelProvider } from "../query-history/history-item-label-pr
import { telemetryListener } from "../common/vscode/telemetry";
import { redactableError } from "../common/errors";
import { ResultsViewCommands } from "../common/commands";
import { App } from "../common/app";
import { Disposable } from "../common/disposable-object";
/**
* results-view.ts
@@ -156,6 +158,12 @@ function numInterpretedPages(
return Math.ceil(n / pageSize);
}
/**
* The results view is used for displaying the results of a local query. It is a singleton; only 1 results view exists
* in the extension. It is created when the extension is activated and disposed of when the extension is deactivated.
* There can be multiple panels linked to this view over the lifetime of the extension, but there is only ever 1 panel
* active at a time.
*/
export class ResultsView extends AbstractWebview<
IntoResultsViewMsg,
FromResultsViewMsg
@@ -167,22 +175,27 @@ export class ResultsView extends AbstractWebview<
"codeql-query-results",
);
// Event listeners that should be disposed of when the view is disposed.
private disposableEventListeners: Disposable[] = [];
constructor(
public ctx: vscode.ExtensionContext,
app: App,
private databaseManager: DatabaseManager,
public cliServer: CodeQLCliServer,
public logger: Logger,
private labelProvider: HistoryItemLabelProvider,
) {
super(ctx);
this.push(this._diagnosticCollection);
this.push(
super(app);
// We can't use this.push for these two event listeners because they need to be disposed of when the view is
// disposed, not when the panel is disposed. The results view is a singleton, so we shouldn't be calling this.push.
this.disposableEventListeners.push(
vscode.window.onDidChangeTextEditorSelection(
this.handleSelectionChange.bind(this),
),
);
this.push(
this.disposableEventListeners.push(
this.databaseManager.onDidChangeDatabaseItem(({ kind }) => {
if (kind === DatabaseEventKind.Remove) {
this._diagnosticCollection.clear();
@@ -980,4 +993,12 @@ export class ResultsView extends AbstractWebview<
editor.setDecorations(shownLocationLineDecoration, []);
}
}
dispose() {
super.dispose();
this._diagnosticCollection.dispose();
this.disposableEventListeners.forEach((d) => d.dispose());
this.disposableEventListeners = [];
}
}

View File

@@ -41,6 +41,7 @@ export async function runQuery({
false,
additionalPacks,
extensionPacks,
{},
queryStorageDir,
undefined,
undefined,

View File

@@ -1,4 +1,5 @@
import { writeFile, promises } from "fs-extra";
import { createReadStream, writeFile } from "fs-extra";
import { LINE_ENDINGS, splitStreamAtSeparators } from "../common/split-stream";
/**
* Location information for a single pipeline invocation in the RA.
@@ -64,59 +65,64 @@ export async function generateSummarySymbolsFile(
async function generateSummarySymbols(
summaryPath: string,
): Promise<SummarySymbols> {
const summary = await promises.readFile(summaryPath, {
const stream = createReadStream(summaryPath, {
encoding: "utf-8",
});
const symbols: SummarySymbols = {
predicates: {},
};
try {
const lines = splitStreamAtSeparators(stream, LINE_ENDINGS);
const lines = summary.split(/\r?\n/);
let lineNumber = 0;
while (lineNumber < lines.length) {
const startLineNumber = lineNumber;
lineNumber++;
const startLine = lines[startLineNumber];
const nonRecursiveMatch = startLine.match(NON_RECURSIVE_TUPLE_COUNT_REGEXP);
let predicateName: string | undefined = undefined;
const symbols: SummarySymbols = {
predicates: {},
};
let lineNumber = 0;
let raStartLine = 0;
let iteration = 0;
if (nonRecursiveMatch) {
predicateName = nonRecursiveMatch.groups!.predicateName;
} else {
const recursiveMatch = startLine.match(RECURSIVE_TUPLE_COUNT_REGEXP);
if (recursiveMatch?.groups) {
predicateName = recursiveMatch.groups.predicateName;
iteration = parseInt(recursiveMatch.groups.iteration);
}
}
if (predicateName !== undefined) {
const raStartLine = lineNumber;
let raEndLine: number | undefined = undefined;
while (lineNumber < lines.length && raEndLine === undefined) {
const raLine = lines[lineNumber];
const returnMatch = raLine.match(RETURN_REGEXP);
let predicateName: string | undefined = undefined;
let startLine = 0;
for await (const line of lines) {
if (predicateName === undefined) {
// Looking for the start of the predicate.
const nonRecursiveMatch = line.match(NON_RECURSIVE_TUPLE_COUNT_REGEXP);
if (nonRecursiveMatch) {
iteration = 0;
predicateName = nonRecursiveMatch.groups!.predicateName;
} else {
const recursiveMatch = line.match(RECURSIVE_TUPLE_COUNT_REGEXP);
if (recursiveMatch?.groups) {
predicateName = recursiveMatch.groups.predicateName;
iteration = parseInt(recursiveMatch.groups.iteration);
}
}
if (predicateName !== undefined) {
startLine = lineNumber;
raStartLine = lineNumber + 1;
}
} else {
const returnMatch = line.match(RETURN_REGEXP);
if (returnMatch) {
raEndLine = lineNumber;
}
lineNumber++;
}
if (raEndLine !== undefined) {
let symbol = symbols.predicates[predicateName];
if (symbol === undefined) {
symbol = {
iterations: {},
let symbol = symbols.predicates[predicateName];
if (symbol === undefined) {
symbol = {
iterations: {},
};
symbols.predicates[predicateName] = symbol;
}
symbol.iterations[iteration] = {
startLine,
raStartLine,
raEndLine: lineNumber,
};
symbols.predicates[predicateName] = symbol;
}
symbol.iterations[iteration] = {
startLine: lineNumber,
raStartLine,
raEndLine,
};
}
}
}
return symbols;
predicateName = undefined;
}
}
lineNumber++;
}
return symbols;
} finally {
stream.close();
}
}

View File

@@ -7,7 +7,6 @@ import { Mode } from "./shared/mode";
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
import { interpretResultsSarif } from "../query-results";
import { join } from "path";
import { assertNever } from "../common/helpers-pure";
import { dir } from "tmp-promise";
import { writeFile, outputFile } from "fs-extra";
import { dump as dumpYaml } from "js-yaml";
@@ -16,17 +15,7 @@ import { runQuery } from "../local-queries/run-query";
import { QueryMetadata } from "../common/interface-types";
import { CancellationTokenSource } from "vscode";
import { resolveQueries } from "../local-queries";
function modeTag(mode: Mode): string {
switch (mode) {
case Mode.Application:
return "application-mode";
case Mode.Framework:
return "framework-mode";
default:
assertNever(mode);
}
}
import { modeTag } from "./mode-tag";
type AutoModelQueriesOptions = {
mode: Mode;
@@ -63,10 +52,8 @@ export async function runAutoModelQueries({
);
// Generate a pack containing the candidate filters
const filterPackDir = await generateCandidateFilterPack(
databaseItem.language,
candidateMethods,
);
const { packDir: filterPackDir, cleanup: cleanupFilterPack } =
await generateCandidateFilterPack(databaseItem.language, candidateMethods);
const additionalPacks = [...getOnDiskWorkspaceFolders(), filterPackDir];
const extensionPacks = Object.keys(
@@ -85,6 +72,8 @@ export async function runAutoModelQueries({
token: cancellationTokenSource.token,
});
await cleanupFilterPack();
if (!completedQuery) {
return undefined;
}
@@ -155,6 +144,11 @@ async function resolveAutomodelQuery(
return queries[0];
}
type CandidateFilterPackResult = {
packDir: string;
cleanup: () => Promise<void>;
};
/**
* generateCandidateFilterPack will create a temporary extension pack.
* This pack will contain a filter that will restrict the automodel queries
@@ -167,9 +161,9 @@ async function resolveAutomodelQuery(
export async function generateCandidateFilterPack(
language: string,
candidateMethods: MethodSignature[],
): Promise<string> {
): Promise<CandidateFilterPackResult> {
// Pack resides in a temporary directory, to not pollute the workspace.
const packDir = (await dir({ unsafeCleanup: true })).path;
const { path: packDir, cleanup } = await dir({ unsafeCleanup: true });
const syntheticConfigPack = {
name: "codeql/automodel-filter",
@@ -208,7 +202,10 @@ export async function generateCandidateFilterPack(
const filterFile = join(packDir, "filter.yml");
await writeFile(filterFile, dumpYaml(filter), "utf8");
return packDir;
return {
packDir,
cleanup,
};
}
async function interpretAutomodelResults(

View File

@@ -14,13 +14,13 @@ import { groupMethods, sortGroupNames, sortMethods } from "./shared/sorting";
* the order in the UI.
* @param mode Whether it is application or framework mode.
* @param methods all methods.
* @param modeledMethods the currently modeled methods.
* @param modeledMethodsBySignature the currently modeled methods.
* @returns list of modeled methods that are candidates for modeling.
*/
export function getCandidates(
mode: Mode,
methods: Method[],
modeledMethods: Record<string, ModeledMethod>,
modeledMethodsBySignature: Record<string, ModeledMethod[]>,
): MethodSignature[] {
// Sort the same way as the UI so we send the first ones listed in the UI first
const grouped = groupMethods(methods, mode);
@@ -32,12 +32,11 @@ export function getCandidates(
const candidates: MethodSignature[] = [];
for (const method of sortedMethods) {
const modeledMethod: ModeledMethod = modeledMethods[method.signature] ?? {
type: "none",
};
const modeledMethods: ModeledMethod[] =
modeledMethodsBySignature[method.signature] ?? [];
// Anything that is modeled is not a candidate
if (modeledMethod.type !== "none") {
if (modeledMethods.some((m) => m.type !== "none")) {
continue;
}

View File

@@ -42,7 +42,7 @@ export class AutoModeler {
inProgressMethods: string[],
) => Promise<void>,
private readonly addModeledMethods: (
modeledMethods: Record<string, ModeledMethod>,
modeledMethods: Record<string, ModeledMethod[]>,
) => Promise<void>,
) {
this.jobs = new Map<string, CancellationTokenSource>();
@@ -59,7 +59,7 @@ export class AutoModeler {
public async startModeling(
packageName: string,
methods: Method[],
modeledMethods: Record<string, ModeledMethod>,
modeledMethods: Record<string, ModeledMethod[]>,
mode: Mode,
): Promise<void> {
if (this.jobs.has(packageName)) {
@@ -106,7 +106,7 @@ export class AutoModeler {
private async modelPackage(
packageName: string,
methods: Method[],
modeledMethods: Record<string, ModeledMethod>,
modeledMethods: Record<string, ModeledMethod[]>,
mode: Mode,
cancellationTokenSource: CancellationTokenSource,
): Promise<void> {
@@ -203,18 +203,20 @@ export class AutoModeler {
// to discussion.
for (const candidate of candidateMethods) {
if (!(candidate.signature in loadedMethods)) {
loadedMethods[candidate.signature] = {
type: "neutral",
kind: "sink",
input: "",
output: "",
provenance: "ai-generated",
signature: candidate.signature,
packageName: candidate.packageName,
typeName: candidate.typeName,
methodName: candidate.methodName,
methodParameters: candidate.methodParameters,
};
loadedMethods[candidate.signature] = [
{
type: "neutral",
kind: "sink",
input: "",
output: "",
provenance: "ai-generated",
signature: candidate.signature,
packageName: candidate.packageName,
typeName: candidate.typeName,
methodName: candidate.methodName,
methodParameters: candidate.methodParameters,
},
];
}
}

View File

@@ -2,43 +2,65 @@ import { DecodedBqrsChunk } from "../common/bqrs-cli-types";
import { Call, CallClassification, Method } from "./method";
import { ModeledMethodType } from "./modeled-method";
import { parseLibraryFilename } from "./library";
import { Mode } from "./shared/mode";
import { ApplicationModeTuple, FrameworkModeTuple } from "./queries/query";
export function decodeBqrsToExternalApiUsages(
export function decodeBqrsToMethods(
chunk: DecodedBqrsChunk,
mode: Mode,
): Method[] {
const methodsByApiName = new Map<string, Method>();
chunk?.tuples.forEach((tuple) => {
const usage = tuple[0] as Call;
const signature = tuple[1] as string;
const supported = (tuple[2] as string) === "true";
let library = tuple[4] as string;
let libraryVersion: string | undefined = tuple[5] as string;
const type = tuple[6] as ModeledMethodType;
const classification = tuple[8] as CallClassification;
let usage: Call;
let packageName: string;
let typeName: string;
let methodName: string;
let methodParameters: string;
let supported: boolean;
let library: string;
let libraryVersion: string | undefined;
let type: ModeledMethodType;
let classification: CallClassification;
const [packageWithType, methodDeclaration] = signature.split("#");
if (mode === Mode.Application) {
[
usage,
packageName,
typeName,
methodName,
methodParameters,
supported,
library,
libraryVersion,
type,
classification,
] = tuple as ApplicationModeTuple;
} else {
[
usage,
packageName,
typeName,
methodName,
methodParameters,
supported,
library,
type,
] = tuple as FrameworkModeTuple;
const packageName = packageWithType.substring(
0,
packageWithType.lastIndexOf("."),
);
const typeName = packageWithType.substring(
packageWithType.lastIndexOf(".") + 1,
);
classification = CallClassification.Unknown;
}
const methodName = methodDeclaration.substring(
0,
methodDeclaration.indexOf("("),
);
const methodParameters = methodDeclaration.substring(
methodDeclaration.indexOf("("),
);
const signature = `${packageName}.${typeName}#${methodName}${methodParameters}`;
// For Java, we'll always get back a .jar file, and the library version may be bad because not all library authors
// properly specify the version. Therefore, we'll always try to parse the name and version from the library filename
// for Java.
if (library.endsWith(".jar") || libraryVersion === "") {
if (
library.endsWith(".jar") ||
libraryVersion === "" ||
libraryVersion === undefined
) {
const { name, version } = parseLibraryFilename(library);
library = name;
if (version) {

View File

@@ -1,45 +0,0 @@
{
"type": "object",
"properties": {
"extensions": {
"type": "array",
"items": {
"type": "object",
"required": ["addsTo", "data"],
"properties": {
"addsTo": {
"type": "object",
"required": ["pack", "extensible"],
"properties": {
"pack": {
"type": "string"
},
"extensible": {
"type": "string"
}
}
},
"data": {
"type": "array",
"items": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "boolean"
},
{
"type": "number"
}
]
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,97 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/ExtensionPackMetadata",
"definitions": {
"ExtensionPackMetadata": {
"type": "object",
"properties": {
"extensionTargets": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"dataExtensions": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"dependencies": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"dbscheme": {
"type": "string"
},
"library": {
"type": "boolean"
},
"defaultSuite": {
"type": "array",
"items": {
"$ref": "#/definitions/SuiteInstruction"
}
},
"defaultSuiteFile": {
"type": "string"
}
},
"required": ["dataExtensions", "extensionTargets", "name", "version"]
},
"SuiteInstruction": {
"type": "object",
"properties": {
"qlpack": {
"type": "string"
},
"query": {
"type": "string"
},
"queries": {
"type": "string"
},
"include": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"exclude": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"description": {
"type": "string"
},
"from": {
"type": "string"
}
},
"description": "A single entry in a .qls file."
}
}
}

View File

@@ -0,0 +1,7 @@
import { QlPackFile } from "../packaging/qlpack-file";
export type ExtensionPackMetadata = QlPackFile & {
// Make both extensionTargets and dataExtensions required
extensionTargets: Record<string, string>;
dataExtensions: string[] | string;
};

View File

@@ -2,6 +2,7 @@ import { join } from "path";
import { outputFile, pathExists, readFile } from "fs-extra";
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
import { Uri } from "vscode";
import Ajv from "ajv";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
import { ProgressCallback } from "../common/vscode/progress";
@@ -10,7 +11,7 @@ import { getQlPackPath, QLPACK_FILENAMES } from "../common/ql";
import { getErrorMessage } from "../common/helpers-pure";
import { ExtensionPack } from "./shared/extension-pack";
import { NotificationLogger, showAndLogErrorMessage } from "../common/logging";
import { getExtensionsDirectory } from "../config";
import { ModelConfig } from "../config";
import {
autoNameExtensionPack,
ExtensionPackName,
@@ -18,9 +19,16 @@ import {
} from "./extension-pack-name";
import { autoPickExtensionsDirectory } from "./extensions-workspace-folder";
import { ExtensionPackMetadata } from "./extension-pack-metadata";
import * as extensionPackMetadataSchemaJson from "./extension-pack-metadata.schema.json";
const ajv = new Ajv({ allErrors: true });
const extensionPackValidate = ajv.compile(extensionPackMetadataSchemaJson);
export async function pickExtensionPack(
cliServer: Pick<CodeQLCliServer, "resolveQlpacks">,
databaseItem: Pick<DatabaseItem, "name" | "language">,
modelConfig: ModelConfig,
logger: NotificationLogger,
progress: ProgressCallback,
maxStep: number,
@@ -49,7 +57,9 @@ export async function pickExtensionPack(
});
// Get the `codeQL.model.extensionsDirectory` setting for the language
const userExtensionsDirectory = getExtensionsDirectory(databaseItem.language);
const userExtensionsDirectory = modelConfig.getExtensionsDirectory(
databaseItem.language,
);
// If the setting is not set, automatically pick a suitable directory
const extensionsDirectory = userExtensionsDirectory
@@ -170,6 +180,22 @@ async function writeExtensionPack(
return extensionPack;
}
function validateExtensionPack(
extensionPack: unknown,
): extensionPack is ExtensionPackMetadata {
extensionPackValidate(extensionPack);
if (extensionPackValidate.errors) {
throw new Error(
`Invalid extension pack YAML: ${extensionPackValidate.errors
.map((error) => `${error.instancePath} ${error.message}`)
.join(", ")}`,
);
}
return true;
}
async function readExtensionPack(
path: string,
language: string,
@@ -188,6 +214,10 @@ async function readExtensionPack(
throw new Error(`Could not parse ${qlpackPath}`);
}
if (!validateExtensionPack(qlpack)) {
throw new Error(`Could not validate ${qlpackPath}`);
}
const dataExtensionValue = qlpack.dataExtensions;
if (
!(

View File

@@ -15,7 +15,11 @@ import { QueryLanguage } from "../common/query-language";
import { fetchExternalApiQueries } from "./queries";
import { Method } from "./method";
import { runQuery } from "../local-queries/run-query";
import { decodeBqrsToExternalApiUsages } from "./bqrs";
import { decodeBqrsToMethods } from "./bqrs";
import {
resolveEndpointsQuery,
syntheticQueryPackName,
} from "./model-editor-queries";
type RunQueryOptions = {
cliServer: CodeQLCliServer;
@@ -88,7 +92,28 @@ export async function runExternalApiQueries(
await cliServer.resolveQlpacks(additionalPacks, true),
);
const queryPath = join(queryDir, queryNameFromMode(mode));
progress({
message: "Resolving query",
step: 2,
maxStep: externalApiQueriesProgressMaxStep,
});
// Resolve the queries from either codeql/java-queries or from the temporary queryDir
const queryPath = await resolveEndpointsQuery(
cliServer,
databaseItem.language,
mode,
[syntheticQueryPackName],
[queryDir],
);
if (!queryPath) {
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError`The ${mode} model editor query could not be found. Try re-opening the model editor. If that doesn't work, try upgrading the CodeQL libraries.`,
);
return;
}
// Run the actual query
const completedQuery = await runQuery({
@@ -132,7 +157,7 @@ export async function runExternalApiQueries(
maxStep: externalApiQueriesProgressMaxStep,
});
return decodeBqrsToExternalApiUsages(bqrsChunk);
return decodeBqrsToMethods(bqrsChunk, mode);
}
type GetResultsOptions = {
@@ -160,7 +185,5 @@ export async function readQueryResults({
}
function queryNameFromMode(mode: Mode): string {
return `FetchExternalApis${
mode.charAt(0).toUpperCase() + mode.slice(1)
}Mode.ql`;
return `${mode.charAt(0).toUpperCase() + mode.slice(1)}ModeEndpoints.ql`;
}

View File

@@ -1,15 +1,34 @@
import { ExtensionContext, window } from "vscode";
import { window } from "vscode";
import { App } from "../../common/app";
import { DisposableObject } from "../../common/disposable-object";
import { MethodModelingViewProvider } from "./method-modeling-view-provider";
import { Method } from "../method";
import { ModelingStore } from "../modeling-store";
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
import { ModelConfigListener } from "../../config";
import { DatabaseItem } from "../../databases/local-databases";
export class MethodModelingPanel extends DisposableObject {
private readonly provider: MethodModelingViewProvider;
constructor(context: ExtensionContext) {
constructor(
app: App,
modelingStore: ModelingStore,
editorViewTracker: ModelEditorViewTracker,
) {
super();
this.provider = new MethodModelingViewProvider(context);
// This is here instead of in MethodModelingViewProvider because we need to
// dispose this when the extension gets disposed, not when the webview gets
// disposed.
const modelConfig = this.push(new ModelConfigListener());
this.provider = new MethodModelingViewProvider(
app,
modelingStore,
editorViewTracker,
modelConfig,
);
this.push(
window.registerWebviewViewProvider(
MethodModelingViewProvider.viewType,
@@ -18,7 +37,10 @@ export class MethodModelingPanel extends DisposableObject {
);
}
public async setMethod(method: Method): Promise<void> {
await this.provider.setMethod(method);
public async setMethod(
databaseItem: DatabaseItem,
method: Method,
): Promise<void> {
await this.provider.setMethod(databaseItem, method);
}
}

View File

@@ -1,66 +1,103 @@
import * as vscode from "vscode";
import { WebviewViewProvider } from "vscode";
import { getHtmlForWebview } from "../../common/vscode/webview-html";
import { FromMethodModelingMessage } from "../../common/interface-types";
import {
FromMethodModelingMessage,
ToMethodModelingMessage,
} from "../../common/interface-types";
import { telemetryListener } from "../../common/vscode/telemetry";
import { showAndLogExceptionWithTelemetry } from "../../common/logging/notifications";
import { extLogger } from "../../common/logging/vscode/loggers";
import { App } from "../../common/app";
import { redactableError } from "../../common/errors";
import { Method } from "../method";
import { ModelingStore } from "../modeling-store";
import { AbstractWebviewViewProvider } from "../../common/vscode/abstract-webview-view-provider";
import { assertNever } from "../../common/helpers-pure";
import { ModelEditorViewTracker } from "../model-editor-view-tracker";
import { ModelConfigListener } from "../../config";
import { DatabaseItem } from "../../databases/local-databases";
import { convertFromLegacyModeledMethod } from "../shared/modeled-methods-legacy";
export class MethodModelingViewProvider implements WebviewViewProvider {
export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
ToMethodModelingMessage,
FromMethodModelingMessage
> {
public static readonly viewType = "codeQLMethodModeling";
private webviewView: vscode.WebviewView | undefined = undefined;
private method: Method | undefined = undefined;
private databaseItem: DatabaseItem | undefined = undefined;
constructor(private readonly context: vscode.ExtensionContext) {}
/**
* This is called when a view first becomes visible. This may happen when the view is
* first loaded or when the user hides and then shows a view again.
*/
public resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken,
constructor(
app: App,
private readonly modelingStore: ModelingStore,
private readonly editorViewTracker: ModelEditorViewTracker,
private readonly modelConfig: ModelConfigListener,
) {
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [this.context.extensionUri],
};
const html = getHtmlForWebview(
this.context,
webviewView.webview,
"method-modeling",
{
allowInlineStyles: true,
allowWasmEval: false,
},
);
webviewView.webview.html = html;
webviewView.webview.onDidReceiveMessage(async (msg) => this.onMessage(msg));
this.webviewView = webviewView;
super(app, "method-modeling");
}
public async setMethod(method: Method): Promise<void> {
if (this.webviewView) {
await this.webviewView.webview.postMessage({
protected override async onWebViewLoaded(): Promise<void> {
await Promise.all([this.setViewState(), this.setInitialState()]);
this.registerToModelingStoreEvents();
this.registerToModelConfigEvents();
}
private async setViewState(): Promise<void> {
await this.postMessage({
t: "setMethodModelingPanelViewState",
viewState: {
showMultipleModels: this.modelConfig.showMultipleModels,
},
});
}
public async setMethod(
databaseItem: DatabaseItem | undefined,
method: Method | undefined,
): Promise<void> {
this.method = method;
this.databaseItem = databaseItem;
if (this.isShowingView) {
await this.postMessage({
t: "setMethod",
method,
});
}
}
private async onMessage(msg: FromMethodModelingMessage): Promise<void> {
private async setInitialState(): Promise<void> {
if (this.modelingStore.hasStateForActiveDb()) {
const selectedMethod = this.modelingStore.getSelectedMethodDetails();
if (selectedMethod) {
this.databaseItem = selectedMethod.databaseItem;
this.method = selectedMethod.method;
await this.postMessage({
t: "setSelectedMethod",
method: selectedMethod.method,
modeledMethods: selectedMethod.modeledMethods,
isModified: selectedMethod.isModified,
});
}
await this.postMessage({
t: "setInModelingMode",
inModelingMode: true,
});
}
}
protected override async onMessage(
msg: FromMethodModelingMessage,
): Promise<void> {
switch (msg.t) {
case "telemetry": {
case "viewLoaded":
await this.onWebViewLoaded();
break;
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
}
case "unhandledError":
void showAndLogExceptionWithTelemetry(
extLogger,
@@ -70,6 +107,123 @@ export class MethodModelingViewProvider implements WebviewViewProvider {
)`Unhandled error in method modeling view: ${msg.error.message}`,
);
break;
case "setModeledMethod": {
if (!this.databaseItem) {
return;
}
this.modelingStore.updateModeledMethods(
this.databaseItem,
msg.method.signature,
convertFromLegacyModeledMethod(msg.method),
);
this.modelingStore.addModifiedMethod(
this.databaseItem,
msg.method.signature,
);
break;
}
case "revealInModelEditor":
await this.revealInModelEditor(msg.method);
break;
case "startModeling":
await this.app.commands.execute(
"codeQL.openModelEditorFromModelingPanel",
);
break;
default:
assertNever(msg);
}
}
private async revealInModelEditor(method: Method): Promise<void> {
if (!this.databaseItem) {
return;
}
const view = this.editorViewTracker.getView(
this.databaseItem.databaseUri.toString(),
);
await view?.revealMethod(method);
}
private registerToModelingStoreEvents(): void {
this.push(
this.modelingStore.onModeledMethodsChanged(async (e) => {
if (this.webviewView && e.isActiveDb && this.method) {
const modeledMethods = e.modeledMethods[this.method.signature];
if (modeledMethods) {
await this.postMessage({
t: "setMultipleModeledMethods",
methodSignature: this.method.signature,
modeledMethods,
});
}
}
}),
);
this.push(
this.modelingStore.onModifiedMethodsChanged(async (e) => {
if (this.webviewView && e.isActiveDb && this.method) {
const isModified = e.modifiedMethods.has(this.method.signature);
await this.postMessage({
t: "setMethodModified",
isModified,
});
}
}),
);
this.push(
this.modelingStore.onSelectedMethodChanged(async (e) => {
if (this.webviewView) {
this.method = e.method;
this.databaseItem = e.databaseItem;
await this.postMessage({
t: "setSelectedMethod",
method: e.method,
modeledMethods: e.modeledMethods,
isModified: e.isModified,
});
}
}),
);
this.push(
this.modelingStore.onDbOpened(async () => {
await this.postMessage({
t: "setInModelingMode",
inModelingMode: true,
});
}),
);
this.push(
this.modelingStore.onDbClosed(async (dbUri) => {
if (!this.modelingStore.anyDbsBeingModeled()) {
await this.postMessage({
t: "setInModelingMode",
inModelingMode: false,
});
}
if (dbUri === this.databaseItem?.databaseUri.toString()) {
await this.setMethod(undefined, undefined);
}
}),
);
}
private registerToModelConfigEvents(): void {
this.push(
this.modelConfig.onDidChangeConfiguration(() => {
void this.setViewState();
}),
);
}
}

View File

@@ -1,5 +1,5 @@
import { ResolvableLocationValue } from "../common/bqrs-cli-types";
import { ModeledMethodType } from "./modeled-method";
import { ModeledMethod, ModeledMethodType } from "./modeled-method";
export type Call = {
label: string;
@@ -30,6 +30,11 @@ export interface MethodSignature {
* e.g. `org.sql2o.Connection#createQuery(String)`
*/
signature: string;
/**
* The package name in Java, or the namespace in C#, e.g. `org.sql2o` or `System.Net.Http.Headers`.
*
* If the class is not in a package, the value should be an empty string.
*/
packageName: string;
typeName: string;
methodName: string;
@@ -52,3 +57,23 @@ export interface Method extends MethodSignature {
supportedType: ModeledMethodType;
usages: Usage[];
}
export function getArgumentsList(methodParameters: string): string[] {
if (methodParameters === "()") {
return [];
}
return methodParameters.substring(1, methodParameters.length - 1).split(",");
}
export function canMethodBeModeled(
method: Method,
modeledMethods: ModeledMethod[],
methodIsUnsaved: boolean,
): boolean {
return (
!method.supported ||
modeledMethods.some((modeledMethod) => modeledMethod.type !== "none") ||
methodIsUnsaved
);
}

View File

@@ -13,16 +13,27 @@ import { Method, Usage } from "../method";
import { DatabaseItem } from "../../databases/local-databases";
import { relative } from "path";
import { CodeQLCliServer } from "../../codeql-cli/cli";
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "../shared/hide-modeled-apis";
import { INITIAL_HIDE_MODELED_METHODS_VALUE } from "../shared/hide-modeled-methods";
import { getModelingStatus } from "../shared/modeling-status";
import { assertNever } from "../../common/helpers-pure";
import { ModeledMethod } from "../modeled-method";
import { groupMethods, sortGroupNames, sortMethods } from "../shared/sorting";
import { INITIAL_MODE, Mode } from "../shared/mode";
export class MethodsUsageDataProvider
extends DisposableObject
implements TreeDataProvider<MethodsUsageTreeViewItem>
{
private methods: Method[] = [];
// sortedMethods is a separate field so we can check if the methods have changed
// by reference, which is faster than checking if the methods have changed by value.
private sortedMethods: Method[] = [];
private databaseItem: DatabaseItem | undefined = undefined;
private sourceLocationPrefix: string | undefined = undefined;
private hideModeledApis: boolean = INITIAL_HIDE_MODELED_APIS_VALUE;
private hideModeledMethods: boolean = INITIAL_HIDE_MODELED_METHODS_VALUE;
private mode: Mode = INITIAL_MODE;
private modeledMethods: Record<string, ModeledMethod[]> = {};
private modifiedMethodSignatures: Set<string> = new Set();
private readonly onDidChangeTreeDataEmitter = this.push(
new EventEmitter<void>(),
@@ -46,18 +57,28 @@ export class MethodsUsageDataProvider
public async setState(
methods: Method[],
databaseItem: DatabaseItem,
hideModeledApis: boolean,
hideModeledMethods: boolean,
mode: Mode,
modeledMethods: Record<string, ModeledMethod[]>,
modifiedMethodSignatures: Set<string>,
): Promise<void> {
if (
this.methods !== methods ||
this.databaseItem !== databaseItem ||
this.hideModeledApis !== hideModeledApis
this.hideModeledMethods !== hideModeledMethods ||
this.mode !== mode ||
this.modeledMethods !== modeledMethods ||
this.modifiedMethodSignatures !== modifiedMethodSignatures
) {
this.methods = methods;
this.sortedMethods = sortMethodsInGroups(methods, mode);
this.databaseItem = databaseItem;
this.sourceLocationPrefix =
await this.databaseItem.getSourceLocationPrefix(this.cliServer);
this.hideModeledApis = hideModeledApis;
this.hideModeledMethods = hideModeledMethods;
this.mode = mode;
this.modeledMethods = modeledMethods;
this.modifiedMethodSignatures = modifiedMethodSignatures;
this.onDidChangeTreeDataEmitter.fire();
}
@@ -68,10 +89,13 @@ export class MethodsUsageDataProvider
return {
label: `${item.packageName}.${item.typeName}.${item.methodName}${item.methodParameters}`,
collapsibleState: TreeItemCollapsibleState.Collapsed,
iconPath: new ThemeIcon("symbol-method"),
iconPath: this.getModelingStatusIcon(item),
};
} else {
const method = this.getParent(item);
if (!method || !isExternalApiUsage(method)) {
throw new Error("Parent not found for tree item");
}
return {
label: item.label,
description: `${this.relativePathWithinDatabase(item.url.uri)} [${
@@ -80,14 +104,33 @@ export class MethodsUsageDataProvider
collapsibleState: TreeItemCollapsibleState.None,
command: {
title: "Show usage",
command: "codeQLModelEditor.jumpToUsageLocation",
arguments: [method, item, this.databaseItem],
command: "codeQLModelEditor.jumpToMethod",
arguments: [method.signature, this.databaseItem],
},
iconPath: new ThemeIcon("error", new ThemeColor("errorForeground")),
};
}
}
private getModelingStatusIcon(method: Method): ThemeIcon {
const modeledMethods = this.modeledMethods[method.signature] ?? [];
const modifiedMethod = this.modifiedMethodSignatures.has(method.signature);
const status = getModelingStatus(modeledMethods, modifiedMethod);
switch (status) {
case "unmodeled":
return new ThemeIcon("error", new ThemeColor("errorForeground"));
case "unsaved":
return new ThemeIcon("pass", new ThemeColor("testing.iconPassed"));
case "saved":
return new ThemeIcon(
"pass-filled",
new ThemeColor("testing.iconPassed"),
);
default:
assertNever(status);
}
}
private relativePathWithinDatabase(uri: string): string {
const parsedUri = Uri.parse(uri);
if (this.sourceLocationPrefix) {
@@ -99,10 +142,10 @@ export class MethodsUsageDataProvider
getChildren(item?: MethodsUsageTreeViewItem): MethodsUsageTreeViewItem[] {
if (item === undefined) {
if (this.hideModeledApis) {
return this.methods.filter((api) => !api.supported);
if (this.hideModeledMethods) {
return this.sortedMethods.filter((api) => !api.supported);
} else {
return this.methods;
return this.sortedMethods;
}
} else if (isExternalApiUsage(item)) {
return item.usages;
@@ -150,3 +193,15 @@ function usagesAreEqual(u1: Usage, u2: Usage): boolean {
u1.url.endColumn === u2.url.endColumn
);
}
function sortMethodsInGroups(methods: Method[], mode: Mode): Method[] {
const grouped = groupMethods(methods, mode);
const sortedGroupNames = sortGroupNames(grouped);
return sortedGroupNames.flatMap((groupName) => {
const group = grouped[groupName];
return sortMethods(group);
});
}

View File

@@ -7,12 +7,18 @@ import {
import { Method, Usage } from "../method";
import { DatabaseItem } from "../../databases/local-databases";
import { CodeQLCliServer } from "../../codeql-cli/cli";
import { ModelingStore } from "../modeling-store";
import { ModeledMethod } from "../modeled-method";
import { Mode } from "../shared/mode";
export class MethodsUsagePanel extends DisposableObject {
private readonly dataProvider: MethodsUsageDataProvider;
private readonly treeView: TreeView<MethodsUsageTreeViewItem>;
public constructor(cliServer: CodeQLCliServer) {
public constructor(
private readonly modelingStore: ModelingStore,
cliServer: CodeQLCliServer,
) {
super();
this.dataProvider = new MethodsUsageDataProvider(cliServer);
@@ -21,15 +27,27 @@ export class MethodsUsagePanel extends DisposableObject {
treeDataProvider: this.dataProvider,
});
this.push(this.treeView);
this.registerToModelingStoreEvents();
}
public async setState(
methods: Method[],
databaseItem: DatabaseItem,
hideModeledApis: boolean,
hideModeledMethods: boolean,
mode: Mode,
modeledMethods: Record<string, ModeledMethod[]>,
modifiedMethodSignatures: Set<string>,
): Promise<void> {
await this.dataProvider.setState(methods, databaseItem, hideModeledApis);
const numOfApis = hideModeledApis
await this.dataProvider.setState(
methods,
databaseItem,
hideModeledMethods,
mode,
modeledMethods,
modifiedMethodSignatures,
);
const numOfApis = hideModeledMethods
? methods.filter((api) => !api.supported).length
: methods.length;
this.treeView.badge = {
@@ -44,4 +62,58 @@ export class MethodsUsagePanel extends DisposableObject {
await this.treeView.reveal(canonicalUsage);
}
}
private registerToModelingStoreEvents(): void {
this.push(
this.modelingStore.onActiveDbChanged(async () => {
await this.handleStateChangeEvent();
}),
);
this.push(
this.modelingStore.onMethodsChanged(async (event) => {
if (event.isActiveDb) {
await this.handleStateChangeEvent();
}
}),
);
this.push(
this.modelingStore.onHideModeledMethodsChanged(async (event) => {
if (event.isActiveDb) {
await this.handleStateChangeEvent();
}
}),
);
this.push(
this.modelingStore.onModeChanged(async (event) => {
if (event.isActiveDb) {
await this.handleStateChangeEvent();
}
}),
);
this.push(
this.modelingStore.onModifiedMethodsChanged(async (event) => {
if (event.isActiveDb) {
await this.handleStateChangeEvent();
}
}),
);
}
private async handleStateChangeEvent(): Promise<void> {
const activeState = this.modelingStore.getStateForActiveDb();
if (activeState !== undefined) {
await this.setState(
activeState.methods,
activeState.databaseItem,
activeState.hideModeledMethods,
activeState.mode,
activeState.modeledMethods,
activeState.modifiedMethodSignatures,
);
}
}
}

View File

@@ -0,0 +1,13 @@
import { Mode } from "./shared/mode";
import { assertNever } from "../common/helpers-pure";
export function modeTag(mode: Mode): string {
switch (mode) {
case Mode.Application:
return "application-mode";
case Mode.Framework:
return "framework-mode";
default:
assertNever(mode);
}
}

View File

@@ -1,4 +1,3 @@
import { ExtensionContext } from "vscode";
import { ModelEditorView } from "./model-editor-view";
import { ModelEditorCommands } from "../common/commands";
import { CliVersionConstraint, CodeQLCliServer } from "../codeql-cli/cli";
@@ -15,23 +14,24 @@ import { dir } from "tmp-promise";
import { isQueryLanguage } from "../common/query-language";
import { DisposableObject } from "../common/disposable-object";
import { MethodsUsagePanel } from "./methods-usage/methods-usage-panel";
import { Mode } from "./shared/mode";
import { showResolvableLocation } from "../databases/local-databases/locations";
import { Method, Usage } from "./method";
import { setUpPack } from "./model-editor-queries";
import { MethodModelingPanel } from "./method-modeling/method-modeling-panel";
import { ModelingStore } from "./modeling-store";
import { showResolvableLocation } from "../databases/local-databases/locations";
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
import { ModelConfigListener } from "../config";
const SUPPORTED_LANGUAGES: string[] = ["java", "csharp"];
export class ModelEditorModule extends DisposableObject {
private readonly queryStorageDir: string;
private readonly modelingStore: ModelingStore;
private readonly editorViewTracker: ModelEditorViewTracker<ModelEditorView>;
private readonly methodsUsagePanel: MethodsUsagePanel;
private readonly methodModelingPanel: MethodModelingPanel;
private mostRecentlyActiveView: ModelEditorView | undefined = undefined;
private constructor(
private readonly ctx: ExtensionContext,
private readonly app: App,
private readonly databaseManager: DatabaseManager,
private readonly cliServer: CodeQLCliServer,
@@ -40,26 +40,19 @@ export class ModelEditorModule extends DisposableObject {
) {
super();
this.queryStorageDir = join(baseQueryStorageDir, "model-editor-results");
this.methodsUsagePanel = this.push(new MethodsUsagePanel(cliServer));
this.methodModelingPanel = this.push(new MethodModelingPanel(ctx));
}
this.modelingStore = new ModelingStore(app);
this.editorViewTracker = new ModelEditorViewTracker();
this.methodsUsagePanel = this.push(
new MethodsUsagePanel(this.modelingStore, cliServer),
);
this.methodModelingPanel = this.push(
new MethodModelingPanel(app, this.modelingStore, this.editorViewTracker),
);
private handleViewBecameActive(view: ModelEditorView): void {
this.mostRecentlyActiveView = view;
}
private handleViewWasDisposed(view: ModelEditorView): void {
if (this.mostRecentlyActiveView === view) {
this.mostRecentlyActiveView = undefined;
}
}
private isMostRecentlyActiveView(view: ModelEditorView): boolean {
return this.mostRecentlyActiveView === view;
this.registerToModelingStoreEvents();
}
public static async initialize(
ctx: ExtensionContext,
app: App,
databaseManager: DatabaseManager,
cliServer: CodeQLCliServer,
@@ -67,7 +60,6 @@ export class ModelEditorModule extends DisposableObject {
queryStorageDir: string,
): Promise<ModelEditorModule> {
const modelEditorModule = new ModelEditorModule(
ctx,
app,
databaseManager,
cliServer,
@@ -81,106 +73,14 @@ export class ModelEditorModule extends DisposableObject {
public getCommands(): ModelEditorCommands {
return {
"codeQL.openModelEditor": async () => {
const db = this.databaseManager.currentDatabaseItem;
if (!db) {
void showAndLogErrorMessage(this.app.logger, "No database selected");
return;
}
const language = db.language;
if (
!SUPPORTED_LANGUAGES.includes(language) ||
!isQueryLanguage(language)
) {
void showAndLogErrorMessage(
this.app.logger,
`The CodeQL Model Editor is not supported for ${language} databases.`,
);
return;
}
return withProgress(
async (progress) => {
const maxStep = 4;
if (!(await this.cliServer.cliConstraints.supportsQlpacksKind())) {
void showAndLogErrorMessage(
this.app.logger,
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND.format()} or later.`,
);
return;
}
if (
!(await this.cliServer.cliConstraints.supportsResolveExtensions())
) {
void showAndLogErrorMessage(
this.app.logger,
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS.format()} or later.`,
);
return;
}
const modelFile = await pickExtensionPack(
this.cliServer,
db,
this.app.logger,
progress,
maxStep,
);
if (!modelFile) {
return;
}
progress({
message: "Installing dependencies...",
step: 3,
maxStep,
});
// Create new temporary directory for query files and pack dependencies
const queryDir = (await dir({ unsafeCleanup: true })).path;
const success = await setUpPack(this.cliServer, queryDir, language);
if (!success) {
return;
}
progress({
message: "Opening editor...",
step: 4,
maxStep,
});
const view = new ModelEditorView(
this.ctx,
this.app,
this.databaseManager,
this.cliServer,
this.queryRunner,
this.queryStorageDir,
queryDir,
db,
modelFile,
Mode.Application,
this.methodsUsagePanel.setState.bind(this.methodsUsagePanel),
this.showMethod.bind(this),
this.handleViewBecameActive.bind(this),
this.handleViewWasDisposed.bind(this),
this.isMostRecentlyActiveView.bind(this),
);
await view.openView();
},
{
title: "Opening CodeQL Model Editor",
},
);
},
"codeQLModelEditor.jumpToUsageLocation": async (
usage: Usage,
"codeQL.openModelEditor": this.openModelEditor.bind(this),
"codeQL.openModelEditorFromModelingPanel":
this.openModelEditor.bind(this),
"codeQLModelEditor.jumpToMethod": async (
methodSignature: string,
databaseItem: DatabaseItem,
) => {
await showResolvableLocation(usage.url, databaseItem, this.app.logger);
this.modelingStore.setSelectedMethod(databaseItem, methodSignature);
},
};
}
@@ -189,8 +89,161 @@ export class ModelEditorModule extends DisposableObject {
await ensureDir(this.queryStorageDir);
}
private async showMethod(method: Method, usage: Usage): Promise<void> {
private registerToModelingStoreEvents(): void {
this.push(
this.modelingStore.onSelectedMethodChanged(async (event) => {
await this.showMethod(event.databaseItem, event.method, event.usage);
}),
);
}
private async showMethod(
databaseItem: DatabaseItem,
method: Method,
usage: Usage,
): Promise<void> {
await this.methodsUsagePanel.revealItem(usage);
await this.methodModelingPanel.setMethod(method);
await this.methodModelingPanel.setMethod(databaseItem, method);
await showResolvableLocation(usage.url, databaseItem, this.app.logger);
}
private async openModelEditor(): Promise<void> {
{
const db = this.databaseManager.currentDatabaseItem;
if (!db) {
void showAndLogErrorMessage(this.app.logger, "No database selected");
return;
}
const language = db.language;
if (
!SUPPORTED_LANGUAGES.includes(language) ||
!isQueryLanguage(language)
) {
void showAndLogErrorMessage(
this.app.logger,
`The CodeQL Model Editor is not supported for ${language} databases.`,
);
return;
}
const existingView = this.editorViewTracker.getView(
db.databaseUri.toString(),
);
if (existingView) {
await existingView.focusView();
return;
}
return withProgress(
async (progress) => {
const maxStep = 4;
if (!(await this.cliServer.cliConstraints.supportsQlpacksKind())) {
void showAndLogErrorMessage(
this.app.logger,
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND.format()} or later.`,
);
return;
}
if (
!(await this.cliServer.cliConstraints.supportsResolveExtensions())
) {
void showAndLogErrorMessage(
this.app.logger,
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS.format()} or later.`,
);
return;
}
const modelConfig = this.push(new ModelConfigListener());
const modelFile = await pickExtensionPack(
this.cliServer,
db,
modelConfig,
this.app.logger,
progress,
maxStep,
);
if (!modelFile) {
return;
}
progress({
message: "Installing dependencies...",
step: 3,
maxStep,
});
// Create new temporary directory for query files and pack dependencies
const { path: queryDir, cleanup: cleanupQueryDir } = await dir({
unsafeCleanup: true,
});
const success = await setUpPack(
this.cliServer,
queryDir,
language,
modelConfig,
);
if (!success) {
await cleanupQueryDir();
return;
}
progress({
message: "Opening editor...",
step: 4,
maxStep,
});
// Check again just before opening the editor to ensure no model editor has been opened between
// our first check and now.
const existingView = this.editorViewTracker.getView(
db.databaseUri.toString(),
);
if (existingView) {
await existingView.focusView();
return;
}
const view = new ModelEditorView(
this.app,
this.modelingStore,
this.editorViewTracker,
modelConfig,
this.databaseManager,
this.cliServer,
this.queryRunner,
this.queryStorageDir,
queryDir,
db,
modelFile,
);
this.modelingStore.onDbClosed(async (dbUri) => {
if (dbUri === db.databaseUri.toString()) {
await cleanupQueryDir();
}
});
this.push(view);
this.push({
dispose(): void {
void cleanupQueryDir();
},
});
await view.openView();
},
{
title: "Opening CodeQL Model Editor",
},
);
}
}
}

View File

@@ -4,43 +4,137 @@ import { writeFile } from "fs-extra";
import { dump } from "js-yaml";
import { prepareExternalApiQuery } from "./external-api-usage-queries";
import { CodeQLCliServer } from "../codeql-cli/cli";
import { ModelConfig } from "../config";
import { Mode } from "./shared/mode";
import { resolveQueriesFromPacks } from "../local-queries";
import { modeTag } from "./mode-tag";
export const syntheticQueryPackName = "codeql/external-api-usage";
/**
* setUpPack sets up a directory to use for the data extension editor queries.
* setUpPack sets up a directory to use for the data extension editor queries if required.
*
* There are two cases (example language is Java):
* - In case the queries are present in the codeql/java-queries, we don't need to write our own queries
* to disk. We still need to create a synthetic query pack so we can pass the queryDir to the query
* resolver without caring about whether the queries are present in the pack or not.
* - In case the queries are not present in the codeql/java-queries, we need to write our own queries
* to disk. We will create a synthetic query pack and install its dependencies so it is fully independent
* and we can simply pass it through when resolving the queries.
*
* These steps together ensure that later steps of the process don't need to keep track of whether the queries
* are present in codeql/java-queries or in our own query pack. They just need to resolve the query.
*
* @param cliServer The CodeQL CLI server to use.
* @param queryDir The directory to set up.
* @param language The language to use for the queries.
* @param modelConfig The model config to use.
* @returns true if the setup was successful, false otherwise.
*/
export async function setUpPack(
cliServer: CodeQLCliServer,
queryDir: string,
language: QueryLanguage,
modelConfig: ModelConfig,
): Promise<boolean> {
// Create the external API query
const externalApiQuerySuccess = await prepareExternalApiQuery(
queryDir,
// Download the required query packs
await cliServer.packDownload([`codeql/${language}-queries`]);
// We'll only check if the application mode query exists in the pack and assume that if it does,
// the framework mode query will also exist.
const applicationModeQuery = await resolveEndpointsQuery(
cliServer,
language,
Mode.Application,
[],
[],
);
if (!externalApiQuerySuccess) {
return false;
if (applicationModeQuery) {
// Set up a synthetic pack so CodeQL doesn't crash later when we try
// to resolve a query within this directory
const syntheticQueryPack = {
name: syntheticQueryPackName,
version: "0.0.0",
dependencies: {},
};
const qlpackFile = join(queryDir, "codeql-pack.yml");
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
} else {
// If we can't resolve the query, we need to write them to desk ourselves.
const externalApiQuerySuccess = await prepareExternalApiQuery(
queryDir,
language,
);
if (!externalApiQuerySuccess) {
return false;
}
// Set up a synthetic pack so that the query can be resolved later.
const syntheticQueryPack = {
name: syntheticQueryPackName,
version: "0.0.0",
dependencies: {
[`codeql/${language}-all`]: "*",
},
};
const qlpackFile = join(queryDir, "codeql-pack.yml");
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
await cliServer.packInstall(queryDir);
}
// Set up a synthetic pack so that the query can be resolved later.
const syntheticQueryPack = {
name: "codeql/external-api-usage",
version: "0.0.0",
dependencies: {
[`codeql/${language}-all`]: "*",
},
};
const qlpackFile = join(queryDir, "codeql-pack.yml");
await writeFile(qlpackFile, dump(syntheticQueryPack), "utf8");
await cliServer.packInstall(queryDir);
// Install the other needed query packs
await cliServer.packDownload([`codeql/${language}-queries`]);
await cliServer.packDownload([`codeql/${language}-automodel-queries`]);
// Download any other required packs
if (language === "java" && modelConfig.llmGeneration) {
await cliServer.packDownload([`codeql/${language}-automodel-queries`]);
}
return true;
}
/**
* Resolve the query path to the model editor endpoints query. All queries are tagged like this:
* modeleditor endpoints <mode>
* Example: modeleditor endpoints framework-mode
*
* @param cliServer The CodeQL CLI server to use.
* @param language The language of the query pack to use.
* @param mode The mode to resolve the query for.
* @param additionalPackNames Additional pack names to search.
* @param additionalPackPaths Additional pack paths to search.
*/
export async function resolveEndpointsQuery(
cliServer: CodeQLCliServer,
language: string,
mode: Mode,
additionalPackNames: string[] = [],
additionalPackPaths: string[] = [],
): Promise<string | undefined> {
const packsToSearch = [`codeql/${language}-queries`, ...additionalPackNames];
// First, resolve the query that we want to run.
// All queries are tagged like this:
// internal extract automodel <mode> <queryTag>
// Example: internal extract automodel framework-mode candidates
const queries = await resolveQueriesFromPacks(
cliServer,
packsToSearch,
{
kind: "table",
"tags contain all": ["modeleditor", "endpoints", modeTag(mode)],
},
additionalPackPaths,
);
if (queries.length > 1) {
throw new Error(
`Found multiple endpoints queries for ${mode}. Can't continue`,
);
}
if (queries.length === 0) {
return undefined;
}
return queries[0];
}

View File

@@ -0,0 +1,33 @@
import { Method } from "./method";
interface ModelEditorViewInterface {
databaseUri: string;
revealMethod(method: Method): Promise<void>;
}
export class ModelEditorViewTracker<
T extends ModelEditorViewInterface = ModelEditorViewInterface,
> {
private readonly views = new Map<string, T>();
constructor() {}
public registerView(view: T): void {
const databaseUri = view.databaseUri;
if (this.views.has(databaseUri)) {
throw new Error(`View for database ${databaseUri} already registered`);
}
this.views.set(databaseUri, view);
}
public unregisterView(view: T): void {
this.views.delete(view.databaseUri);
}
public getView(databaseUri: string): T | undefined {
return this.views.get(databaseUri);
}
}

View File

@@ -1,6 +1,7 @@
import {
CancellationTokenSource,
ExtensionContext,
Tab,
TabInputWebview,
Uri,
ViewColumn,
window,
@@ -16,8 +17,8 @@ import {
import { ProgressCallback, withProgress } from "../common/vscode/progress";
import { QueryRunner } from "../query-server";
import {
showAndLogExceptionWithTelemetry,
showAndLogErrorMessage,
showAndLogExceptionWithTelemetry,
} from "../common/logging";
import { DatabaseItem, DatabaseManager } from "../databases/local-databases";
import { CodeQLCliServer } from "../codeql-cli/cli";
@@ -25,24 +26,24 @@ import { asError, assertNever, getErrorMessage } from "../common/helpers-pure";
import { runFlowModelQueries } from "./flow-model-queries";
import { promptImportGithubDatabase } from "../databases/database-fetcher";
import { App } from "../common/app";
import { showResolvableLocation } from "../databases/local-databases/locations";
import { redactableError } from "../common/errors";
import {
externalApiQueriesProgressMaxStep,
runExternalApiQueries,
} from "./external-api-usage-queries";
import { Method, Usage } from "./method";
import { Method } from "./method";
import { ModeledMethod } from "./modeled-method";
import { ExtensionPack } from "./shared/extension-pack";
import { showFlowGeneration, showLlmGeneration } from "../config";
import { Mode } from "./shared/mode";
import { ModelConfigListener } from "../config";
import { INITIAL_MODE, Mode } from "./shared/mode";
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
import { join } from "path";
import { pickExtensionPack } from "./extension-pack-picker";
import { getLanguageDisplayName } from "../common/query-language";
import { AutoModeler } from "./auto-modeler";
import { INITIAL_HIDE_MODELED_APIS_VALUE } from "./shared/hide-modeled-apis";
import { telemetryListener } from "../common/vscode/telemetry";
import { ModelingStore } from "./modeling-store";
import { ModelEditorViewTracker } from "./model-editor-view-tracker";
import { convertFromLegacyModeledMethod } from "./shared/modeled-methods-legacy";
export class ModelEditorView extends AbstractWebview<
ToModelEditorMessage,
@@ -50,12 +51,11 @@ export class ModelEditorView extends AbstractWebview<
> {
private readonly autoModeler: AutoModeler;
private methods: Method[];
private hideModeledApis: boolean;
public constructor(
ctx: ExtensionContext,
private readonly app: App,
protected readonly app: App,
private readonly modelingStore: ModelingStore,
private readonly viewTracker: ModelEditorViewTracker<ModelEditorView>,
private readonly modelConfig: ModelConfigListener,
private readonly databaseManager: DatabaseManager,
private readonly cliServer: CodeQLCliServer,
private readonly queryRunner: QueryRunner,
@@ -63,23 +63,15 @@ export class ModelEditorView extends AbstractWebview<
private readonly queryDir: string,
private readonly databaseItem: DatabaseItem,
private readonly extensionPack: ExtensionPack,
private mode: Mode,
private readonly updateMethodsUsagePanelState: (
methods: Method[],
databaseItem: DatabaseItem,
hideModeledApis: boolean,
) => Promise<void>,
private readonly showMethod: (
method: Method,
usage: Usage,
) => Promise<void>,
private readonly handleViewBecameActive: (view: ModelEditorView) => void,
private readonly handleViewWasDisposed: (view: ModelEditorView) => void,
private readonly isMostRecentlyActiveView: (
view: ModelEditorView,
) => boolean,
initialMode: Mode = INITIAL_MODE,
) {
super(ctx);
super(app);
this.modelingStore.initializeStateForDb(databaseItem, initialMode);
this.registerToModelingStoreEvents();
this.registerToModelConfigEvents();
this.viewTracker.registerView(this);
this.autoModeler = new AutoModeler(
app,
@@ -95,11 +87,9 @@ export class ModelEditorView extends AbstractWebview<
});
},
async (modeledMethods) => {
await this.postMessage({ t: "addModeledMethods", modeledMethods });
this.addModeledMethods(modeledMethods);
},
);
this.methods = [];
this.hideModeledApis = INITIAL_HIDE_MODELED_APIS_VALUE;
}
public async openView() {
@@ -108,17 +98,12 @@ export class ModelEditorView extends AbstractWebview<
panel.onDidChangeViewState(async () => {
if (panel.active) {
this.handleViewBecameActive(this);
await this.updateMethodsUsagePanelState(
this.methods,
this.databaseItem,
this.hideModeledApis,
);
this.modelingStore.setActiveDb(this.databaseItem);
}
});
panel.onDidDispose(() => {
this.handleViewWasDisposed(this);
this.modelingStore.removeDb(this.databaseItem);
// onDidDispose is called after the tab has been closed,
// so we want to check if there are any others still open.
void this.app.commands.execute(
@@ -139,15 +124,20 @@ export class ModelEditorView extends AbstractWebview<
private isAModelEditorOpen(): boolean {
return window.tabGroups.all.some((tabGroup) =>
tabGroup.tabs.some((tab) => {
const viewType: string | undefined = (tab.input as any)?.viewType;
// The viewType has a prefix, such as "mainThreadWebview-", but if the
// suffix matches that should be enough to identify the view.
return viewType && viewType.endsWith("model-editor");
}),
tabGroup.tabs.some((tab) => this.isTabModelEditorView(tab)),
);
}
private isTabModelEditorView(tab: Tab): boolean {
if (!(tab.input instanceof TabInputWebview)) {
return false;
}
// The viewType has a prefix, such as "mainThreadWebview-", but if the
// suffix matches that should be enough to identify the view.
return tab.input.viewType.endsWith("model-editor");
}
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
return {
viewId: "model-editor",
@@ -158,18 +148,20 @@ export class ModelEditorView extends AbstractWebview<
preserveFocus: true,
view: "model-editor",
iconPath: {
dark: Uri.file(
join(this.ctx.extensionPath, "media/dark/symbol-misc.svg"),
dark: Uri.joinPath(
Uri.file(this.app.extensionPath),
"media/dark/symbol-misc.svg",
),
light: Uri.file(
join(this.ctx.extensionPath, "media/light/symbol-misc.svg"),
light: Uri.joinPath(
Uri.file(this.app.extensionPath),
"media/light/symbol-misc.svg",
),
},
};
}
protected onPanelDispose(): void {
// Nothing to do here
this.viewTracker.unregisterView(this);
}
protected async onMessage(msg: FromModelEditorMessage): Promise<void> {
@@ -193,7 +185,7 @@ export class ModelEditorView extends AbstractWebview<
break;
case "refreshMethods":
await withProgress((progress) => this.loadExternalApiUsages(progress), {
await withProgress((progress) => this.loadMethods(progress), {
cancellable: false,
});
@@ -202,47 +194,67 @@ export class ModelEditorView extends AbstractWebview<
);
break;
case "jumpToUsage":
await this.handleJumpToUsage(msg.method, msg.usage);
void telemetryListener?.sendUIInteraction("model-editor-jump-to-usage");
case "jumpToMethod":
await this.handleJumpToMethod(msg.methodSignature);
void telemetryListener?.sendUIInteraction(
"model-editor-jump-to-method",
);
break;
case "saveModeledMethods":
await withProgress(
async (progress) => {
progress({
step: 1,
maxStep: 500 + externalApiQueriesProgressMaxStep,
message: "Writing model files",
});
await saveModeledMethods(
this.extensionPack,
this.databaseItem.language,
msg.methods,
msg.modeledMethods,
this.mode,
this.cliServer,
this.app.logger,
);
{
const methods = this.modelingStore.getMethods(
this.databaseItem,
msg.methodSignatures,
);
const modeledMethods = this.modelingStore.getModeledMethods(
this.databaseItem,
msg.methodSignatures,
);
const mode = this.modelingStore.getMode(this.databaseItem);
await Promise.all([
this.setViewState(),
this.loadExternalApiUsages((update) =>
progress({
...update,
step: update.step + 500,
maxStep: 500 + externalApiQueriesProgressMaxStep,
}),
),
]);
},
{
cancellable: false,
},
);
void telemetryListener?.sendUIInteraction(
"model-editor-save-modeled-methods",
);
await withProgress(
async (progress) => {
progress({
step: 1,
maxStep: 500 + externalApiQueriesProgressMaxStep,
message: "Writing model files",
});
await saveModeledMethods(
this.extensionPack,
this.databaseItem.language,
methods,
modeledMethods,
mode,
this.cliServer,
this.app.logger,
);
await Promise.all([
this.setViewState(),
this.loadMethods((update) =>
progress({
...update,
step: update.step + 500,
maxStep: 500 + externalApiQueriesProgressMaxStep,
}),
),
]);
},
{
cancellable: false,
},
);
this.modelingStore.removeModifiedMethods(
this.databaseItem,
Object.keys(modeledMethods),
);
void telemetryListener?.sendUIInteraction(
"model-editor-save-modeled-methods",
);
}
break;
case "generateMethod":
@@ -255,8 +267,7 @@ export class ModelEditorView extends AbstractWebview<
case "generateMethodsFromLlm":
await this.generateModeledMethodsFromLlm(
msg.packageName,
msg.methods,
msg.modeledMethods,
msg.methodSignatures,
);
void telemetryListener?.sendUIInteraction(
"model-editor-generate-methods-from-llm",
@@ -275,30 +286,47 @@ export class ModelEditorView extends AbstractWebview<
);
break;
case "switchMode":
this.mode = msg.mode;
this.methods = [];
this.modelingStore.setMode(this.databaseItem, msg.mode);
this.modelingStore.setMethods(this.databaseItem, []);
await Promise.all([
this.postMessage({
t: "setMethods",
methods: this.methods,
methods: [],
}),
this.setViewState(),
withProgress((progress) => this.loadExternalApiUsages(progress), {
withProgress((progress) => this.loadMethods(progress), {
cancellable: false,
}),
]);
void telemetryListener?.sendUIInteraction("model-editor-switch-modes");
break;
case "hideModeledApis":
this.hideModeledApis = msg.hideModeledApis;
await this.updateMethodsUsagePanelState(
this.methods,
case "hideModeledMethods":
this.modelingStore.setHideModeledMethods(
this.databaseItem,
this.hideModeledApis,
msg.hideModeledMethods,
);
void telemetryListener?.sendUIInteraction(
"model-editor-hide-modeled-apis",
"model-editor-hide-modeled-methods",
);
break;
case "setModeledMethod": {
this.setModeledMethods(
msg.method.signature,
convertFromLegacyModeledMethod(msg.method),
);
break;
}
case "telemetry":
telemetryListener?.sendUIInteraction(msg.action);
break;
case "unhandledError":
void showAndLogExceptionWithTelemetry(
this.app.logger,
telemetryListener,
redactableError(
msg.error,
)`Unhandled error in model editor view: ${msg.error.message}`,
);
break;
default:
@@ -311,31 +339,52 @@ export class ModelEditorView extends AbstractWebview<
await Promise.all([
this.setViewState(),
withProgress((progress) => this.loadExternalApiUsages(progress), {
withProgress((progress) => this.loadMethods(progress), {
cancellable: false,
}),
this.loadExistingModeledMethods(),
]);
}
public get databaseUri(): string {
return this.databaseItem.databaseUri.toString();
}
public async focusView(): Promise<void> {
this.panel?.reveal();
}
public async revealMethod(method: Method): Promise<void> {
this.panel?.reveal();
await this.postMessage({
t: "revealMethod",
methodSignature: method.signature,
});
}
private async setViewState(): Promise<void> {
const showLlmButton =
this.databaseItem.language === "java" && showLlmGeneration();
this.databaseItem.language === "java" && this.modelConfig.llmGeneration;
const sourceArchiveAvailable =
this.databaseItem.hasSourceArchiveInExplorer();
await this.postMessage({
t: "setModelEditorViewState",
viewState: {
extensionPack: this.extensionPack,
showFlowGeneration: showFlowGeneration(),
showFlowGeneration: this.modelConfig.flowGeneration,
showLlmButton,
mode: this.mode,
showMultipleModels: this.modelConfig.showMultipleModels,
mode: this.modelingStore.getMode(this.databaseItem),
sourceArchiveAvailable,
},
});
}
protected async handleJumpToUsage(method: Method, usage: Usage) {
await this.showMethod(method, usage);
await showResolvableLocation(usage.url, this.databaseItem, this.app.logger);
protected async handleJumpToMethod(methodSignature: string) {
this.modelingStore.setSelectedMethod(this.databaseItem, methodSignature);
}
protected async loadExistingModeledMethods(): Promise<void> {
@@ -345,10 +394,7 @@ export class ModelEditorView extends AbstractWebview<
this.cliServer,
this.app.logger,
);
await this.postMessage({
t: "loadModeledMethods",
modeledMethods,
});
this.modelingStore.setModeledMethods(this.databaseItem, modeledMethods);
} catch (e: unknown) {
void showAndLogErrorMessage(
this.app.logger,
@@ -357,12 +403,12 @@ export class ModelEditorView extends AbstractWebview<
}
}
protected async loadExternalApiUsages(
progress: ProgressCallback,
): Promise<void> {
protected async loadMethods(progress: ProgressCallback): Promise<void> {
const mode = this.modelingStore.getMode(this.databaseItem);
try {
const cancellationTokenSource = new CancellationTokenSource();
const queryResult = await runExternalApiQueries(this.mode, {
const queryResult = await runExternalApiQueries(mode, {
cliServer: this.cliServer,
queryRunner: this.queryRunner,
databaseItem: this.databaseItem,
@@ -378,19 +424,8 @@ export class ModelEditorView extends AbstractWebview<
if (!queryResult) {
return;
}
this.methods = queryResult;
await this.postMessage({
t: "setMethods",
methods: this.methods,
});
if (this.isMostRecentlyActiveView(this)) {
await this.updateMethodsUsagePanelState(
this.methods,
this.databaseItem,
this.hideModeledApis,
);
}
this.modelingStore.setMethods(this.databaseItem, queryResult);
} catch (err) {
void showAndLogExceptionWithTelemetry(
this.app.logger,
@@ -407,11 +442,13 @@ export class ModelEditorView extends AbstractWebview<
async (progress) => {
const tokenSource = new CancellationTokenSource();
const mode = this.modelingStore.getMode(this.databaseItem);
let addedDatabase: DatabaseItem | undefined;
// In application mode, we need the database of a specific library to generate
// the modeled methods. In framework mode, we'll use the current database.
if (this.mode === Mode.Application) {
if (mode === Mode.Application) {
addedDatabase = await this.promptChooseNewOrExistingDatabase(
progress,
);
@@ -433,16 +470,19 @@ export class ModelEditorView extends AbstractWebview<
queryStorageDir: this.queryStorageDir,
databaseItem: addedDatabase ?? this.databaseItem,
onResults: async (modeledMethods) => {
const modeledMethodsByName: Record<string, ModeledMethod> = {};
const modeledMethodsByName: Record<string, ModeledMethod[]> = {};
for (const modeledMethod of modeledMethods) {
modeledMethodsByName[modeledMethod.signature] = modeledMethod;
if (!(modeledMethod.signature in modeledMethodsByName)) {
modeledMethodsByName[modeledMethod.signature] = [];
}
modeledMethodsByName[modeledMethod.signature].push(
modeledMethod,
);
}
await this.postMessage({
t: "addModeledMethods",
modeledMethods: modeledMethodsByName,
});
this.addModeledMethods(modeledMethodsByName);
},
progress,
token: tokenSource.token,
@@ -463,14 +503,22 @@ export class ModelEditorView extends AbstractWebview<
private async generateModeledMethodsFromLlm(
packageName: string,
methods: Method[],
modeledMethods: Record<string, ModeledMethod>,
methodSignatures: string[],
): Promise<void> {
const methods = this.modelingStore.getMethods(
this.databaseItem,
methodSignatures,
);
const modeledMethods = this.modelingStore.getModeledMethods(
this.databaseItem,
methodSignatures,
);
const mode = this.modelingStore.getMode(this.databaseItem);
await this.autoModeler.startModeling(
packageName,
methods,
modeledMethods,
this.mode,
mode,
);
}
@@ -483,9 +531,19 @@ export class ModelEditorView extends AbstractWebview<
return;
}
let existingView = this.viewTracker.getView(
addedDatabase.databaseUri.toString(),
);
if (existingView) {
await existingView.focusView();
return;
}
const modelFile = await pickExtensionPack(
this.cliServer,
addedDatabase,
this.modelConfig,
this.app.logger,
progress,
3,
@@ -494,9 +552,22 @@ export class ModelEditorView extends AbstractWebview<
return;
}
// Check again just before opening the editor to ensure no model editor has been opened between
// our first check and now.
existingView = this.viewTracker.getView(
addedDatabase.databaseUri.toString(),
);
if (existingView) {
await existingView.focusView();
return;
}
const view = new ModelEditorView(
this.ctx,
this.app,
this.modelingStore,
this.viewTracker,
this.modelConfig,
this.databaseManager,
this.cliServer,
this.queryRunner,
@@ -505,11 +576,6 @@ export class ModelEditorView extends AbstractWebview<
addedDatabase,
modelFile,
Mode.Framework,
this.updateMethodsUsagePanelState,
this.showMethod,
this.handleViewBecameActive,
this.handleViewWasDisposed,
this.isMostRecentlyActiveView,
);
await view.openView();
});
@@ -578,6 +644,7 @@ export class ModelEditorView extends AbstractWebview<
this.cliServer,
this.databaseItem.language,
makeSelected,
false,
);
if (!addedDatabase) {
void this.app.logger.log("No database chosen");
@@ -586,4 +653,65 @@ export class ModelEditorView extends AbstractWebview<
return addedDatabase;
}
private registerToModelingStoreEvents() {
this.push(
this.modelingStore.onMethodsChanged(async (event) => {
if (event.dbUri === this.databaseItem.databaseUri.toString()) {
await this.postMessage({
t: "setMethods",
methods: event.methods,
});
}
}),
);
this.push(
this.modelingStore.onModeledMethodsChanged(async (event) => {
if (event.dbUri === this.databaseItem.databaseUri.toString()) {
await this.postMessage({
t: "setModeledMethods",
methods: event.modeledMethods,
});
}
}),
);
this.push(
this.modelingStore.onModifiedMethodsChanged(async (event) => {
if (event.dbUri === this.databaseItem.databaseUri.toString()) {
await this.postMessage({
t: "setModifiedMethods",
methodSignatures: [...event.modifiedMethods],
});
}
}),
);
}
private registerToModelConfigEvents() {
this.push(
this.modelConfig.onDidChangeConfiguration(() => {
void this.setViewState();
}),
);
}
private addModeledMethods(modeledMethods: Record<string, ModeledMethod[]>) {
this.modelingStore.addModeledMethods(this.databaseItem, modeledMethods);
this.modelingStore.addModifiedMethods(
this.databaseItem,
new Set(Object.keys(modeledMethods)),
);
}
private setModeledMethods(signature: string, methods: ModeledMethod[]) {
this.modelingStore.updateModeledMethods(
this.databaseItem,
signature,
methods,
);
this.modelingStore.addModifiedMethod(this.databaseItem, signature);
}
}

View File

@@ -0,0 +1,45 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/ModelExtensionFile",
"definitions": {
"ModelExtensionFile": {
"type": "object",
"properties": {
"extensions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"addsTo": {
"type": "object",
"properties": {
"pack": {
"type": "string"
},
"extensible": {
"type": "string"
}
},
"required": ["pack", "extensible"]
},
"data": {
"type": "array",
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/DataTuple"
}
}
}
},
"required": ["addsTo", "data"]
}
}
},
"required": ["extensions"]
},
"DataTuple": {
"type": ["boolean", "number", "string"]
}
}
}

View File

@@ -0,0 +1,17 @@
type ExtensibleReference = {
pack: string;
extensible: string;
};
export type DataTuple = boolean | number | string;
type DataRow = DataTuple[];
type ModelExtension = {
addsTo: ExtensibleReference;
data: DataRow[];
};
export type ModelExtensionFile = {
extensions: ModelExtension[];
};

View File

@@ -15,7 +15,7 @@ export async function saveModeledMethods(
extensionPack: ExtensionPack,
language: string,
methods: Method[],
modeledMethods: Record<string, ModeledMethod>,
modeledMethods: Record<string, ModeledMethod[]>,
mode: Mode,
cliServer: CodeQLCliServer,
logger: NotificationLogger,
@@ -45,12 +45,12 @@ async function loadModeledMethodFiles(
extensionPack: ExtensionPack,
cliServer: CodeQLCliServer,
logger: NotificationLogger,
): Promise<Record<string, Record<string, ModeledMethod>>> {
): Promise<Record<string, Record<string, ModeledMethod[]>>> {
const modelFiles = await listModelFiles(extensionPack.path, cliServer);
const modeledMethodsByFile: Record<
string,
Record<string, ModeledMethod>
Record<string, ModeledMethod[]>
> = {};
for (const modelFile of modelFiles) {
@@ -78,8 +78,8 @@ export async function loadModeledMethods(
extensionPack: ExtensionPack,
cliServer: CodeQLCliServer,
logger: NotificationLogger,
): Promise<Record<string, ModeledMethod>> {
const existingModeledMethods: Record<string, ModeledMethod> = {};
): Promise<Record<string, ModeledMethod[]>> {
const existingModeledMethods: Record<string, ModeledMethod[]> = {};
const modeledMethodsByFile = await loadModeledMethodFiles(
extensionPack,
@@ -88,7 +88,11 @@ export async function loadModeledMethods(
);
for (const modeledMethods of Object.values(modeledMethodsByFile)) {
for (const [key, value] of Object.entries(modeledMethods)) {
existingModeledMethods[key] = value;
if (!(key in existingModeledMethods)) {
existingModeledMethods[key] = [];
}
existingModeledMethods[key].push(...value);
}
}

View File

@@ -23,6 +23,8 @@ export interface ModeledMethod extends MethodSignature {
type: ModeledMethodType;
input: string;
output: string;
kind: string;
kind: ModeledMethodKind;
provenance: Provenance;
}
export type ModeledMethodKind = string;

View File

@@ -0,0 +1,433 @@
import { App } from "../common/app";
import { DisposableObject } from "../common/disposable-object";
import { AppEvent, AppEventEmitter } from "../common/events";
import { DatabaseItem } from "../databases/local-databases";
import { Method, Usage } from "./method";
import { ModeledMethod } from "./modeled-method";
import { INITIAL_HIDE_MODELED_METHODS_VALUE } from "./shared/hide-modeled-methods";
import { INITIAL_MODE, Mode } from "./shared/mode";
interface DbModelingState {
databaseItem: DatabaseItem;
methods: Method[];
hideModeledMethods: boolean;
mode: Mode;
modeledMethods: Record<string, ModeledMethod[]>;
modifiedMethodSignatures: Set<string>;
selectedMethod: Method | undefined;
selectedUsage: Usage | undefined;
}
interface MethodsChangedEvent {
methods: Method[];
dbUri: string;
isActiveDb: boolean;
}
interface HideModeledMethodsChangedEvent {
hideModeledMethods: boolean;
isActiveDb: boolean;
}
interface ModeChangedEvent {
mode: Mode;
isActiveDb: boolean;
}
interface ModeledMethodsChangedEvent {
modeledMethods: Record<string, ModeledMethod[]>;
dbUri: string;
isActiveDb: boolean;
}
interface ModifiedMethodsChangedEvent {
modifiedMethods: Set<string>;
dbUri: string;
isActiveDb: boolean;
}
interface SelectedMethodChangedEvent {
databaseItem: DatabaseItem;
method: Method;
usage: Usage;
modeledMethods: ModeledMethod[];
isModified: boolean;
}
export class ModelingStore extends DisposableObject {
public readonly onActiveDbChanged: AppEvent<void>;
public readonly onDbOpened: AppEvent<string>;
public readonly onDbClosed: AppEvent<string>;
public readonly onMethodsChanged: AppEvent<MethodsChangedEvent>;
public readonly onHideModeledMethodsChanged: AppEvent<HideModeledMethodsChangedEvent>;
public readonly onModeChanged: AppEvent<ModeChangedEvent>;
public readonly onModeledMethodsChanged: AppEvent<ModeledMethodsChangedEvent>;
public readonly onModifiedMethodsChanged: AppEvent<ModifiedMethodsChangedEvent>;
public readonly onSelectedMethodChanged: AppEvent<SelectedMethodChangedEvent>;
private readonly state: Map<string, DbModelingState>;
private activeDb: string | undefined;
private readonly onActiveDbChangedEventEmitter: AppEventEmitter<void>;
private readonly onDbOpenedEventEmitter: AppEventEmitter<string>;
private readonly onDbClosedEventEmitter: AppEventEmitter<string>;
private readonly onMethodsChangedEventEmitter: AppEventEmitter<MethodsChangedEvent>;
private readonly onHideModeledMethodsChangedEventEmitter: AppEventEmitter<HideModeledMethodsChangedEvent>;
private readonly onModeChangedEventEmitter: AppEventEmitter<ModeChangedEvent>;
private readonly onModeledMethodsChangedEventEmitter: AppEventEmitter<ModeledMethodsChangedEvent>;
private readonly onModifiedMethodsChangedEventEmitter: AppEventEmitter<ModifiedMethodsChangedEvent>;
private readonly onSelectedMethodChangedEventEmitter: AppEventEmitter<SelectedMethodChangedEvent>;
constructor(app: App) {
super();
// State initialization
this.state = new Map<string, DbModelingState>();
// Event initialization
this.onActiveDbChangedEventEmitter = this.push(
app.createEventEmitter<void>(),
);
this.onActiveDbChanged = this.onActiveDbChangedEventEmitter.event;
this.onDbOpenedEventEmitter = this.push(app.createEventEmitter<string>());
this.onDbOpened = this.onDbOpenedEventEmitter.event;
this.onDbClosedEventEmitter = this.push(app.createEventEmitter<string>());
this.onDbClosed = this.onDbClosedEventEmitter.event;
this.onMethodsChangedEventEmitter = this.push(
app.createEventEmitter<MethodsChangedEvent>(),
);
this.onMethodsChanged = this.onMethodsChangedEventEmitter.event;
this.onHideModeledMethodsChangedEventEmitter = this.push(
app.createEventEmitter<HideModeledMethodsChangedEvent>(),
);
this.onHideModeledMethodsChanged =
this.onHideModeledMethodsChangedEventEmitter.event;
this.onModeChangedEventEmitter = this.push(
app.createEventEmitter<ModeChangedEvent>(),
);
this.onModeChanged = this.onModeChangedEventEmitter.event;
this.onModeledMethodsChangedEventEmitter = this.push(
app.createEventEmitter<ModeledMethodsChangedEvent>(),
);
this.onModeledMethodsChanged =
this.onModeledMethodsChangedEventEmitter.event;
this.onModifiedMethodsChangedEventEmitter = this.push(
app.createEventEmitter<ModifiedMethodsChangedEvent>(),
);
this.onModifiedMethodsChanged =
this.onModifiedMethodsChangedEventEmitter.event;
this.onSelectedMethodChangedEventEmitter = this.push(
app.createEventEmitter<SelectedMethodChangedEvent>(),
);
this.onSelectedMethodChanged =
this.onSelectedMethodChangedEventEmitter.event;
}
public initializeStateForDb(
databaseItem: DatabaseItem,
mode: Mode = INITIAL_MODE,
) {
const dbUri = databaseItem.databaseUri.toString();
this.state.set(dbUri, {
databaseItem,
methods: [],
hideModeledMethods: INITIAL_HIDE_MODELED_METHODS_VALUE,
mode,
modeledMethods: {},
modifiedMethodSignatures: new Set(),
selectedMethod: undefined,
selectedUsage: undefined,
});
this.onDbOpenedEventEmitter.fire(dbUri);
}
public setActiveDb(databaseItem: DatabaseItem) {
this.activeDb = databaseItem.databaseUri.toString();
this.onActiveDbChangedEventEmitter.fire();
}
public removeDb(databaseItem: DatabaseItem) {
const dbUri = databaseItem.databaseUri.toString();
if (!this.state.has(dbUri)) {
throw Error("Cannot remove a database that has not been initialized");
}
if (this.activeDb === dbUri) {
this.activeDb = undefined;
this.onActiveDbChangedEventEmitter.fire();
}
this.state.delete(dbUri);
this.onDbClosedEventEmitter.fire(dbUri);
}
public getStateForActiveDb(): DbModelingState | undefined {
if (!this.activeDb) {
return undefined;
}
return this.state.get(this.activeDb);
}
public hasStateForActiveDb(): boolean {
return !!this.getStateForActiveDb();
}
public anyDbsBeingModeled(): boolean {
return this.state.size > 0;
}
/**
* Returns the methods for the given database item and method signatures.
* If the `methodSignatures` argument is not provided or is undefined, returns all methods.
*/
public getMethods(
dbItem: DatabaseItem,
methodSignatures?: string[],
): Method[] {
const methods = this.getState(dbItem).methods;
if (!methodSignatures) {
return methods;
}
return methods.filter((method) =>
methodSignatures.includes(method.signature),
);
}
public setMethods(dbItem: DatabaseItem, methods: Method[]) {
const dbState = this.getState(dbItem);
const dbUri = dbItem.databaseUri.toString();
dbState.methods = [...methods];
this.onMethodsChangedEventEmitter.fire({
methods,
dbUri,
isActiveDb: dbUri === this.activeDb,
});
}
public setHideModeledMethods(
dbItem: DatabaseItem,
hideModeledMethods: boolean,
) {
const dbState = this.getState(dbItem);
const dbUri = dbItem.databaseUri.toString();
dbState.hideModeledMethods = hideModeledMethods;
this.onHideModeledMethodsChangedEventEmitter.fire({
hideModeledMethods,
isActiveDb: dbUri === this.activeDb,
});
}
public setMode(dbItem: DatabaseItem, mode: Mode) {
const dbState = this.getState(dbItem);
const dbUri = dbItem.databaseUri.toString();
dbState.mode = mode;
this.onModeChangedEventEmitter.fire({
mode,
isActiveDb: dbUri === this.activeDb,
});
}
public getMode(dbItem: DatabaseItem) {
return this.getState(dbItem).mode;
}
/**
* Returns the modeled methods for the given database item and method signatures.
* If the `methodSignatures` argument is not provided or is undefined, returns all modeled methods.
*/
public getModeledMethods(
dbItem: DatabaseItem,
methodSignatures?: string[],
): Record<string, ModeledMethod[]> {
const modeledMethods = this.getState(dbItem).modeledMethods;
if (!methodSignatures) {
return modeledMethods;
}
return Object.fromEntries(
Object.entries(modeledMethods).filter(([key]) =>
methodSignatures.includes(key),
),
);
}
public addModeledMethods(
dbItem: DatabaseItem,
methods: Record<string, ModeledMethod[]>,
) {
this.changeModeledMethods(dbItem, (state) => {
const newModeledMethods = {
...methods,
// Keep all methods that are already modeled in some form in the state
...Object.fromEntries(
Object.entries(state.modeledMethods).filter(([_, value]) =>
value.some((m) => m.type !== "none"),
),
),
};
state.modeledMethods = newModeledMethods;
});
}
public setModeledMethods(
dbItem: DatabaseItem,
methods: Record<string, ModeledMethod[]>,
) {
this.changeModeledMethods(dbItem, (state) => {
state.modeledMethods = { ...methods };
});
}
public updateModeledMethods(
dbItem: DatabaseItem,
signature: string,
modeledMethods: ModeledMethod[],
) {
this.changeModeledMethods(dbItem, (state) => {
const newModeledMethods = { ...state.modeledMethods };
newModeledMethods[signature] = modeledMethods;
state.modeledMethods = newModeledMethods;
});
}
public setModifiedMethods(
dbItem: DatabaseItem,
methodSignatures: Set<string>,
) {
this.changeModifiedMethods(dbItem, (state) => {
state.modifiedMethodSignatures = new Set(methodSignatures);
});
}
public addModifiedMethods(
dbItem: DatabaseItem,
methodSignatures: Iterable<string>,
) {
this.changeModifiedMethods(dbItem, (state) => {
const newModifiedMethods = new Set([
...state.modifiedMethodSignatures,
...methodSignatures,
]);
state.modifiedMethodSignatures = newModifiedMethods;
});
}
public addModifiedMethod(dbItem: DatabaseItem, methodSignature: string) {
this.addModifiedMethods(dbItem, [methodSignature]);
}
public removeModifiedMethods(
dbItem: DatabaseItem,
methodSignatures: string[],
) {
this.changeModifiedMethods(dbItem, (state) => {
const newModifiedMethods = Array.from(
state.modifiedMethodSignatures,
).filter((s) => !methodSignatures.includes(s));
state.modifiedMethodSignatures = new Set(newModifiedMethods);
});
}
public setSelectedMethod(dbItem: DatabaseItem, methodSignature: string) {
const dbState = this.getState(dbItem);
const method = dbState.methods.find((m) => m.signature === methodSignature);
if (method === undefined) {
throw new Error(
`No method with signature "${methodSignature}" found in modeling store`,
);
}
const usage = method.usages[0];
dbState.selectedMethod = method;
dbState.selectedUsage = usage;
this.onSelectedMethodChangedEventEmitter.fire({
databaseItem: dbItem,
method,
usage,
modeledMethods: dbState.modeledMethods[method.signature] ?? [],
isModified: dbState.modifiedMethodSignatures.has(method.signature),
});
}
public getSelectedMethodDetails() {
const dbState = this.getStateForActiveDb();
if (!dbState) {
throw new Error("No active state found in modeling store");
}
const selectedMethod = dbState.selectedMethod;
if (!selectedMethod) {
return undefined;
}
return {
databaseItem: dbState.databaseItem,
method: selectedMethod,
usage: dbState.selectedUsage,
modeledMethods: dbState.modeledMethods[selectedMethod.signature] ?? [],
isModified: dbState.modifiedMethodSignatures.has(
selectedMethod.signature,
),
};
}
private getState(databaseItem: DatabaseItem): DbModelingState {
if (!this.state.has(databaseItem.databaseUri.toString())) {
throw Error(
"Cannot get state for a database that has not been initialized",
);
}
return this.state.get(databaseItem.databaseUri.toString())!;
}
private changeModifiedMethods(
dbItem: DatabaseItem,
updateState: (state: DbModelingState) => void,
) {
const state = this.getState(dbItem);
updateState(state);
this.onModifiedMethodsChangedEventEmitter.fire({
modifiedMethods: state.modifiedMethodSignatures,
dbUri: dbItem.databaseUri.toString(),
isActiveDb: dbItem.databaseUri.toString() === this.activeDb,
});
}
private changeModeledMethods(
dbItem: DatabaseItem,
updateState: (state: DbModelingState) => void,
) {
const state = this.getState(dbItem);
updateState(state);
this.onModeledMethodsChangedEventEmitter.fire({
modeledMethods: state.modeledMethods,
dbUri: dbItem.databaseUri.toString(),
isActiveDb: dbItem.databaseUri.toString() === this.activeDb,
});
}
}

View File

@@ -1,16 +1,15 @@
import { ModeledMethod, ModeledMethodType, Provenance } from "./modeled-method";
import { DataTuple } from "./model-extension-file";
export type ExtensiblePredicateDefinition = {
extensiblePredicate: string;
generateMethodDefinition: (method: ModeledMethod) => Tuple[];
readModeledMethod: (row: Tuple[]) => ModeledMethod;
generateMethodDefinition: (method: ModeledMethod) => DataTuple[];
readModeledMethod: (row: DataTuple[]) => ModeledMethod;
supportedKinds?: string[];
};
type Tuple = boolean | number | string;
function readRowToMethod(row: Tuple[]): string {
function readRowToMethod(row: DataTuple[]): string {
return `${row[0]}.${row[1]}#${row[3]}${row[4]}`;
}

View File

@@ -2,130 +2,151 @@ import { Query } from "./query";
export const fetchExternalApisQuery: Query = {
applicationModeQuery: `/**
* @name Usage of APIs coming from external libraries
* @description A list of 3rd party APIs used in the codebase.
* @tags telemetry
* @kind problem
* @id cs/telemetry/fetch-external-apis
* @name Fetch endpoints for use in the model editor (application mode)
* @description A list of 3rd party endpoints (methods and attributes) used in the codebase. Excludes test and generated code.
* @kind table
* @id csharp/utils/modeleditor/application-mode-endpoints
* @tags modeleditor endpoints application-mode
*/
private import csharp
private import AutomodelVsCode
import csharp
import ApplicationModeEndpointsQuery
import ModelEditor
class ExternalApi extends CallableMethod {
ExternalApi() {
this.isUnboundDeclaration() and
this.fromLibrary() and
this.(Modifiable).isEffectivelyPublic()
}
}
private Call aUsage(ExternalEndpoint api) { result.getTarget().getUnboundDeclaration() = api }
private Call aUsage(ExternalApi api) { result.getTarget().getUnboundDeclaration() = api }
from
ExternalApi api, string apiName, boolean supported, Call usage, string type, string classification
from ExternalEndpoint endpoint, boolean supported, Call usage, string type, string classification
where
apiName = api.getApiName() and
supported = isSupported(api) and
usage = aUsage(api) and
type = supportedType(api) and
supported = isSupported(endpoint) and
usage = aUsage(endpoint) and
type = supportedType(endpoint) and
classification = methodClassification(usage)
select usage, apiName, supported.toString(), "supported", api.dllName(), api.dllVersion(), type,
"type", classification, "classification"
select usage, endpoint.getNamespace(), endpoint.getTypeName(), endpoint.getName(),
endpoint.getParameterTypes(), supported, endpoint.dllName(), endpoint.dllVersion(), type,
classification
`,
frameworkModeQuery: `/**
* @name Public methods
* @description A list of APIs callable by consumers. Excludes test and generated code.
* @tags telemetry
* @kind problem
* @id cs/telemetry/fetch-public-methods
* @name Fetch endpoints for use in the model editor (framework mode)
* @description A list of endpoints accessible (methods and attributes) for consumers of the library. Excludes test and generated code.
* @kind table
* @id csharp/utils/modeleditor/framework-mode-endpoints
* @tags modeleditor endpoints framework-mode
*/
private import csharp
private import dotnet
private import semmle.code.csharp.frameworks.Test
private import AutomodelVsCode
import csharp
import FrameworkModeEndpointsQuery
import ModelEditor
class PublicMethod extends CallableMethod {
PublicMethod() { this.fromSource() and not this.getFile() instanceof TestFile }
}
from PublicMethod publicMethod, string apiName, boolean supported, string type
from PublicEndpointFromSource endpoint, boolean supported, string type
where
apiName = publicMethod.getApiName() and
supported = isSupported(publicMethod) and
type = supportedType(publicMethod)
select publicMethod, apiName, supported.toString(), "supported",
publicMethod.getFile().getBaseName(), "library", type, "type", "unknown", "classification"
supported = isSupported(endpoint) and
type = supportedType(endpoint)
select endpoint, endpoint.getNamespace(), endpoint.getTypeName(), endpoint.getName(),
endpoint.getParameterTypes(), supported, endpoint.getFile().getBaseName(), type
`,
dependencies: {
"AutomodelVsCode.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */
private import csharp
private import dotnet
private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.dataflow.ExternalFlow
private import semmle.code.csharp.dataflow.FlowSummary
private import semmle.code.csharp.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
"ApplicationModeEndpointsQuery.qll": `private import csharp
private import semmle.code.csharp.dataflow.ExternalFlow as ExternalFlow
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate
private import semmle.code.csharp.frameworks.Test
private import semmle.code.csharp.security.dataflow.flowsources.Remote
pragma[nomagic]
private predicate isTestNamespace(Namespace ns) {
ns.getFullName()
.matches([
"NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%"
])
}
private import ModelEditor
/**
* A test library.
* A class of effectively public callables in library code.
*/
class TestLibrary extends RefType {
TestLibrary() { isTestNamespace(this.getNamespace()) }
class ExternalEndpoint extends Endpoint {
ExternalEndpoint() { this.fromLibrary() }
/** Gets a node that is an input to a call to this API. */
private ArgumentNode getAnInput() {
result
.getCall()
.(DataFlowDispatch::NonDelegateDataFlowCall)
.getATarget(_)
.getUnboundDeclaration() = this
}
/** Gets a node that is an output from a call to this API. */
private DataFlow::Node getAnOutput() {
exists(Call c, DataFlowDispatch::NonDelegateDataFlowCall dc |
dc.getDispatchCall().getCall() = c and
c.getTarget().getUnboundDeclaration() = this
|
result = DataFlowDispatch::getAnOutNode(dc, _)
)
}
override predicate hasSummary() {
Endpoint.super.hasSummary()
or
defaultAdditionalTaintStep(this.getAnInput(), _)
}
override predicate isSource() {
this.getAnOutput() instanceof RemoteFlowSource or ExternalFlow::sourceNode(this.getAnOutput(), _)
}
override predicate isSink() { ExternalFlow::sinkNode(this.getAnInput(), _) }
}
`,
"FrameworkModeEndpointsQuery.qll": `private import csharp
private import semmle.code.csharp.frameworks.Test
private import ModelEditor
/**
* A class of effectively public callables from source code.
*/
class PublicEndpointFromSource extends Endpoint {
PublicEndpointFromSource() { this.fromSource() and not this.getFile() instanceof TestFile }
override predicate isSource() { this instanceof SourceCallable }
override predicate isSink() { this instanceof SinkCallable }
}`,
"ModelEditor.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */
private import csharp
private import semmle.code.csharp.dataflow.FlowSummary
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.frameworks.Test
/** Holds if the given callable is not worth supporting. */
private predicate isUninteresting(DotNet::Declaration c) {
private predicate isUninteresting(Callable c) {
c.getDeclaringType() instanceof TestLibrary or
c.(Constructor).isParameterless() or
c.getDeclaringType() instanceof AnonymousClass
}
/**
* An callable method from either the C# Standard Library, a 3rd party library, or from the source.
* A callable method or accessor from either the C# Standard Library, a 3rd party library, or from the source.
*/
class CallableMethod extends DotNet::Declaration {
CallableMethod() {
this.(Modifiable).isEffectivelyPublic() and
not isUninteresting(this)
class Endpoint extends Callable {
Endpoint() {
[this.(Modifiable), this.(Accessor).getDeclaration()].isEffectivelyPublic() and
not isUninteresting(this) and
this.isUnboundDeclaration()
}
/**
* Gets the unbound type, name and parameter types of this API.
*/
bindingset[this]
private string getSignature() {
result =
nestedName(this.getDeclaringType().getUnboundDeclaration()) + "#" + this.getName() + "(" +
parameterQualifiedTypeNamesToString(this) + ")"
}
/**
* Gets the namespace of this API.
* Gets the namespace of this endpoint.
*/
bindingset[this]
string getNamespace() { this.getDeclaringType().hasQualifiedName(result, _) }
/**
* Gets the namespace and signature of this API.
* Gets the unbound type name of this endpoint.
*/
bindingset[this]
string getApiName() { result = this.getNamespace() + "." + this.getSignature() }
string getTypeName() { result = nestedName(this.getDeclaringType().getUnboundDeclaration()) }
/**
* Gets the parameter types of this endpoint.
*/
bindingset[this]
string getParameterTypes() { result = "(" + parameterQualifiedTypeNamesToString(this) + ")" }
private string getDllName() { result = this.getLocation().(Assembly).getName() }
@@ -143,44 +164,17 @@ class CallableMethod extends DotNet::Declaration {
not exists(this.getDllVersion()) and result = ""
}
/** Gets a node that is an input to a call to this API. */
private ArgumentNode getAnInput() {
result
.getCall()
.(DataFlowDispatch::NonDelegateDataFlowCall)
.getATarget(_)
.getUnboundDeclaration() = this
}
/** Gets a node that is an output from a call to this API. */
private DataFlow::Node getAnOutput() {
exists(
Call c, DataFlowDispatch::NonDelegateDataFlowCall dc, DataFlowImplCommon::ReturnKindExt ret
|
dc.getDispatchCall().getCall() = c and
c.getTarget().getUnboundDeclaration() = this
|
result = ret.getAnOutNode(dc)
)
}
/** Holds if this API has a supported summary. */
pragma[nomagic]
predicate hasSummary() {
this instanceof SummarizedCallable
or
defaultAdditionalTaintStep(this.getAnInput(), _)
}
predicate hasSummary() { this instanceof SummarizedCallable }
/** Holds if this API is a known source. */
pragma[nomagic]
predicate isSource() {
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
}
abstract predicate isSource();
/** Holds if this API is a known sink. */
pragma[nomagic]
predicate isSink() { sinkNode(this.getAnInput(), _) }
abstract predicate isSink();
/** Holds if this API is a known neutral. */
pragma[nomagic]
@@ -195,23 +189,20 @@ class CallableMethod extends DotNet::Declaration {
}
}
boolean isSupported(CallableMethod callableMethod) {
callableMethod.isSupported() and result = true
or
not callableMethod.isSupported() and
result = false
boolean isSupported(Endpoint endpoint) {
if endpoint.isSupported() then result = true else result = false
}
string supportedType(CallableMethod method) {
method.isSink() and result = "sink"
string supportedType(Endpoint endpoint) {
endpoint.isSink() and result = "sink"
or
method.isSource() and result = "source"
endpoint.isSource() and result = "source"
or
method.hasSummary() and result = "summary"
endpoint.hasSummary() and result = "summary"
or
method.isNeutral() and result = "neutral"
endpoint.isNeutral() and result = "neutral"
or
not method.isSupported() and result = ""
not endpoint.isSupported() and result = ""
}
string methodClassification(Call method) {
@@ -222,18 +213,51 @@ string methodClassification(Call method) {
}
/**
* Gets the nested name of the declaration.
* Gets the nested name of the type \`t\`.
*
* If the declaration is not a nested type, the result is the same as \`getName()\`.
* If the type is not a nested type, the result is the same as \`getName()\`.
* Otherwise the name of the nested type is prefixed with a \`+\` and appended to
* the name of the enclosing type, which might be a nested type as well.
*/
private string nestedName(Declaration declaration) {
not exists(declaration.getDeclaringType().getUnboundDeclaration()) and
result = declaration.getName()
private string nestedName(Type t) {
not exists(t.getDeclaringType().getUnboundDeclaration()) and
result = t.getName()
or
nestedName(declaration.getDeclaringType().getUnboundDeclaration()) + "+" + declaration.getName() =
result
nestedName(t.getDeclaringType().getUnboundDeclaration()) + "+" + t.getName() = result
}
// Temporary copy of csharp/ql/src/Telemetry/TestLibrary.qll
pragma[nomagic]
private predicate isTestNamespace(Namespace ns) {
ns.getFullName()
.matches([
"NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%"
])
}
/**
* A test library.
*/
class TestLibrary extends RefType {
TestLibrary() { isTestNamespace(this.getNamespace()) }
}
// Temporary copy of csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll
private import semmle.code.csharp.dataflow.internal.FlowSummaryImplSpecific
/**
* A callable where there exists a MaD sink model that applies to it.
*/
class SinkCallable extends Callable {
SinkCallable() { sinkElement(this, _, _, _) }
}
/**
* A callable where there exists a MaD source model that applies to it.
*/
class SourceCallable extends Callable {
SourceCallable() { sourceElement(this, _, _, _) }
}
`,
},

View File

@@ -2,66 +2,113 @@ import { Query } from "./query";
export const fetchExternalApisQuery: Query = {
applicationModeQuery: `/**
* @name Usage of APIs coming from external libraries
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
* @tags telemetry
* @kind problem
* @id java/telemetry/fetch-external-apis
* @name Fetch endpoints for use in the model editor (application mode)
* @description A list of 3rd party endpoints (methods) used in the codebase. Excludes test and generated code.
* @kind table
* @id java/utils/modeleditor/application-mode-endpoints
* @tags modeleditor endpoints application-mode
*/
import java
import AutomodelVsCode
class ExternalApi extends CallableMethod {
ExternalApi() { not this.fromSource() }
}
private Call aUsage(ExternalApi api) { result.getCallee().getSourceDeclaration() = api }
from
ExternalApi externalApi, string apiName, boolean supported, Call usage, string type,
string classification
where
apiName = externalApi.getApiName() and
supported = isSupported(externalApi) and
usage = aUsage(externalApi) and
type = supportedType(externalApi) and
classification = methodClassification(usage)
select usage, apiName, supported.toString(), "supported", externalApi.jarContainer(),
externalApi.jarVersion(), type, "type", classification, "classification"
`,
frameworkModeQuery: `/**
* @name Public methods
* @description A list of APIs callable by consumers. Excludes test and generated code.
* @tags telemetry
* @kind problem
* @id java/telemetry/fetch-public-methods
*/
import java
import AutomodelVsCode
class PublicMethodFromSource extends CallableMethod, ModelApi { }
from PublicMethodFromSource publicMethod, string apiName, boolean supported, string type
where
apiName = publicMethod.getApiName() and
supported = isSupported(publicMethod) and
type = supportedType(publicMethod)
select publicMethod, apiName, supported.toString(), "supported",
publicMethod.getCompilationUnit().getParentContainer().getBaseName(), "library", type, "type",
"unknown", "classification"
`,
dependencies: {
"AutomodelVsCode.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */
private import java
private import semmle.code.java.dataflow.DataFlow
private import ApplicationModeEndpointsQuery
private import ModelEditor
private Call aUsage(ExternalEndpoint endpoint) {
result.getCallee().getSourceDeclaration() = endpoint
}
from ExternalEndpoint endpoint, boolean supported, Call usage, string type, string classification
where
supported = isSupported(endpoint) and
usage = aUsage(endpoint) and
type = supportedType(endpoint) and
classification = usageClassification(usage)
select usage, endpoint.getPackageName(), endpoint.getTypeName(), endpoint.getName(),
endpoint.getParameterTypes(), supported, endpoint.jarContainer(), endpoint.jarVersion(), type,
classification
`,
frameworkModeQuery: `/**
* @name Fetch endpoints for use in the model editor (framework mode)
* @description A list of endpoints accessible (methods) for consumers of the library. Excludes test and generated code.
* @kind table
* @id java/utils/modeleditor/framework-mode-endpoints
* @tags modeleditor endpoints framework-mode
*/
private import java
private import FrameworkModeEndpointsQuery
private import ModelEditor
from PublicEndpointFromSource endpoint, boolean supported, string type
where
supported = isSupported(endpoint) and
type = supportedType(endpoint)
select endpoint, endpoint.getPackageName(), endpoint.getTypeName(), endpoint.getName(),
endpoint.getParameterTypes(), supported,
endpoint.getCompilationUnit().getParentContainer().getBaseName(), type
`,
dependencies: {
"ApplicationModeEndpointsQuery.qll": `private import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dataflow.internal.DataFlowPrivate
private import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import ModelEditor
/**
* A class of effectively public callables in library code.
*/
class ExternalEndpoint extends Endpoint {
ExternalEndpoint() { not this.fromSource() }
/** Gets a node that is an input to a call to this API. */
private DataFlow::Node getAnInput() {
exists(Call call | call.getCallee().getSourceDeclaration() = this |
result.asExpr().(Argument).getCall() = call or
result.(ArgumentNode).getCall().asCall() = call
)
}
/** Gets a node that is an output from a call to this API. */
private DataFlow::Node getAnOutput() {
exists(Call call | call.getCallee().getSourceDeclaration() = this |
result.asExpr() = call or
result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call
)
}
override predicate hasSummary() {
Endpoint.super.hasSummary()
or
TaintTracking::localAdditionalTaintStep(this.getAnInput(), _)
}
override predicate isSource() {
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
}
override predicate isSink() { sinkNode(this.getAnInput(), _) }
}
`,
"FrameworkModeEndpointsQuery.qll": `private import java
private import semmle.code.java.dataflow.internal.DataFlowPrivate
private import semmle.code.java.dataflow.internal.FlowSummaryImplSpecific
private import semmle.code.java.dataflow.internal.ModelExclusions
private import ModelEditor
/**
* A class of effectively public callables from source code.
*/
class PublicEndpointFromSource extends Endpoint, ModelApi {
override predicate isSource() { sourceElement(this, _, _, _) }
override predicate isSink() { sinkElement(this, _, _, _) }
}
`,
"ModelEditor.qll": `/** Provides classes and predicates related to handling APIs for the VS Code extension. */
private import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dataflow.TaintTracking
private import semmle.code.java.dataflow.internal.ModelExclusions
@@ -75,17 +122,23 @@ private predicate isUninteresting(Callable c) {
/**
* A callable method from either the Standard Library, a 3rd party library or from the source.
*/
class CallableMethod extends Callable {
CallableMethod() { not isUninteresting(this) }
class Endpoint extends Callable {
Endpoint() { not isUninteresting(this) }
/**
* Gets information about the external API in the form expected by the MaD modeling framework.
* Gets the package name of this endpoint.
*/
string getApiName() {
result =
this.getDeclaringType().getPackage() + "." + this.getDeclaringType().nestedName() + "#" +
this.getName() + paramsString(this)
}
string getPackageName() { result = this.getDeclaringType().getPackage().getName() }
/**
* Gets the type name of this endpoint.
*/
string getTypeName() { result = this.getDeclaringType().nestedName() }
/**
* Gets the parameter types of this endpoint.
*/
string getParameterTypes() { result = paramsString(this) }
private string getJarName() {
result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName()
@@ -113,43 +166,23 @@ class CallableMethod extends Callable {
not exists(this.getJarVersion()) and result = ""
}
/** Gets a node that is an input to a call to this API. */
private DataFlow::Node getAnInput() {
exists(Call call | call.getCallee().getSourceDeclaration() = this |
result.asExpr().(Argument).getCall() = call or
result.(ArgumentNode).getCall().asCall() = call
)
}
/** Gets a node that is an output from a call to this API. */
private DataFlow::Node getAnOutput() {
exists(Call call | call.getCallee().getSourceDeclaration() = this |
result.asExpr() = call or
result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call
)
}
/** Holds if this API has a supported summary. */
pragma[nomagic]
predicate hasSummary() {
this = any(SummarizedCallable sc).asCallable() or
TaintTracking::localAdditionalTaintStep(this.getAnInput(), _)
}
predicate hasSummary() { this = any(SummarizedCallable sc).asCallable() }
/** Holds if this API is a known source. */
pragma[nomagic]
predicate isSource() {
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
}
abstract predicate isSource();
/** Holds if this API is a known sink. */
pragma[nomagic]
predicate isSink() { sinkNode(this.getAnInput(), _) }
abstract predicate isSink();
/** Holds if this API is a known neutral. */
pragma[nomagic]
predicate isNeutral() {
exists(string namespace, string type, string name, string signature, string kind, string provenance |
neutralModel(namespace, type, name, signature, kind, provenance) and
exists(string namespace, string type, string name, string signature |
neutralModel(namespace, type, name, signature, _, _) and
this = interpretElement(namespace, type, false, name, signature, "")
)
}
@@ -163,108 +196,38 @@ class CallableMethod extends Callable {
}
}
boolean isSupported(CallableMethod method) {
method.isSupported() and result = true
boolean isSupported(Endpoint endpoint) {
endpoint.isSupported() and result = true
or
not method.isSupported() and result = false
not endpoint.isSupported() and result = false
}
string supportedType(CallableMethod method) {
method.isSink() and result = "sink"
string supportedType(Endpoint endpoint) {
endpoint.isSink() and result = "sink"
or
method.isSource() and result = "source"
endpoint.isSource() and result = "source"
or
method.hasSummary() and result = "summary"
endpoint.hasSummary() and result = "summary"
or
method.isNeutral() and result = "neutral"
endpoint.isNeutral() and result = "neutral"
or
not method.isSupported() and result = ""
not endpoint.isSupported() and result = ""
}
string methodClassification(Call method) {
isInTestFile(method.getLocation().getFile()) and result = "test"
string usageClassification(Call usage) {
isInTestFile(usage.getLocation().getFile()) and result = "test"
or
method.getFile() instanceof GeneratedFile and result = "generated"
usage.getFile() instanceof GeneratedFile and result = "generated"
or
not isInTestFile(method.getLocation().getFile()) and
not method.getFile() instanceof GeneratedFile and
not isInTestFile(usage.getLocation().getFile()) and
not usage.getFile() instanceof GeneratedFile and
result = "source"
}
// The below is a copy of https://github.com/github/codeql/blob/249f9f863db1e94e3c46ca85b49fb0ec32f8ca92/java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll
// to avoid the use of internal modules.
/** Holds if the given package \`p\` is a test package. */
pragma[nomagic]
private predicate isTestPackage(Package p) {
p.getName()
.matches([
"org.junit%", "junit.%", "org.mockito%", "org.assertj%",
"com.github.tomakehurst.wiremock%", "org.hamcrest%", "org.springframework.test.%",
"org.springframework.mock.%", "org.springframework.boot.test.%", "reactor.test%",
"org.xmlunit%", "org.testcontainers.%", "org.opentest4j%", "org.mockserver%",
"org.powermock%", "org.skyscreamer.jsonassert%", "org.rnorth.visibleassertions",
"org.openqa.selenium%", "com.gargoylesoftware.htmlunit%", "org.jboss.arquillian.testng%",
"org.testng%"
])
}
/**
* A test library.
*/
class TestLibrary extends RefType {
TestLibrary() { isTestPackage(this.getPackage()) }
}
/** Holds if the given file is a test file. */
private predicate isInTestFile(File file) {
// Temporarily copied from java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll
predicate isInTestFile(File file) {
file.getAbsolutePath().matches(["%/test/%", "%/guava-tests/%", "%/guava-testlib/%"]) and
not file.getAbsolutePath().matches("%/ql/test/%") // allows our test cases to work
}
/** Holds if the given compilation unit's package is a JDK internal. */
private predicate isJdkInternal(CompilationUnit cu) {
cu.getPackage().getName().matches("org.graalvm%") or
cu.getPackage().getName().matches("com.sun%") or
cu.getPackage().getName().matches("sun%") or
cu.getPackage().getName().matches("jdk%") or
cu.getPackage().getName().matches("java2d%") or
cu.getPackage().getName().matches("build.tools%") or
cu.getPackage().getName().matches("propertiesparser%") or
cu.getPackage().getName().matches("org.jcp%") or
cu.getPackage().getName().matches("org.w3c%") or
cu.getPackage().getName().matches("org.ietf.jgss%") or
cu.getPackage().getName().matches("org.xml.sax%") or
cu.getPackage().getName().matches("com.oracle%") or
cu.getPackage().getName().matches("org.omg%") or
cu.getPackage().getName().matches("org.relaxng%") or
cu.getPackage().getName() = "compileproperties" or
cu.getPackage().getName() = "transparentruler" or
cu.getPackage().getName() = "genstubs" or
cu.getPackage().getName() = "netscape.javascript" or
cu.getPackage().getName() = ""
}
/** Holds if the given callable is not worth modeling. */
predicate isUninterestingForModels(Callable c) {
isInTestFile(c.getCompilationUnit().getFile()) or
isJdkInternal(c.getCompilationUnit()) or
c instanceof MainMethod or
c instanceof StaticInitializer or
exists(FunctionalExpr funcExpr | c = funcExpr.asMethod()) or
c.getDeclaringType() instanceof TestLibrary or
c.(Constructor).isParameterless()
}
/**
* A class that represents all callables for which we might be
* interested in having a MaD model.
*/
class ModelApi extends SrcCallable {
ModelApi() {
this.fromSource() and
this.isEffectivelyPublic() and
not isUninterestingForModels(this)
}
not file.getAbsolutePath().matches(["%/ql/test/%", "%/ql/automodel/test/%"]) // allows our test cases to work
}
`,
},

View File

@@ -1,18 +1,21 @@
import { Call, CallClassification } from "../method";
import { ModeledMethodType } from "../modeled-method";
export type Query = {
/**
* The application query.
*
* It should select all usages of external APIs, and return the following result pattern:
* - usage: the usage of the external API. This is an entity.
* - apiName: the name of the external API. This is a string.
* - supported: whether the external API is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query.
* - "supported": a string literal. This is required to make the query a valid problem query.
* - packageName: the package name of the external API. This is a string.
* - typeName: the type name of the external API. This is a string.
* - methodName: the method name of the external API. This is a string.
* - methodParameters: the parameters of the external API. This is a string.
* - supported: whether the external API is modeled. This is a boolean.
* - libraryName: the name of the library that contains the external API. This is a string and usually the basename of a file.
* - libraryVersion: the version of the library that contains the external API. This is a string and can be empty if the version cannot be determined.
* - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral"
* - "type": a string literal. This is required to make the query a valid problem query.
* - classification: the classification of the use of the method, either "source", "test", "generated", or "unknown"
* - "classification: a string literal. This is required to make the query a valid problem query.
*/
applicationModeQuery: string;
/**
@@ -21,18 +24,40 @@ export type Query = {
* It should select all methods that are callable by applications, which is usually all public methods (and constructors).
* The result pattern should be as follows:
* - method: the method that is callable by applications. This is an entity.
* - apiName: the name of the external API. This is a string.
* - packageName: the package name of the method. This is a string.
* - typeName: the type name of the method. This is a string.
* - methodName: the method name of the method. This is a string.
* - methodParameters: the parameters of the method. This is a string.
* - supported: whether this method is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query.
* - "supported": a string literal. This is required to make the query a valid problem query.
* - libraryName: an arbitrary string. This is required to make it match the structure of the application query.
* - libraryVersion: an arbitrary string. This is required to make it match the structure of the application query.
* - libraryName: the name of the file or library that contains the method. This is a string and usually the basename of a file.
* - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral"
* - "type": a string literal. This is required to make the query a valid problem query.
* - "unknown": a string literal. This is required to make it match the structure of the application query.
* - "classification: a string literal. This is required to make the query a valid problem query.
*/
frameworkModeQuery: string;
dependencies?: {
[filename: string]: string;
};
};
export type ApplicationModeTuple = [
Call,
string,
string,
string,
string,
boolean,
string,
string,
ModeledMethodType,
CallClassification,
];
export type FrameworkModeTuple = [
Call,
string,
string,
string,
string,
boolean,
string,
ModeledMethodType,
];

View File

@@ -1 +0,0 @@
export const INITIAL_HIDE_MODELED_APIS_VALUE = true;

View File

@@ -0,0 +1 @@
export const INITIAL_HIDE_MODELED_METHODS_VALUE = true;

View File

@@ -1,7 +1,7 @@
/**
* A class that keeps track of which methods are in progress for each package.
*
* This class is immutable and therefore is safe to be used in a react useState hook.
* This class is immutable and therefore is safe to be used in a React useState hook.
*/
export class InProgressMethods {
// A map of in-progress method signatures for each package.

View File

@@ -2,3 +2,5 @@ export enum Mode {
Application = "application",
Framework = "framework",
}
export const INITIAL_MODE = Mode.Application;

View File

@@ -0,0 +1,32 @@
import { ModeledMethod } from "../modeled-method";
/**
* Converts a single ModeledMethod to a ModeledMethod[] for legacy usage. This function should always be used instead
* of the trivial conversion to track usages of this conversion.
*
* This method should only be called inside a `onMessage` function (or its equivalent). If it's used anywhere else,
* consider whether the boundary is correct: the boundary should as close as possible to the webview -> extension host
* boundary.
*
* @param modeledMethod The single ModeledMethod
*/
export function convertFromLegacyModeledMethod(
modeledMethod: ModeledMethod | undefined,
): ModeledMethod[] {
return modeledMethod ? [modeledMethod] : [];
}
/**
* Converts a ModeledMethod[] to a single ModeledMethod for legacy usage. This function should always be used instead
* of the trivial conversion to track usages of this conversion.
*
* This method should only be called inside a `postMessage` call. If it's used anywhere else, consider whether the
* boundary is correct: the boundary should as close as possible to the extension host -> webview boundary.
*
* @param modeledMethods The ModeledMethod[]
*/
export function convertToLegacyModeledMethod(
modeledMethods: ModeledMethod[],
): ModeledMethod | undefined {
return modeledMethods[0];
}

View File

@@ -0,0 +1,17 @@
import { ModeledMethod } from "../modeled-method";
export type ModelingStatus = "unmodeled" | "unsaved" | "saved";
export function getModelingStatus(
modeledMethods: ModeledMethod[],
methodIsUnsaved: boolean,
): ModelingStatus {
if (modeledMethods.length > 0) {
if (methodIsUnsaved) {
return "unsaved";
} else if (modeledMethods.some((m) => m.type !== "none")) {
return "saved";
}
}
return "unmodeled";
}

Some files were not shown because too many files have changed in this diff Show More