From 9248060471edbc073fc1daa34dc6c974979911b9 Mon Sep 17 00:00:00 2001 From: George Zahariev Date: Fri, 7 Mar 2025 01:15:52 -0800 Subject: [PATCH] [flow][match] Quickfix for invalid object pattern shorthand Summary: Adds quickfixes for invalid object pattern shorthand error, one for each intended possibility. Future work will include adding more support for match expressions/statements in the ast differ so that we get more fine-grained changes. Changelog: [internal] Reviewed By: panagosg7 Differential Revision: D70727295 fbshipit-source-id: d4d097cefb54ea5351f80f3169dd18d7a3259644 --- .../fix-match-invalid-object-shorthand.json | 71 +++++++++++++++++++ .../code-action/quickfix/_flowconfig_match | 2 + ...-match-invalid-object-shorthand.js.ignored | 8 +++ newtests/lsp/code-action/quickfix/test.js | 27 +++++++ .../code_action/autofix_match_syntax.ml | 50 +++++++++++++ .../code_action/code_action_service.ml | 19 +++++ 6 files changed, 177 insertions(+) create mode 100644 newtests/lsp/code-action/quickfix/__snapshots__/fix-match-invalid-object-shorthand.json create mode 100644 newtests/lsp/code-action/quickfix/_flowconfig_match create mode 100644 newtests/lsp/code-action/quickfix/fix-match-invalid-object-shorthand.js.ignored create mode 100644 src/services/code_action/autofix_match_syntax.ml diff --git a/newtests/lsp/code-action/quickfix/__snapshots__/fix-match-invalid-object-shorthand.json b/newtests/lsp/code-action/quickfix/__snapshots__/fix-match-invalid-object-shorthand.json new file mode 100644 index 00000000000..444df169df4 --- /dev/null +++ b/newtests/lsp/code-action/quickfix/__snapshots__/fix-match-invalid-object-shorthand.json @@ -0,0 +1,71 @@ +{ + "method": "textDocument/codeAction", + "result": [ + { + "title": "Convert to `const foo`", + "kind": "quickfix", + "diagnostics": [], + "edit": { + "changes": { + "/fix-match-invalid-object-shorthand.js": [ + { + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 7, + "character": 1 + } + }, + "newText": "match (x) {\n {const foo}: 0,\n _: 0,\n}" + } + ] + } + }, + "command": { + "title": "", + "command": "log:org.flow:", + "arguments": [ + "textDocument/codeAction", + "convert_match_object_shorthand_to_const", + "Convert to `const foo`" + ] + } + }, + { + "title": "Convert to `foo: foo`", + "kind": "quickfix", + "diagnostics": [], + "edit": { + "changes": { + "/fix-match-invalid-object-shorthand.js": [ + { + "range": { + "start": { + "line": 4, + "character": 12 + }, + "end": { + "line": 7, + "character": 1 + } + }, + "newText": "match (x) {\n {foo: foo}: 0,\n _: 0,\n}" + } + ] + } + }, + "command": { + "title": "", + "command": "log:org.flow:", + "arguments": [ + "textDocument/codeAction", + "convert_match_object_shorthand_to_reference", + "Convert to `foo: foo`" + ] + } + } + ] +} diff --git a/newtests/lsp/code-action/quickfix/_flowconfig_match b/newtests/lsp/code-action/quickfix/_flowconfig_match new file mode 100644 index 00000000000..cd5c24fb555 --- /dev/null +++ b/newtests/lsp/code-action/quickfix/_flowconfig_match @@ -0,0 +1,2 @@ +[options] +experimental.pattern_matching=true diff --git a/newtests/lsp/code-action/quickfix/fix-match-invalid-object-shorthand.js.ignored b/newtests/lsp/code-action/quickfix/fix-match-invalid-object-shorthand.js.ignored new file mode 100644 index 00000000000..3e6defe4c1a --- /dev/null +++ b/newtests/lsp/code-action/quickfix/fix-match-invalid-object-shorthand.js.ignored @@ -0,0 +1,8 @@ +// @flow + +declare const x: {foo: 1}; + +const out = match (x) { + {foo}: 0, + _: 0, +}; diff --git a/newtests/lsp/code-action/quickfix/test.js b/newtests/lsp/code-action/quickfix/test.js index 5f917d86986..f28dd5354e2 100644 --- a/newtests/lsp/code-action/quickfix/test.js +++ b/newtests/lsp/code-action/quickfix/test.js @@ -1979,5 +1979,32 @@ module.exports = (suite( ['textDocument/publishDiagnostics', ...lspIgnoreStatusAndCancellation], ), ]), + test('match invalid object shorthand quickfix', [ + addFile( + 'fix-match-invalid-object-shorthand.js.ignored', + 'fix-match-invalid-object-shorthand.js', + ), + lspStartAndConnect(), + lspRequestAndWaitUntilResponse('textDocument/codeAction', { + textDocument: { + uri: '/fix-match-invalid-object-shorthand.js', + }, + range: { + start: {line: 5, character: 5}, + end: {line: 5, character: 5}, + }, + context: { + only: ['quickfix'], + diagnostics: [], + }, + }).verifyLSPMessageSnapshot( + path.join( + __dirname, + '__snapshots__', + 'fix-match-invalid-object-shorthand.json', + ), + ['textDocument/publishDiagnostics', ...lspIgnoreStatusAndCancellation], + ), + ]).flowConfig('_flowconfig_match'), ], ): SuiteType); diff --git a/src/services/code_action/autofix_match_syntax.ml b/src/services/code_action/autofix_match_syntax.ml new file mode 100644 index 00000000000..106b0984faa --- /dev/null +++ b/src/services/code_action/autofix_match_syntax.ml @@ -0,0 +1,50 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type kind = + | ObjShorthandToConst + | ObjShorthandToReference + +class mapper target_loc ~kind = + object (this) + inherit Flow_ast_contains_mapper.mapper target_loc as super + + method! match_object_pattern_property prop = + let open Flow_ast.MatchPattern.ObjectPattern in + let (loc, _) = prop in + if not @@ this#is_target loc then + super#match_object_pattern_property prop + else + match prop with + | (_, Property.Valid _) -> super#match_object_pattern_property prop + | (_, Property.InvalidShorthand id) -> + let key = Property.Identifier id in + (match kind with + | ObjShorthandToConst -> + let pattern = + ( Loc.none, + Flow_ast.MatchPattern.BindingPattern + { + Flow_ast.MatchPattern.BindingPattern.kind = Flow_ast.Variable.Const; + id; + comments = None; + } + ) + in + (Loc.none, Property.Valid { Property.key; pattern; shorthand = true; comments = None }) + | ObjShorthandToReference -> + let pattern = (Loc.none, Flow_ast.MatchPattern.IdentifierPattern id) in + (Loc.none, Property.Valid { Property.key; pattern; shorthand = false; comments = None })) + end + +let convert_object_shorthand_to_const ast loc = + let mapper = new mapper loc ~kind:ObjShorthandToConst in + mapper#program ast + +let convert_object_shorthand_to_reference ast loc = + let mapper = new mapper loc ~kind:ObjShorthandToReference in + mapper#program ast diff --git a/src/services/code_action/code_action_service.ml b/src/services/code_action/code_action_service.ml index 081de614487..f25d9f34caf 100644 --- a/src/services/code_action/code_action_service.ml +++ b/src/services/code_action/code_action_service.ml @@ -1085,6 +1085,25 @@ let ast_transforms_of_error ~loc_of_aloc ?loc = function ] else [] + | Error_message.EMatchInvalidObjectShorthand { loc = error_loc; name } -> + if loc_opt_intersects ~error_loc ~loc then + [ + { + title = Utils_js.spf "Convert to `const %s`" name; + diagnostic_title = "convert_match_object_shorthand_to_const"; + transform = untyped_ast_transform Autofix_match_syntax.convert_object_shorthand_to_const; + target_loc = error_loc; + }; + { + title = Utils_js.spf "Convert to `%s: %s`" name name; + diagnostic_title = "convert_match_object_shorthand_to_reference"; + transform = + untyped_ast_transform Autofix_match_syntax.convert_object_shorthand_to_reference; + target_loc = error_loc; + }; + ] + else + [] | error_message -> (match error_message |> Error_message.friendly_message_of_msg with | Error_message.PropMissing