├── .cmake.conf ├── .gitreview ├── .tag ├── CMakeLists.txt ├── LICENSES ├── BSD-3-Clause.txt ├── CC-BY-4.0.txt ├── GFDL-1.3-no-invariants-only.txt ├── GPL-2.0-only.txt ├── GPL-3.0-only.txt ├── LGPL-3.0-only.txt ├── LicenseRef-Qt-Commercial.txt └── Qt-GPL-exception-1.0.txt ├── REUSE.toml ├── coin ├── axivion │ └── ci_config_linux.json └── module_config.yaml ├── conanfile.py ├── configure.cmake ├── dependencies.yaml ├── licenseRule.json ├── qt_cmdline.cmake ├── src ├── CMakeLists.txt ├── jsonrpc │ ├── CMakeLists.txt │ ├── doc │ │ └── qtjsonrpc.qdocconf │ ├── qhttpmessagestreamparser.cpp │ ├── qhttpmessagestreamparser_p.h │ ├── qjsonrpcprotocol.cpp │ ├── qjsonrpcprotocol_p.h │ ├── qjsonrpcprotocol_p_p.h │ ├── qjsonrpctransport_p.h │ ├── qjsontypedrpc.cpp │ ├── qjsontypedrpc_p.h │ ├── qtjsonrpcglobal.h │ ├── qtypedjson.cpp │ └── qtypedjson_p.h ├── languageserver │ ├── 3rdparty │ │ └── specification.md │ ├── CMakeLists.txt │ ├── generate.ts │ ├── package.json │ ├── qlanguageserverbase.cpp │ ├── qlanguageserverbase_p.h │ ├── qlanguageserverbase_p_p.h │ ├── qlanguageservergen.cpp │ ├── qlanguageservergen_p.h │ ├── qlanguageservergen_p_p.h │ ├── qlanguageserverjsonrpctransport.cpp │ ├── qlanguageserverjsonrpctransport_p.h │ ├── qlanguageserverprespectypes_p.h │ ├── qlanguageserverprotocol.cpp │ ├── qlanguageserverprotocol_p.h │ ├── qlanguageserverspec_p.h │ ├── qlanguageserverspectypes_p.h │ ├── qlspnotifysignals.cpp │ ├── qlspnotifysignals_p.h │ └── qtlanguageserverglobal.h └── qtlanguageserver.qdoc └── tests ├── CMakeLists.txt └── auto ├── CMakeLists.txt ├── cmake ├── CMakeLists.txt └── test_qtjsonrpc_module │ ├── CMakeLists.txt │ └── main.cpp ├── qjsonrpcprotocol ├── CMakeLists.txt └── tst_qjsonrpcprotocol.cpp ├── qlanguageserver ├── CMakeLists.txt ├── qiopipe.cpp ├── qiopipe.h └── tst_qlanguageserver.cpp └── typedjson ├── CMakeLists.txt ├── data ├── Range.json └── ReferenceParams.json ├── tst_typedjson.cpp └── tst_typedjson.h /.cmake.conf: -------------------------------------------------------------------------------- 1 | set(QT_REPO_MODULE_VERSION "6.11.0") 2 | set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") 3 | set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_AS_CONST=1") 4 | list(APPEND QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_FOREACH=1") 5 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=codereview.qt-project.org 3 | project=qt/qtlanguageserver 4 | defaultbranch=dev 5 | -------------------------------------------------------------------------------- /.tag: -------------------------------------------------------------------------------- 1 | 36a7dad54f7be27874e475413f81992ce1224d3c 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | cmake_minimum_required(VERSION 3.16.0) 5 | 6 | include(.cmake.conf) 7 | project(QtLanguageServer 8 | VERSION "${QT_REPO_MODULE_VERSION}" 9 | DESCRIPTION "Qt Language Server" 10 | HOMEPAGE_URL "https://qt.io/" 11 | LANGUAGES CXX C 12 | ) 13 | 14 | find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals Core) 15 | find_package(Qt6 ${PROJECT_VERSION} QUIET CONFIG OPTIONAL_COMPONENTS Network Concurrent Test) 16 | qt_internal_project_setup() 17 | 18 | if(ANDROID) 19 | # Android tests needs to link against Qt6::Gui 20 | find_package(Qt6 ${PROJECT_VERSION} QUIET CONFIG OPTIONAL_COMPONENTS Gui) 21 | endif() 22 | 23 | qt_build_repo() 24 | -------------------------------------------------------------------------------- /LICENSES/BSD-3-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) . 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 7 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /LICENSES/CC-BY-4.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International 2 | 3 | Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. 4 | 5 | Using Creative Commons Public Licenses 6 | 7 | Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. 8 | 9 | Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. More considerations for licensors. 10 | 11 | Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More considerations for the public. 12 | 13 | Creative Commons Attribution 4.0 International Public License 14 | 15 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 16 | 17 | Section 1 – Definitions. 18 | 19 | a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 20 | 21 | b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. 22 | 23 | c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 24 | 25 | d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 26 | 27 | e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 28 | 29 | f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 30 | 31 | g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 32 | 33 | h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. 34 | 35 | i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 36 | 37 | j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 38 | 39 | k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 40 | 41 | Section 2 – Scope. 42 | 43 | a. License grant. 44 | 45 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 46 | 47 | A. reproduce and Share the Licensed Material, in whole or in part; and 48 | 49 | B. produce, reproduce, and Share Adapted Material. 50 | 51 | 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 52 | 53 | 3. Term. The term of this Public License is specified in Section 6(a). 54 | 55 | 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 56 | 57 | 5. Downstream recipients. 58 | 59 | A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 60 | 61 | B. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 62 | 63 | 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 64 | 65 | b. Other rights. 66 | 67 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 68 | 69 | 2. Patent and trademark rights are not licensed under this Public License. 70 | 71 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. 72 | 73 | Section 3 – License Conditions. 74 | 75 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 76 | 77 | a. Attribution. 78 | 79 | 1. If You Share the Licensed Material (including in modified form), You must: 80 | 81 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 82 | 83 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 84 | 85 | ii. a copyright notice; 86 | 87 | iii. a notice that refers to this Public License; 88 | 89 | iv. a notice that refers to the disclaimer of warranties; 90 | 91 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 92 | 93 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 94 | 95 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 96 | 97 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 98 | 99 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 100 | 101 | 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. 102 | 103 | Section 4 – Sui Generis Database Rights. 104 | 105 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 106 | 107 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; 108 | 109 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and 110 | 111 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 112 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 113 | 114 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 115 | 116 | a. Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. 117 | 118 | b. To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. 119 | 120 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 121 | 122 | Section 6 – Term and Termination. 123 | 124 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 125 | 126 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 127 | 128 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 129 | 130 | 2. upon express reinstatement by the Licensor. 131 | 132 | c. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 133 | 134 | d. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 135 | 136 | e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 137 | 138 | Section 7 – Other Terms and Conditions. 139 | 140 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 141 | 142 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 143 | 144 | Section 8 – Interpretation. 145 | 146 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 147 | 148 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 149 | 150 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 151 | 152 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 153 | 154 | Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. 155 | 156 | Creative Commons may be contacted at creativecommons.org. 157 | -------------------------------------------------------------------------------- /LICENSES/GPL-2.0-only.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /LICENSES/LGPL-3.0-only.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /LICENSES/LicenseRef-Qt-Commercial.txt: -------------------------------------------------------------------------------- 1 | Licensees holding valid commercial Qt licenses may use this software in 2 | accordance with the the terms contained in a written agreement between 3 | you and The Qt Company. Alternatively, the terms and conditions that were 4 | accepted by the licensee when buying and/or downloading the 5 | software do apply. 6 | 7 | For the latest licensing terms and conditions, see https://www.qt.io/terms-conditions. 8 | For further information use the contact form at https://www.qt.io/contact-us. 9 | -------------------------------------------------------------------------------- /LICENSES/Qt-GPL-exception-1.0.txt: -------------------------------------------------------------------------------- 1 | The Qt Company GPL Exception 1.0 2 | 3 | Exception 1: 4 | 5 | As a special exception you may create a larger work which contains the 6 | output of this application and distribute that work under terms of your 7 | choice, so long as the work is not otherwise derived from or based on 8 | this application and so long as the work does not in itself generate 9 | output that contains the output from this application in its original 10 | or modified form. 11 | 12 | Exception 2: 13 | 14 | As a special exception, you have permission to combine this application 15 | with Plugins licensed under the terms of your choice, to produce an 16 | executable, and to copy and distribute the resulting executable under 17 | the terms of your choice. However, the executable must be accompanied 18 | by a prominent notice offering all users of the executable the entire 19 | source code to this application, excluding the source code of the 20 | independent modules, but including any changes you have made to this 21 | application, under the terms of this license. 22 | 23 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[annotations]] 4 | path = ["tests/**.json"] 5 | precedence = "closest" 6 | comment = "test" 7 | SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." 8 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GPL-3.0-only" 9 | 10 | [[annotations]] 11 | path = ["src/**.json"] 12 | precedence = "closest" 13 | comment = "src and plugin" 14 | SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." 15 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only" 16 | 17 | [[annotations]] 18 | path = ["**.qrc", "**CMakeLists.txt", ".cmake.conf", "**.yaml", 19 | "**.cmake", ".tag", "**/.gitattributes", 20 | "**.gitignore", "**./gitreview", "**ci_config_linux.json"] 21 | precedence = "closest" 22 | comment = "build system" 23 | SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." 24 | SPDX-License-Identifier = "BSD-3-Clause" 25 | 26 | [[annotations]] 27 | path = ["**/.gitattributes", "**.gitignore", "**.gitreview"] 28 | precedence = "closest" 29 | comment = "infrastructure" 30 | SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." 31 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" 32 | 33 | [[annotations]] 34 | path = ["**.qdocconf"] 35 | comment = "documentation" 36 | precedence = "closest" 37 | SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." 38 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only" 39 | 40 | [[annotations]] 41 | path = ["src/languageserver/3rdparty/specification.md"] 42 | precedence = "closest" 43 | SPDX-FileCopyrightText = "Copyright (c) Microsoft Corporation." 44 | SPDX-License-Identifier = "CC-BY-4.0" 45 | 46 | [[annotations]] 47 | path = ["**.toml", "licenseRule.json"] 48 | precedence = "override" 49 | SPDX-FileCopyrightText = "Copyright (C) 2024 The Qt Company Ltd." 50 | SPDX-License-Identifier = "LicenseRef-Qt-Commercial OR BSD-3-Clause" 51 | 52 | -------------------------------------------------------------------------------- /coin/axivion/ci_config_linux.json: -------------------------------------------------------------------------------- 1 | { 2 | "Project": { 3 | "BuildSystemIntegration": { 4 | "child_order": [ 5 | "GCCSetup", 6 | "CMake", 7 | "LinkLibraries" 8 | ] 9 | }, 10 | "CMake": { 11 | "_active": true, 12 | "_copy_from": "CMakeIntegration", 13 | "build_environment": {}, 14 | "build_options": "-j4", 15 | "generate_options": "--fresh", 16 | "generator": "Ninja" 17 | }, 18 | "GCCSetup": { 19 | "_active": true, 20 | "_copy_from": "Command", 21 | "build_command": "gccsetup --cc gcc --cxx g++ --config ../../../axivion/" 22 | }, 23 | "LinkLibraries": { 24 | "_active": true, 25 | "_copy_from": "AxivionLinker", 26 | "input_files": [ 27 | "build/lib/lib*.a.ir" 28 | ], 29 | "ir": "build/$(env:TESTED_MODULE_COIN).ir", 30 | "options": "--include_unused" 31 | 32 | } 33 | }, 34 | "_Format": "1.0", 35 | "_Version": "7.6.2", 36 | "_VersionNum": [ 37 | 7, 38 | 6, 39 | 2, 40 | 12725 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /coin/module_config.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | accept_configuration: 3 | condition: property 4 | property: features 5 | not_contains_value: Disable 6 | 7 | instructions: 8 | Build: 9 | - type: EnvironmentVariable 10 | variableName: VERIFY_SOURCE_SBOM 11 | variableValue: "ON" 12 | - !include "{{qt/qtbase}}/coin_module_build_template_v2.yaml" 13 | 14 | Test: 15 | - !include "{{qt/qtbase}}/coin_module_test_template_v3.yaml" 16 | - !include "{{qt/qtbase}}/coin_module_test_docs.yaml" 17 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 3 | 4 | from conans import ConanFile 5 | import re 6 | from pathlib import Path 7 | 8 | 9 | def _parse_qt_version_by_key(key: str) -> str: 10 | with open(Path(__file__).parent.resolve() / ".cmake.conf") as f: 11 | m = re.search(fr'{key} .*"(.*)"', f.read()) 12 | return m.group(1) if m else "" 13 | 14 | 15 | def _get_qt_minor_version() -> str: 16 | return ".".join(_parse_qt_version_by_key("QT_REPO_MODULE_VERSION").split(".")[:2]) 17 | 18 | 19 | class QtLanguageServer(ConanFile): 20 | name = "qtlanguageserver" 21 | license = "LGPL-3.0, GPL-2.0+, Commercial Qt License Agreement" 22 | author = "The Qt Company " 23 | url = "https://code.qt.io/cgit/qt/qtlanguageserver.git" 24 | description = "Language server protocol https://microsoft.github.io/language-server-protocol/specification implementation for Qt." 25 | topics = ("qt", "qt6", "addon", "LPS", "languageserver" ) 26 | settings = "os", "compiler", "arch", "build_type" 27 | # for referencing the version number and prerelease tag and dependencies info 28 | exports = ".cmake.conf", "dependencies.yaml" 29 | exports_sources = "*", "!conan*.*" 30 | python_requires = f"qt-conan-common/{_get_qt_minor_version()}@qt/everywhere" 31 | python_requires_extend = "qt-conan-common.QtLeafModule" 32 | -------------------------------------------------------------------------------- /configure.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | 5 | 6 | #### Inputs 7 | 8 | 9 | 10 | #### Libraries 11 | 12 | 13 | 14 | #### Tests 15 | 16 | 17 | 18 | #### Features 19 | 20 | 21 | qt_extra_definition("QT_VERSION_STR" "\"${PROJECT_VERSION}\"" PUBLIC) 22 | qt_extra_definition("QT_VERSION_MAJOR" ${PROJECT_VERSION_MAJOR} PUBLIC) 23 | qt_extra_definition("QT_VERSION_MINOR" ${PROJECT_VERSION_MINOR} PUBLIC) 24 | qt_extra_definition("QT_VERSION_PATCH" ${PROJECT_VERSION_PATCH} PUBLIC) 25 | -------------------------------------------------------------------------------- /dependencies.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | ../qtbase: 3 | ref: 51d5b9e2580a4f54594f616cf9859af86626c887 4 | required: true 5 | -------------------------------------------------------------------------------- /licenseRule.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "comment": ["file_pattern_ending: strings matched against the end of a file name.", 4 | "location keys: regular expression matched against the beginning of", 5 | "the file path (relative to the git submodule root).", 6 | "spdx: list of SPDX-License-Expression's allowed in the matching files.", 7 | "-------------------------------------------------------", 8 | "Files with the following endings are Build System licensed,", 9 | "unless they are examples", 10 | "Files with other endings can also be build system files" 11 | ], 12 | "file_pattern_ending": ["CMakeLists.txt", ".cmake", ".pro", ".pri", ".prf", 13 | "configure", "configure.bat", "cmake.in", "plist.in", "CMakeLists.txt.in", 14 | ".cmake.conf", ".tag", "ci_config_linux.json", 15 | ".yaml"], 16 | "location": { 17 | "": { 18 | "comment": "Default", 19 | "file type": "build system", 20 | "spdx": ["BSD-3-Clause"] 21 | }, 22 | "(.*)(examples/|snippets/)": { 23 | "comment": "Example takes precedence", 24 | "file type": "examples and snippets", 25 | "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] 26 | } 27 | } 28 | }, 29 | { 30 | "comments": ["Files with the following endings are infrastructure licensed"], 31 | "file_pattern_ending": [".gitattributes", ".gitignore", ".gitmodules", ".gitreview", 32 | "_clang-format", "licenseRule.json", "REUSE.toml"], 33 | "location":{ 34 | "": { 35 | "comment": "Default", 36 | "file type": "infrastructure", 37 | "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] 38 | } 39 | } 40 | }, 41 | { 42 | "comments": ["Files with the following endings are Tool licensed,", 43 | "unless they are examples.", 44 | "Files with other endings can also be tool files."], 45 | "file_pattern_ending": [".sh", ".py", ".pl", ".bat", ".ps1"], 46 | "location":{ 47 | "": { 48 | "comment": "Default", 49 | "file type": "tools and utils", 50 | "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"] 51 | }, 52 | "(.*)(examples/|snippets/)": { 53 | "comment": "Example takes precedence", 54 | "file type": "examples and snippets", 55 | "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] 56 | } 57 | } 58 | }, 59 | { 60 | "comment": "Files with the following endings are Documentation licensed.", 61 | "file_pattern_ending": [".qdoc", ".qdocinc" , ".qdocconf", "README", "qt_attribution.json"], 62 | "location":{ 63 | "": { 64 | "comment": "", 65 | "file type": "documentation", 66 | "spdx": ["LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only"] 67 | } 68 | } 69 | }, 70 | { 71 | "comment": ["All other files", 72 | "The licensing is defined only by the file location in the Qt module repository.", 73 | "NO key for this case!", 74 | "This needs to be the last entry of the file."], 75 | "location": { 76 | "": { 77 | "comment": "Default", 78 | "file type": "module and plugin", 79 | "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] 80 | }, 81 | "src/": { 82 | "comment": "Default", 83 | "file type": "module and plugin", 84 | "spdx": ["LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only"] 85 | }, 86 | "tests/": { 87 | "comment": "Default", 88 | "file type": "test", 89 | "spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only"] 90 | }, 91 | "(.*)(examples/|snippets/)": { 92 | "comment": "Default", 93 | "file type": "examples and snippets", 94 | "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"] 95 | } 96 | } 97 | } 98 | ] 99 | -------------------------------------------------------------------------------- /qt_cmdline.cmake: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qt/qtlanguageserver/36a7dad54f7be27874e475413f81992ce1224d3c/qt_cmdline.cmake -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(jsonrpc) 2 | add_subdirectory(languageserver) 3 | -------------------------------------------------------------------------------- /src/jsonrpc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | ##################################################################### 5 | ## JsonRpc Module: 6 | ##################################################################### 7 | 8 | qt_internal_add_module(JsonRpcPrivate 9 | INTERNAL_MODULE 10 | STATIC 11 | SOURCES 12 | qhttpmessagestreamparser_p.h qhttpmessagestreamparser.cpp 13 | qjsonrpcprotocol.cpp qjsonrpcprotocol_p.h qjsonrpcprotocol_p_p.h 14 | qjsonrpctransport_p.h 15 | qtjsonrpcglobal.h 16 | qjsontypedrpc_p.h qjsontypedrpc.cpp 17 | qtypedjson_p.h qtypedjson.cpp 18 | DEFINES 19 | QT_BUILD_JSONRPC_LIB 20 | QT_NO_CONTEXTLESS_CONNECT 21 | PUBLIC_LIBRARIES 22 | Qt::Core 23 | ) 24 | -------------------------------------------------------------------------------- /src/jsonrpc/doc/qtjsonrpc.qdocconf: -------------------------------------------------------------------------------- 1 | include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) 2 | 3 | project = QtJsonRpc 4 | description = Qt JsonRpc Reference Documentation 5 | version = $QT_VERSION 6 | 7 | examplesinstallpath = jsonrpc 8 | exampledirs = ../../../examples/jsonrpc 9 | imagedirs = ../../../examples/doc/images 10 | 11 | qhp.QtJsonRpc.subprojects = classes examples 12 | qhp.QtJsonRpc.subprojects.classes.title = C++ Classes 13 | qhp.QtJsonRpc.subprojects.classes.indexTitle = Qt JSON RPC C++ Classes 14 | qhp.QtJsonRpc.subprojects.classes.selectors = class fake:headerfile 15 | qhp.QtJsonRpc.subprojects.classes.sortPages = true 16 | qhp.QtJsonRpc.subprojects.examples.title = Examples 17 | qhp.QtJsonRpc.subprojects.examples.indexTitle = Qt JSON RPC Examples 18 | qhp.QtJsonRpc.subprojects.examples.selectors = fake:example 19 | 20 | qhp.projects = QtJsonRpc 21 | 22 | qhp.QtJsonRpc.file = qtjsonrpc.qhp 23 | qhp.QtJsonRpc.namespace = org.qt-project.qtjsonrpc.$QT_VERSION_TAG 24 | qhp.QtJsonRpc.virtualFolder = qtjsonrpc 25 | qhp.QtJsonRpc.indexTitle = Qt JSON RPC 26 | qhp.QtJsonRpc.indexRoot = 27 | 28 | depends += qtcore qtdoc qmake 29 | 30 | sourcedirs += .. \ 31 | ../../../examples/jsonrpc 32 | 33 | tagfile = qtjsonrpc.tags 34 | 35 | navigation.landingpage = "Qt JSON RPC" 36 | navigation.cppclassespage = "Qt JSON RPC C++ Classes" 37 | 38 | # Allow zero warnings when testing documentation in CI 39 | warninglimit = 0 40 | -------------------------------------------------------------------------------- /src/jsonrpc/qhttpmessagestreamparser.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qhttpmessagestreamparser_p.h" 5 | 6 | #include 7 | 8 | QT_BEGIN_NAMESPACE 9 | 10 | using namespace Qt::StringLiterals; 11 | 12 | /*! 13 | * \class QHttpMessageStreamParser 14 | * \brief Decodes a stream of headers and payloads encoded according to rfc2616 (HTTP/1.1) 15 | * 16 | * It complains about invalid sequences, but is quite permissive in accepting them 17 | */ 18 | 19 | QHttpMessageStreamParser::QHttpMessageStreamParser( 20 | std::function headerHandler, 21 | std::function bodyHandler, 22 | std::function errorHandler, Mode mode) 23 | : m_headerHandler(std::move(headerHandler)), 24 | m_bodyHandler(std::move(bodyHandler)), 25 | m_errorHandler(std::move(errorHandler)), 26 | m_mode(mode) 27 | { 28 | } 29 | 30 | bool QHttpMessageStreamParser::receiveEof() 31 | { 32 | if (m_state != State::PreHeader) { 33 | errorMessage(QtWarningMsg, u"Partial message at end of file"_s); 34 | return false; 35 | } 36 | return true; 37 | } 38 | 39 | void QHttpMessageStreamParser::receiveData(QByteArray data) 40 | { 41 | const char lf = '\n'; 42 | const char cr = '\r'; 43 | const char colon = ':'; 44 | const char space = ' '; 45 | const char tab = '\t'; 46 | qsizetype dataPos = 0; 47 | bool didAdvance = false; 48 | auto advance = [&]() { 49 | data = data.mid(dataPos); 50 | dataPos = 0; 51 | didAdvance = true; 52 | }; 53 | while (dataPos < data.size()) { 54 | switch (m_state) { 55 | case State::PreHeader: 56 | switch (data.at(dataPos)) { 57 | case lf: 58 | errorMessage(QtWarningMsg, 59 | QStringLiteral("Unexpected newline without preceding carriage " 60 | "return at start of headers") 61 | .arg(QString::fromUtf8(m_currentHeaderField))); 62 | m_state = State::AfterCrLf; 63 | ++dataPos; 64 | continue; 65 | case cr: 66 | m_state = State::AfterCr; 67 | ++dataPos; 68 | continue; 69 | case tab: 70 | case space: 71 | errorMessage(QtWarningMsg, 72 | u"Unexpected space at start of headers, skipping"_s.arg( 73 | QString::fromUtf8(m_currentHeaderField))); 74 | while (dataPos < data.size()) { 75 | char c = data.at(++dataPos); 76 | if (c != space && c != tab) { 77 | advance(); 78 | m_state = State::InHeaderField; 79 | break; 80 | } 81 | } 82 | break; 83 | default: 84 | m_state = State::InHeaderField; 85 | break; 86 | } 87 | Q_ASSERT(m_currentHeaderField.isEmpty() && m_currentHeaderValue.isEmpty()); 88 | break; 89 | case State::InHeaderField: { 90 | didAdvance = false; 91 | while (!didAdvance) { 92 | char c = data.at(dataPos); 93 | switch (c) { 94 | case lf: 95 | m_currentHeaderField.append(data.mid(0, dataPos)); 96 | errorMessage( 97 | QtWarningMsg, 98 | u"Unexpected carriage return without newline in unterminated header %1"_s 99 | .arg(QString::fromUtf8(m_currentHeaderField))); 100 | 101 | m_state = State::AfterCrLf; 102 | advance(); 103 | ++dataPos; 104 | break; 105 | case cr: 106 | m_state = State::AfterCr; 107 | m_currentHeaderField.append(data.mid(0, dataPos)); 108 | errorMessage(QtWarningMsg, 109 | u"Newline before colon in header %1"_s.arg( 110 | QString::fromUtf8(m_currentHeaderField))); 111 | advance(); 112 | ++dataPos; 113 | break; 114 | case colon: 115 | m_currentHeaderField.append(data.mid(0, dataPos)); 116 | m_state = State::HeaderValueSpace; 117 | ++dataPos; 118 | advance(); 119 | break; 120 | case space: 121 | case tab: 122 | errorMessage(QtWarningMsg, u"Space in header field name"_s); 123 | Q_FALLTHROUGH(); 124 | default: 125 | if (++dataPos == data.size()) { 126 | m_currentHeaderField.append(data); 127 | return; 128 | } 129 | break; 130 | } 131 | } 132 | } break; 133 | case State::HeaderValueSpace: 134 | while (dataPos < data.size()) { 135 | char c = data.at(dataPos); 136 | if (c != space && c != tab) { 137 | advance(); 138 | m_state = State::InHeaderValue; 139 | m_currentHeaderValue.clear(); 140 | break; 141 | } 142 | ++dataPos; 143 | } 144 | break; 145 | case State::InHeaderValue: { 146 | didAdvance = false; 147 | while (!didAdvance) { 148 | char c = data.at(dataPos); 149 | switch (c) { 150 | case lf: 151 | m_currentHeaderValue.append(data.mid(0, dataPos)); 152 | errorMessage(QtWarningMsg, 153 | QStringLiteral("Unexpected newline without preceding " 154 | "carriage return in header %1") 155 | .arg(QString::fromUtf8(m_currentHeaderField))); 156 | 157 | m_state = State::AfterCrLf; 158 | advance(); 159 | ++dataPos; 160 | break; 161 | case cr: 162 | m_currentHeaderValue.append(data.mid(0, dataPos)); 163 | m_state = State::AfterCr; 164 | advance(); 165 | ++dataPos; 166 | break; 167 | default: 168 | if (++dataPos == data.size()) { 169 | m_currentHeaderValue.append(data); 170 | return; 171 | } 172 | break; 173 | } 174 | } 175 | } break; 176 | case State::AfterCr: { 177 | char c = data.at(dataPos); 178 | switch (c) { 179 | case lf: 180 | m_state = State::AfterCrLf; 181 | ++dataPos; 182 | break; 183 | case cr: 184 | errorMessage(QtWarningMsg, 185 | QStringLiteral("Double carriage return encountred, interpreting it as " 186 | "header end after header %1") 187 | .arg(QString::fromUtf8(m_currentHeaderField))); 188 | m_currentPacket.clear(); 189 | m_currentPacketSize = 0; 190 | ++dataPos; 191 | advance(); 192 | m_state = State::InBody; 193 | callHasHeader(); 194 | break; 195 | case space: 196 | case tab: 197 | errorMessage( 198 | QtWarningMsg, 199 | u"Unexpected carriage return without following newline in header %1"_s.arg( 200 | QString::fromUtf8(m_currentHeaderField))); 201 | m_state = State::InHeaderValue; 202 | // m_currentHeaderValue.append(data.mid(0,dataPos)) to preserve the (non 203 | // significant) newlines in header value 204 | advance(); 205 | break; 206 | default: 207 | errorMessage( 208 | QtWarningMsg, 209 | u"Unexpected carriage return without following newline in header %1"_s.arg( 210 | QString::fromUtf8(m_currentHeaderField))); 211 | m_state = State::InHeaderField; 212 | advance(); 213 | callHasHeader(); 214 | break; 215 | } 216 | } break; 217 | case State::AfterCrLf: { 218 | char c = data.at(dataPos); 219 | switch (c) { 220 | case lf: 221 | errorMessage(QtWarningMsg, 222 | u"Newline without carriage return in header %1"_s.arg( 223 | QString::fromUtf8(m_currentHeaderField))); 224 | // avoid seeing it as end of headers? 225 | m_state = State::AfterCrLfCr; 226 | break; 227 | case cr: 228 | m_state = State::AfterCrLfCr; 229 | ++dataPos; 230 | break; 231 | case space: 232 | case tab: 233 | m_state = State::InHeaderValue; 234 | // m_currentHeaderValue.append(data.mid(0,dataPos)) to preserve the (non 235 | // significant) newlines in header value 236 | advance(); 237 | break; 238 | default: 239 | m_state = State::InHeaderField; 240 | advance(); 241 | callHasHeader(); 242 | break; 243 | } 244 | } break; 245 | case State::AfterCrLfCr: { 246 | char c = data.at(dataPos); 247 | switch (c) { 248 | case lf: 249 | m_currentPacket.clear(); 250 | m_currentPacketSize = 0; 251 | ++dataPos; 252 | advance(); 253 | m_state = State::InBody; 254 | callHasHeader(); 255 | break; 256 | default: 257 | errorMessage( 258 | QtWarningMsg, 259 | u"crlfcr without final lf encountred, ignoring it (non clear terminator)"_s); 260 | m_state = State::InHeaderField; 261 | advance(); 262 | callHasHeader(); 263 | break; 264 | } 265 | } break; 266 | case State::InBody: { 267 | if (m_contentSize == -1) { 268 | errorMessage(QtWarningMsg, u"missing valid Content-Length header"_s); 269 | m_state = State::PreHeader; 270 | continue; 271 | } 272 | qint64 missing = m_contentSize - m_currentPacketSize; 273 | if (missing > 0) { 274 | dataPos = qMin(qsizetype(missing), data.size()); 275 | m_currentPacketSize += dataPos; 276 | if (m_mode == BUFFERED) 277 | m_currentPacket.append(data.mid(0, dataPos)); 278 | advance(); 279 | } 280 | if (m_currentPacketSize >= m_contentSize) { 281 | m_state = State::PreHeader; 282 | callHasBody(); 283 | } 284 | } break; 285 | } 286 | } 287 | if (m_state == State::InBody && (m_contentSize == -1 || m_contentSize == 0)) { 288 | // nothing to read, but emit empty body... 289 | m_state = State::PreHeader; 290 | if (m_contentSize == -1) 291 | errorMessage(QtWarningMsg, u"missing valid Content-Length header"_s); 292 | callHasBody(); 293 | } 294 | } 295 | 296 | void QHttpMessageStreamParser::callHasHeader() 297 | { 298 | static const QByteArray s_contentLengthFieldName = "Content-Length"; 299 | if (m_currentHeaderField.isEmpty() && m_currentHeaderValue.isEmpty()) 300 | return; 301 | QByteArray field = m_currentHeaderField; 302 | QByteArray value = m_currentHeaderValue; 303 | m_currentHeaderField.clear(); 304 | m_currentHeaderValue.clear(); 305 | if (s_contentLengthFieldName.compare(field, Qt::CaseInsensitive) == 0) { 306 | bool ok = false; 307 | const int size = value.toInt(&ok); 308 | if (ok) { 309 | m_contentSize = size; 310 | } else { 311 | errorMessage( 312 | QtWarningMsg, 313 | u"Invalid %1: %2"_s.arg(QString::fromUtf8(field), QString::fromUtf8(value))); 314 | } 315 | } 316 | if (m_headerHandler) 317 | m_headerHandler(field, value); 318 | } 319 | 320 | void QHttpMessageStreamParser::callHasBody() 321 | { 322 | // uses an empty QByteArray in callback for dry run 323 | if (m_mode == UNBUFFERED) { 324 | if (m_bodyHandler) 325 | m_bodyHandler(QByteArray()); 326 | return; 327 | } 328 | 329 | QByteArray body = m_currentPacket; 330 | m_currentPacket.clear(); 331 | m_currentPacketSize = 0; 332 | m_contentSize = -1; 333 | 334 | if (m_bodyHandler) 335 | m_bodyHandler(body); 336 | } 337 | 338 | void QHttpMessageStreamParser::errorMessage(QtMsgType error, QString msg) 339 | { 340 | if (m_errorHandler) 341 | m_errorHandler(error, msg); 342 | } 343 | 344 | QT_END_NAMESPACE 345 | -------------------------------------------------------------------------------- /src/jsonrpc/qhttpmessagestreamparser_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QHTTPMESSAGESTREAMPARSER_P_H 5 | #define QHTTPMESSAGESTREAMPARSER_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | QT_BEGIN_NAMESPACE 25 | 26 | class Q_JSONRPC_EXPORT QHttpMessageStreamParser 27 | { 28 | public: 29 | enum class State { 30 | PreHeader, 31 | InHeaderField, 32 | HeaderValueSpace, 33 | InHeaderValue, 34 | AfterCr, 35 | AfterCrLf, 36 | AfterCrLfCr, 37 | InBody 38 | }; 39 | 40 | /*! 41 | * \internal 42 | * \brief Allows to run the FSM with or without keeping any buffers. 43 | */ 44 | enum Mode { BUFFERED, UNBUFFERED }; 45 | 46 | QHttpMessageStreamParser( 47 | std::function headerHandler, 48 | std::function bodyHandler, 49 | std::function errorHandler, Mode mode = BUFFERED); 50 | void receiveData(QByteArray data); 51 | bool receiveEof(); 52 | 53 | State state() const { return m_state; } 54 | 55 | private: 56 | void callHasHeader(); 57 | void callHasBody(); 58 | void errorMessage(QtMsgType error, QString msg); 59 | 60 | std::function m_headerHandler; 61 | std::function m_bodyHandler; 62 | std::function m_errorHandler; 63 | 64 | State m_state = State::PreHeader; 65 | QByteArray m_currentHeaderField; 66 | QByteArray m_currentHeaderValue; 67 | QByteArray m_currentPacket; 68 | int m_contentSize = -1; 69 | int m_currentPacketSize = 0; 70 | Mode m_mode; 71 | }; 72 | 73 | QT_END_NAMESPACE 74 | #endif // QHTTPMESSAGESTREAMPARSER_P_H 75 | -------------------------------------------------------------------------------- /src/jsonrpc/qjsonrpcprotocol.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qjsonrpcprotocol_p_p.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | QT_BEGIN_NAMESPACE 13 | 14 | using namespace Qt::StringLiterals; 15 | 16 | static QJsonObject createResponse(const QJsonValue &id, const QJsonRpcProtocol::Response &response) 17 | { 18 | QJsonObject object; 19 | object.insert(u"jsonrpc", u"2.0"_s); 20 | object.insert(u"id", id); 21 | if (response.errorCode.isDouble()) { 22 | QJsonObject error; 23 | error.insert(u"code", response.errorCode); 24 | error.insert(u"message", response.errorMessage); 25 | if (!response.data.isUndefined()) 26 | error.insert(u"data", response.data); 27 | object.insert(u"error", error); 28 | } else { 29 | object.insert(u"result", response.data); 30 | } 31 | return object; 32 | } 33 | 34 | static QJsonRpcProtocol::Response 35 | createPredefinedError(QJsonRpcProtocol::ErrorCode code, 36 | const QJsonValue &id = QJsonValue::Undefined) 37 | { 38 | QJsonRpcProtocol::Response response; 39 | response.errorCode = static_cast(code); 40 | switch (code) { 41 | case QJsonRpcProtocol::ErrorCode::ParseError: 42 | response.errorMessage = u"Parse error"_s; 43 | break; 44 | case QJsonRpcProtocol::ErrorCode::InvalidRequest: 45 | response.errorMessage = u"Invalid Request"_s; 46 | break; 47 | case QJsonRpcProtocol::ErrorCode::MethodNotFound: 48 | response.errorMessage = u"Method not found"_s; 49 | break; 50 | case QJsonRpcProtocol::ErrorCode::InvalidParams: 51 | response.errorMessage = u"Invalid Parameters"_s; 52 | break; 53 | case QJsonRpcProtocol::ErrorCode::InternalError: 54 | response.errorMessage = u"Internal Error"_s; 55 | break; 56 | } 57 | response.id = id; 58 | return response; 59 | } 60 | 61 | static QJsonObject createParseErrorResponse() 62 | { 63 | return createResponse(QJsonValue::Null, 64 | createPredefinedError(QJsonRpcProtocol::ErrorCode::ParseError)); 65 | } 66 | 67 | static QJsonObject createInvalidRequestResponse(const QJsonValue &id = QJsonValue::Null) 68 | { 69 | return createResponse(id, createPredefinedError(QJsonRpcProtocol::ErrorCode::InvalidRequest)); 70 | } 71 | 72 | static QJsonObject createMethodNotFoundResponse(const QJsonValue &id) 73 | { 74 | return createResponse(id, createPredefinedError(QJsonRpcProtocol::ErrorCode::MethodNotFound)); 75 | } 76 | 77 | template 78 | static QJsonObject createNotification(const Notification ¬ification) 79 | { 80 | QJsonObject object; 81 | object.insert(u"jsonrpc", u"2.0"_s); 82 | object.insert(u"method", notification.method); 83 | object.insert(u"params", notification.params); 84 | return object; 85 | } 86 | 87 | class RequestBatchHandler 88 | { 89 | Q_DISABLE_COPY(RequestBatchHandler) 90 | public: 91 | RequestBatchHandler() = default; 92 | RequestBatchHandler(RequestBatchHandler &&) noexcept = default; 93 | RequestBatchHandler &operator=(RequestBatchHandler &&) noexcept = default; 94 | ~RequestBatchHandler(); 95 | 96 | void processMessages(QJsonRpcProtocolPrivate *protocol, const QJsonArray &messages); 97 | 98 | private: 99 | QJsonArray m_finished; 100 | QJsonRpcTransport *m_transport = nullptr; 101 | uint m_pending = 0; 102 | }; 103 | 104 | QJsonRpcProtocol::QJsonRpcProtocol() : d(std::make_unique()) { } 105 | 106 | QJsonRpcProtocol &QJsonRpcProtocol::operator=(QJsonRpcProtocol &&) noexcept = default; 107 | QJsonRpcProtocol::QJsonRpcProtocol(QJsonRpcProtocol &&) noexcept = default; 108 | QJsonRpcProtocol::~QJsonRpcProtocol() = default; 109 | 110 | void QJsonRpcProtocol::setMessageHandler(const QString &method, 111 | QJsonRpcProtocol::MessageHandler *handler) 112 | { 113 | d->setMessageHandler(method, std::unique_ptr(handler)); 114 | } 115 | 116 | void QJsonRpcProtocol::setDefaultMessageHandler(QJsonRpcProtocol::MessageHandler *handler) 117 | { 118 | d->setDefaultMessageHandler(std::unique_ptr(handler)); 119 | } 120 | 121 | QJsonRpcProtocol::MessageHandler *QJsonRpcProtocol::messageHandler(const QString &method) const 122 | { 123 | return d->messageHandler(method); 124 | } 125 | 126 | QJsonRpcProtocol::MessageHandler *QJsonRpcProtocol::defaultMessageHandler() const 127 | { 128 | return d->defaultMessageHandler(); 129 | } 130 | 131 | template 132 | static QJsonObject createRequest(const Request &request) 133 | { 134 | QJsonObject object; 135 | object.insert(u"jsonrpc", u"2.0"_s); 136 | object.insert(u"id", request.id); 137 | object.insert(u"method", request.method); 138 | object.insert(u"params", request.params); 139 | return object; 140 | } 141 | 142 | void QJsonRpcProtocol::sendRequest(const Request &request, 143 | const QJsonRpcProtocol::Handler &handler) 144 | { 145 | switch (request.id.type()) { 146 | case QJsonValue::Null: 147 | case QJsonValue::Double: 148 | case QJsonValue::String: 149 | if (d->addPendingRequest(request.id, handler)) { 150 | d->sendMessage(createRequest(request)); 151 | return; 152 | } 153 | break; 154 | default: 155 | break; 156 | } 157 | 158 | handler(createPredefinedError(QJsonRpcProtocol::ErrorCode::InvalidRequest, request.id)); 159 | } 160 | 161 | void QJsonRpcProtocol::sendNotification(const QJsonRpcProtocol::Notification ¬ification) 162 | { 163 | d->sendMessage(createNotification(notification)); 164 | } 165 | 166 | void QJsonRpcProtocol::sendBatch( 167 | QJsonRpcProtocol::Batch &&batch, 168 | const QJsonRpcProtocol::Handler &handler) 169 | { 170 | QJsonArray array; 171 | for (BatchPrivate::Item &item : batch.d->m_items) { 172 | if (item.id.isUndefined()) { 173 | array.append(createNotification(item)); 174 | } else { 175 | switch (item.id.type()) { 176 | case QJsonValue::Null: 177 | case QJsonValue::Double: 178 | case QJsonValue::String: 179 | if (d->addPendingRequest(item.id, handler)) { 180 | array.append(createRequest(item)); 181 | break; 182 | } 183 | Q_FALLTHROUGH(); 184 | default: 185 | handler(createPredefinedError(QJsonRpcProtocol::ErrorCode::InvalidRequest, 186 | item.id)); 187 | break; 188 | } 189 | } 190 | } 191 | if (!array.isEmpty()) 192 | d->sendMessage(array); 193 | } 194 | 195 | void QJsonRpcProtocol::setTransport(QJsonRpcTransport *transport) 196 | { 197 | d->setTransport(transport); 198 | } 199 | 200 | void QJsonRpcProtocol::setProtocolErrorHandler(const QJsonRpcProtocol::ResponseHandler &handler) 201 | { 202 | d->setProtocolErrorHandler(handler); 203 | } 204 | 205 | QJsonRpcProtocol::ResponseHandler QJsonRpcProtocol::protocolErrorHandler() const 206 | { 207 | return d->protocolErrorHandler(); 208 | } 209 | 210 | void QJsonRpcProtocol::setInvalidResponseHandler(const QJsonRpcProtocol::ResponseHandler &handler) 211 | { 212 | d->setInvalidResponseHandler(handler); 213 | } 214 | 215 | QJsonRpcProtocol::ResponseHandler QJsonRpcProtocol::invalidResponseHandler() const 216 | { 217 | return d->invalidResponseHandler(); 218 | } 219 | 220 | /*! 221 | * \internal 222 | * \typealias QJsonRpcProtocol::MessagePreprocessor 223 | * \brief A function preprocessing incoming messages 224 | * 225 | * A type representing a function receiving a const QJsonDocument &doc, const QJsonParseError 226 | * &error, const QJsonRpcProtocol::Handler &responseHandler) and returning either 227 | * Processing::Continue or Processing::Stop. Handler can be used to return a response when 228 | * Processing::Stop is returned and doc is a request, i.e. doc.object().contains(u"id") is true. 229 | */ 230 | 231 | QJsonRpcProtocol::MessagePreprocessor QJsonRpcProtocol::messagePreprocessor() const 232 | { 233 | return d->messagePreprocessor(); 234 | } 235 | 236 | void QJsonRpcProtocol::installMessagePreprocessor(const QJsonRpcProtocol::MessagePreprocessor &h) 237 | { 238 | return d->installMessagePreprocessor(h); 239 | } 240 | 241 | void QJsonRpcProtocolPrivate::setTransport(QJsonRpcTransport *newTransport) 242 | { 243 | if (newTransport == m_transport) 244 | return; 245 | 246 | if (m_transport) 247 | m_transport->setMessageHandler(nullptr); 248 | 249 | m_transport = newTransport; 250 | 251 | if (m_transport) { 252 | m_transport->setMessageHandler( 253 | [this](const QJsonDocument &message, const QJsonParseError &error) { 254 | processMessage(message, error); 255 | }); 256 | } 257 | } 258 | 259 | static QJsonRpcProtocol::Request parseRequest(const QJsonObject &object) 260 | { 261 | QJsonRpcProtocol::Request request; 262 | request.id = object.value(u"id"); 263 | request.method = object.value(u"method").toString(); 264 | request.params = object.value(u"params"); 265 | return request; 266 | } 267 | 268 | static QJsonRpcProtocol::Notification parseNotification(const QJsonObject &object) 269 | { 270 | QJsonRpcProtocol::Notification notification; 271 | notification.method = object.value(u"method").toString(); 272 | notification.params = object.value(u"params"); 273 | return notification; 274 | } 275 | 276 | void RequestBatchHandler::processMessages(QJsonRpcProtocolPrivate *protocol, 277 | const QJsonArray &messages) 278 | { 279 | m_transport = protocol->transport(); 280 | 281 | for (const QJsonValue &value : messages) { 282 | if (!value.isObject()) { 283 | m_finished.append(createInvalidRequestResponse()); 284 | continue; 285 | } 286 | 287 | QJsonObject object = value.toObject(); 288 | 289 | if (!object.contains(u"method") || !object.value(u"method").isString()) { 290 | m_finished.append(createInvalidRequestResponse()); 291 | continue; 292 | } 293 | 294 | if (!object.contains(u"id")) { 295 | QJsonRpcProtocol::Notification notification = parseNotification(object); 296 | if (QJsonRpcProtocol::MessageHandler *handler = 297 | protocol->messageHandler(notification.method)) { 298 | handler->handleNotification(notification); 299 | } 300 | continue; 301 | } 302 | 303 | QJsonRpcProtocol::Request request = parseRequest(object); 304 | QJsonRpcProtocol::MessageHandler *handler = protocol->messageHandler(request.method); 305 | if (!handler) { 306 | m_finished.append(createMethodNotFoundResponse(request.id)); 307 | continue; 308 | } 309 | 310 | ++m_pending; 311 | const QJsonValue id = request.id; 312 | handler->handleRequest(request, [this, id](const QJsonRpcProtocol::Response &response) { 313 | m_finished.append(createResponse(id, response)); 314 | bool found = false; 315 | for (QJsonValueRef entry : m_finished) { 316 | if (entry.toObject()[u"id"] == id) { 317 | found = true; 318 | break; 319 | } 320 | } 321 | if (!found) { 322 | m_finished.append(createResponse( 323 | id, 324 | { id, QJsonValue::Undefined, 325 | QJsonValue(static_cast(QJsonRpcProtocol::ErrorCode::InternalError)), 326 | u"Message handler did not produce a result."_s })); 327 | } 328 | 329 | if (--m_pending == 0) 330 | delete this; 331 | }); 332 | } 333 | if (m_pending == 0) 334 | delete this; 335 | } 336 | 337 | RequestBatchHandler::~RequestBatchHandler() 338 | { 339 | if (m_transport && !m_finished.isEmpty()) 340 | m_transport->sendMessage(QJsonDocument(m_finished)); 341 | } 342 | 343 | void QJsonRpcProtocolPrivate::processRequest(const QJsonObject &object) 344 | { 345 | QJsonRpcProtocol::Request request = parseRequest(object); 346 | if (auto handler = messageHandler(request.method)) { 347 | const QJsonValue id = request.id; 348 | handler->handleRequest(request, [id, this](const QJsonRpcProtocol::Response &response) { 349 | sendMessage(createResponse(id, response)); 350 | }); 351 | } else { 352 | sendMessage(createMethodNotFoundResponse(request.id)); 353 | } 354 | } 355 | 356 | void QJsonRpcProtocolPrivate::processResponse(const QJsonObject &object) 357 | { 358 | QJsonRpcProtocol::Response response; 359 | 360 | response.id = object.value(u"id"); 361 | if (object.contains(u"error")) { 362 | const QJsonObject error = object.value(u"error").toObject(); 363 | response.errorCode = error.value(u"code"); 364 | response.errorMessage = error.value(u"message").toString(); 365 | response.data = error.value(u"data"); 366 | } else if (object.contains(u"result")) { 367 | response.data = object.value(u"result"); 368 | } 369 | 370 | auto pending = m_pendingRequests.find(response.id); 371 | if (pending != m_pendingRequests.end()) { 372 | auto handler = pending->second; 373 | m_pendingRequests.erase(pending); 374 | handler(response); 375 | } else if (response.id.isNull()) { 376 | if (m_protocolErrorHandler) 377 | m_protocolErrorHandler(response); 378 | } else { 379 | if (m_invalidResponseHandler) 380 | m_invalidResponseHandler(response); 381 | } 382 | } 383 | 384 | void QJsonRpcProtocolPrivate::processNotification(const QJsonObject &object) 385 | { 386 | QJsonRpcProtocol::Notification notification = parseNotification(object); 387 | if (auto handler = messageHandler(notification.method)) 388 | handler->handleNotification(notification); 389 | } 390 | 391 | void QJsonRpcProtocolPrivate::processMessage(const QJsonDocument &message, 392 | const QJsonParseError &error) 393 | { 394 | if (m_messagePreprocessor 395 | && m_messagePreprocessor(message, error, 396 | [message, this](const QJsonRpcProtocol::Response &r) { 397 | Q_ASSERT(message.object().contains(u"id")); 398 | this->sendMessage(createResponse(message.object()[u"id"], r)); 399 | }) 400 | != QJsonRpcProtocol::Processing::Continue) { 401 | return; 402 | } 403 | if (error.error != QJsonParseError::NoError) { 404 | sendMessage(createParseErrorResponse()); 405 | } else if (message.isObject()) { 406 | const QJsonObject object = message.object(); 407 | if (object.contains(u"method")) { 408 | if (!object.value(u"method").isString()) { 409 | sendMessage(createInvalidRequestResponse()); 410 | } else if (object.contains(u"id")) { 411 | switch (object.value(u"id").type()) { 412 | case QJsonValue::Null: 413 | case QJsonValue::Double: 414 | case QJsonValue::String: 415 | processRequest(object); 416 | break; 417 | default: 418 | sendMessage(createInvalidRequestResponse()); 419 | break; 420 | } 421 | } else { 422 | processNotification(object); 423 | } 424 | } else if (object.contains(u"id")) { 425 | processResponse(object); 426 | } else { 427 | sendMessage(createInvalidRequestResponse()); 428 | } 429 | } else if (message.isArray()) { 430 | const QJsonArray array = message.array(); 431 | if (array.isEmpty()) { 432 | sendMessage(createInvalidRequestResponse()); 433 | return; 434 | } 435 | 436 | for (const QJsonValue &item : array) { 437 | if (!item.isObject()) { 438 | (new RequestBatchHandler)->processMessages(this, array); // Will delete itself 439 | return; 440 | } 441 | 442 | const QJsonObject object = item.toObject(); 443 | // If it's not clearly a response, consider the whole batch as request-y 444 | if (object.contains(u"method") || !object.contains(u"id")) { 445 | (new RequestBatchHandler)->processMessages(this, array); // Will delete itself 446 | return; 447 | } 448 | } 449 | 450 | // As we haven't returned above, we can consider them all responseses now. 451 | for (const QJsonValue &item : array) 452 | processResponse(item.toObject()); 453 | 454 | } else { 455 | sendMessage(createInvalidRequestResponse()); 456 | } 457 | } 458 | 459 | QJsonRpcProtocol::MessageHandler::MessageHandler() = default; 460 | QJsonRpcProtocol::MessageHandler::~MessageHandler() = default; 461 | 462 | void QJsonRpcProtocol::MessageHandler::handleRequest(const QJsonRpcProtocol::Request &request, 463 | const ResponseHandler &handler) 464 | { 465 | Q_UNUSED(request); 466 | handler(error(QJsonRpcProtocol::ErrorCode::MethodNotFound)); 467 | } 468 | 469 | void QJsonRpcProtocol::MessageHandler::handleNotification( 470 | const QJsonRpcProtocol::Notification ¬ification) 471 | { 472 | Q_UNUSED(notification); 473 | } 474 | 475 | QJsonRpcProtocol::Response QJsonRpcProtocol::MessageHandler::error(QJsonRpcProtocol::ErrorCode code) 476 | { 477 | return createPredefinedError(code); 478 | } 479 | 480 | QJsonRpcProtocol::Response QJsonRpcProtocol::MessageHandler::error(int code, const QString &message, 481 | const QJsonValue &data) 482 | { 483 | QJsonRpcProtocol::Response response; 484 | response.errorCode = code; 485 | response.errorMessage = message; 486 | response.data = data; 487 | return response; 488 | } 489 | 490 | QJsonRpcProtocol::Response QJsonRpcProtocol::MessageHandler::result(const QJsonValue &result) 491 | { 492 | QJsonRpcProtocol::Response response; 493 | response.data = result; 494 | return response; 495 | } 496 | 497 | QJsonRpcProtocol::Batch::Batch() : d(std::make_unique()) { } 498 | QJsonRpcProtocol::Batch::~Batch() = default; 499 | QJsonRpcProtocol::Batch::Batch(QJsonRpcProtocol::Batch &&) noexcept = default; 500 | QJsonRpcProtocol::Batch & 501 | QJsonRpcProtocol::Batch::operator=(QJsonRpcProtocol::Batch &&) noexcept = default; 502 | 503 | void QJsonRpcProtocol::Batch::addNotification(const Notification ¬ification) 504 | { 505 | BatchPrivate::Item item; 506 | item.method = notification.method; 507 | item.params = notification.params; 508 | d->m_items.push_back(std::move(item)); 509 | } 510 | 511 | void QJsonRpcProtocol::Batch::addRequest(const Request &request) 512 | { 513 | BatchPrivate::Item item; 514 | item.id = request.id; 515 | item.method = request.method; 516 | item.params = request.params; 517 | d->m_items.push_back(std::move(item)); 518 | } 519 | 520 | QT_END_NAMESPACE 521 | -------------------------------------------------------------------------------- /src/jsonrpc/qjsonrpcprotocol_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QJSONRPCPROTOCOL_P_H 5 | #define QJSONRPCPROTOCOL_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | QT_BEGIN_NAMESPACE 25 | 26 | class QLaguageServer; 27 | class QJsonRpcTransport; 28 | class QJsonRpcProtocolPrivate; 29 | class Q_JSONRPC_EXPORT QJsonRpcProtocol 30 | { 31 | Q_DISABLE_COPY(QJsonRpcProtocol) 32 | public: 33 | QJsonRpcProtocol(); 34 | QJsonRpcProtocol(QJsonRpcProtocol &&) noexcept; 35 | QJsonRpcProtocol &operator=(QJsonRpcProtocol &&) noexcept; 36 | 37 | ~QJsonRpcProtocol(); 38 | 39 | enum class ErrorCode { 40 | ParseError = -32700, 41 | InvalidRequest = -32600, 42 | MethodNotFound = -32601, 43 | InvalidParams = -32602, 44 | InternalError = -32603, 45 | }; 46 | 47 | struct Request 48 | { 49 | QJsonValue id = QJsonValue::Undefined; 50 | QString method; 51 | QJsonValue params = QJsonValue::Undefined; 52 | }; 53 | 54 | struct Response 55 | { 56 | // ID is disregarded on responses generated from MessageHandlers. 57 | // You cannot reply to a request you didn't handle. The original request ID is used instead. 58 | QJsonValue id = QJsonValue::Undefined; 59 | 60 | // In case of !errorCode.isDouble(), data is the "data" member of the error object. 61 | // Otherwise it's the "result" member of the base response. 62 | QJsonValue data = QJsonValue::Undefined; 63 | 64 | // Error codes from and including -32768 to -32000 are reserved for pre-defined errors. 65 | // Don't use them for your own protocol. We cannot enforce this here. 66 | QJsonValue errorCode = QJsonValue::Undefined; 67 | 68 | QString errorMessage = QString(); 69 | }; 70 | 71 | template 72 | using Handler = std::function; 73 | 74 | struct Notification 75 | { 76 | QString method; 77 | QJsonValue params = QJsonValue::Undefined; 78 | }; 79 | 80 | class Q_JSONRPC_EXPORT MessageHandler 81 | { 82 | Q_DISABLE_COPY_MOVE(MessageHandler) 83 | public: 84 | using ResponseHandler = std::function; 85 | 86 | MessageHandler(); 87 | virtual ~MessageHandler(); 88 | virtual void handleRequest(const Request &request, const ResponseHandler &handler); 89 | virtual void handleNotification(const Notification ¬ification); 90 | 91 | static Response error(ErrorCode code); 92 | static Response error(int code, const QString &message, 93 | const QJsonValue &data = QJsonValue::Undefined); 94 | 95 | protected: 96 | static Response result(const QJsonValue &result); 97 | }; 98 | 99 | class BatchPrivate; 100 | class Q_JSONRPC_EXPORT Batch 101 | { 102 | Q_DISABLE_COPY(Batch) 103 | public: 104 | Batch(); 105 | ~Batch(); 106 | 107 | Batch(Batch &&) noexcept; 108 | Batch &operator=(Batch &&) noexcept; 109 | 110 | void addNotification(const Notification ¬ification); 111 | void addRequest(const Request &request); 112 | 113 | private: 114 | friend class QJsonRpcProtocol; 115 | std::unique_ptr d; 116 | }; 117 | 118 | void setMessageHandler(const QString &method, MessageHandler *handler); 119 | void setDefaultMessageHandler(MessageHandler *handler); 120 | MessageHandler *messageHandler(const QString &method) const; 121 | MessageHandler *defaultMessageHandler() const; 122 | 123 | void sendRequest(const Request &request, const QJsonRpcProtocol::Handler &handler); 124 | void sendNotification(const Notification ¬ification); 125 | void sendBatch(Batch &&batch, const QJsonRpcProtocol::Handler &handler); 126 | 127 | void setTransport(QJsonRpcTransport *transport); 128 | 129 | // For id:null responses 130 | using ResponseHandler = std::function; 131 | void setProtocolErrorHandler(const ResponseHandler &handler); 132 | ResponseHandler protocolErrorHandler() const; 133 | 134 | // For responses with unknown IDs 135 | void setInvalidResponseHandler(const ResponseHandler &handler); 136 | ResponseHandler invalidResponseHandler() const; 137 | 138 | enum class Processing { Continue, Stop }; 139 | using MessagePreprocessor = 140 | std::function &handler)>; 142 | MessagePreprocessor messagePreprocessor() const; 143 | void installMessagePreprocessor(const MessagePreprocessor &preHandler); 144 | 145 | private: 146 | std::unique_ptr d; 147 | }; 148 | 149 | QT_END_NAMESPACE 150 | 151 | #endif // QJSONRPCPROTOCOL_P_H 152 | -------------------------------------------------------------------------------- /src/jsonrpc/qjsonrpcprotocol_p_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QJSONRPCPROTOCOL_P_P_H 5 | #define QJSONRPCPROTOCOL_P_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | QT_BEGIN_NAMESPACE 28 | 29 | template 30 | struct QHasher 31 | { 32 | using argument_type = T; 33 | using result_type = size_t; 34 | result_type operator()(const argument_type &value) const { return qHash(value); } 35 | }; 36 | 37 | class QJsonRpcProtocolPrivate 38 | { 39 | public: 40 | template 41 | using Map = std::unordered_map>; 42 | 43 | using ResponseHandler = QJsonRpcProtocol::ResponseHandler; 44 | using MessageHandler = QJsonRpcProtocol::MessageHandler; 45 | using OwnedMessageHandler = std::unique_ptr; 46 | using MessageHandlerMap = Map; 47 | using ResponseMap = Map>; 48 | 49 | void processMessage(const QJsonDocument &message, const QJsonParseError &error); 50 | void processError(const QString &error); 51 | 52 | template 53 | void sendMessage(const JSON &value) 54 | { 55 | m_transport->sendMessage(QJsonDocument(value)); 56 | } 57 | 58 | void processRequest(const QJsonObject &object); 59 | void processResponse(const QJsonObject &object); 60 | void processNotification(const QJsonObject &object); 61 | 62 | MessageHandler *messageHandler(const QString &method) const 63 | { 64 | auto it = m_messageHandlers.find(method); 65 | return it != m_messageHandlers.end() ? it->second.get() : m_defaultHandler.get(); 66 | } 67 | 68 | void setMessageHandler(const QString &method, OwnedMessageHandler handler) 69 | { 70 | m_messageHandlers[method] = std::move(handler); 71 | } 72 | 73 | MessageHandler *defaultMessageHandler() const { return m_defaultHandler.get(); } 74 | void setDefaultMessageHandler(OwnedMessageHandler handler) 75 | { 76 | m_defaultHandler = std::move(handler); 77 | } 78 | 79 | bool addPendingRequest(const QJsonValue &id, 80 | QJsonRpcProtocol::Handler handler) 81 | { 82 | auto it = m_pendingRequests.find(id); 83 | if (it == m_pendingRequests.end()) { 84 | m_pendingRequests.insert(std::make_pair(id, std::move(handler))); 85 | return true; 86 | } 87 | return false; 88 | } 89 | 90 | void setTransport(QJsonRpcTransport *newTransport); 91 | QJsonRpcTransport *transport() const { return m_transport; } 92 | 93 | ResponseHandler invalidResponseHandler() const { return m_invalidResponseHandler; } 94 | void setInvalidResponseHandler(ResponseHandler handler) 95 | { 96 | m_invalidResponseHandler = std::move(handler); 97 | } 98 | 99 | ResponseHandler protocolErrorHandler() const { return m_protocolErrorHandler; } 100 | void setProtocolErrorHandler(ResponseHandler handler) 101 | { 102 | m_protocolErrorHandler = std::move(handler); 103 | } 104 | 105 | QJsonRpcProtocol::MessagePreprocessor messagePreprocessor() const 106 | { 107 | return m_messagePreprocessor; 108 | } 109 | void installMessagePreprocessor(QJsonRpcProtocol::MessagePreprocessor preHandler) 110 | { 111 | m_messagePreprocessor = std::move(preHandler); 112 | } 113 | 114 | private: 115 | ResponseMap m_pendingRequests; 116 | MessageHandlerMap m_messageHandlers; 117 | 118 | OwnedMessageHandler m_defaultHandler; 119 | 120 | QJsonRpcTransport *m_transport = nullptr; 121 | 122 | ResponseHandler m_protocolErrorHandler; 123 | ResponseHandler m_invalidResponseHandler; 124 | QJsonRpcProtocol::MessagePreprocessor m_messagePreprocessor; 125 | }; 126 | 127 | class QJsonRpcProtocol::BatchPrivate 128 | { 129 | public: 130 | struct Item 131 | { 132 | QJsonValue id = QJsonValue::Undefined; 133 | QString method; 134 | QJsonValue params = QJsonValue::Undefined; 135 | }; 136 | 137 | std::vector m_items; 138 | }; 139 | 140 | QT_END_NAMESPACE 141 | 142 | #endif // QJSONRPCPROTOCOL_P_P_H 143 | -------------------------------------------------------------------------------- /src/jsonrpc/qjsonrpctransport_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QJSONRPCTRANSPORT_H 5 | #define QJSONRPCTRANSPORT_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | QT_BEGIN_NAMESPACE 23 | 24 | class Q_JSONRPC_EXPORT QJsonRpcTransport 25 | { 26 | Q_DISABLE_COPY_MOVE(QJsonRpcTransport) 27 | public: 28 | enum DiagnosticLevel { Warning, Error }; 29 | 30 | using MessageHandler = std::function; 31 | using DataHandler = std::function; 32 | using DiagnosticHandler = std::function; 33 | 34 | QJsonRpcTransport() = default; 35 | virtual ~QJsonRpcTransport() = default; 36 | 37 | // Parse data and call messageHandler for any messages found in it. 38 | virtual void receiveData(const QByteArray &data) = 0; 39 | 40 | // serialize the message and call dataHandler for the resulting data. 41 | // Needs to be guarded by a mutex if called by different threads 42 | virtual void sendMessage(const QJsonDocument &packet) = 0; 43 | 44 | void setMessageHandler(const MessageHandler &handler) { m_messageHandler = handler; } 45 | MessageHandler messageHandler() const { return m_messageHandler; } 46 | 47 | void setDataHandler(const DataHandler &handler) { m_dataHandler = handler; } 48 | DataHandler dataHandler() const { return m_dataHandler; } 49 | 50 | void setDiagnosticHandler(const DiagnosticHandler &handler) { m_diagnosticHandler = handler; } 51 | DiagnosticHandler diagnosticHandler() const { return m_diagnosticHandler; } 52 | 53 | private: 54 | MessageHandler m_messageHandler; 55 | DataHandler m_dataHandler; 56 | DiagnosticHandler m_diagnosticHandler; 57 | }; 58 | 59 | QT_END_NAMESPACE 60 | 61 | #endif // QJSONRPCTRANSPORT_H 62 | -------------------------------------------------------------------------------- /src/jsonrpc/qjsontypedrpc.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | #include "qjsontypedrpc_p.h" 4 | #include 5 | 6 | QT_BEGIN_NAMESPACE 7 | 8 | namespace QJsonRpc { 9 | void TypedResponse::addOnCloseAction(const OnCloseAction &act) 10 | { 11 | switch (m_status) { 12 | case Status::Started: 13 | m_onCloseActions.append(act); 14 | break; 15 | case Status::Invalid: 16 | qCWarning(QTypedJson::jsonRpcLog) 17 | << "addOnCloseAction called on moved QJsonTypedResponse" << idToString(m_id); 18 | Q_ASSERT(false); 19 | Q_FALLTHROUGH(); 20 | case Status::SentSuccess: 21 | case Status::SentError: 22 | act(m_status, m_id, *m_typedRpc); 23 | break; 24 | } 25 | } 26 | 27 | void TypedResponse::doOnCloseActions() 28 | { 29 | m_typedRpc->doOnCloseAction(m_status, m_id); 30 | for (const auto &a : m_onCloseActions) { 31 | a(m_status, m_id, *m_typedRpc); 32 | } 33 | m_onCloseActions.clear(); 34 | } 35 | 36 | void TypedResponse::sendErrorResponse(int code, const QByteArray &message) 37 | { 38 | sendErrorResponse>(code, message, std::optional()); 39 | } 40 | 41 | void TypedRpc::installOnCloseAction(const TypedResponse::OnCloseAction &closeAction) 42 | { 43 | m_onCloseAction = closeAction; 44 | } 45 | 46 | TypedResponse::OnCloseAction TypedRpc::onCloseAction() 47 | { 48 | return m_onCloseAction; 49 | } 50 | 51 | void TypedRpc::doOnCloseAction(TypedResponse::Status status, const IdType &id) 52 | { 53 | if (m_onCloseAction) 54 | m_onCloseAction(status, id, *this); 55 | } 56 | 57 | } // namespace QJsonRpc 58 | 59 | QT_END_NAMESPACE 60 | -------------------------------------------------------------------------------- /src/jsonrpc/qjsontypedrpc_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QJSONTYPEDRPC_P_H 5 | #define QJSONTYPEDRPC_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | QT_BEGIN_NAMESPACE 26 | 27 | namespace QJsonRpc { 28 | class TypedRpc; 29 | 30 | using IdType = std::variant; 31 | 32 | template 33 | QString idToString(const std::variant &v) 34 | { 35 | struct ToStr 36 | { 37 | QString operator()(const QByteArray &v) { return QString::fromUtf8(v); } 38 | 39 | QString operator()(int v) { return QString::number(v); } 40 | 41 | QString operator()(std::nullptr_t) { return QStringLiteral("null"); } 42 | } toStr; 43 | return std::visit(toStr, v); 44 | } 45 | 46 | // concurrent usage by multiple threads not supported, the user should take care 47 | class Q_JSONRPC_EXPORT TypedResponse 48 | { 49 | Q_DISABLE_COPY(TypedResponse) 50 | public: 51 | enum class Status { Started, SentSuccess, SentError, Invalid }; 52 | TypedResponse() = default; 53 | 54 | TypedResponse(IdType id, TypedRpc *typedRpc, 55 | const QJsonRpcProtocol::ResponseHandler &responseHandler, 56 | Status status = Status::Started) 57 | : m_status(status), m_id(id), m_typedRpc(typedRpc), m_responseHandler(responseHandler) 58 | { 59 | } 60 | TypedResponse(TypedResponse &&o) 61 | : m_status(o.m_status), 62 | m_id(o.m_id), 63 | m_typedRpc(o.m_typedRpc), 64 | m_responseHandler(std::move(o.m_responseHandler)) 65 | { 66 | o.m_status = Status::Invalid; 67 | } 68 | TypedResponse &operator=(TypedResponse &&o) noexcept 69 | { 70 | m_status = o.m_status; 71 | m_id = o.m_id; 72 | m_typedRpc = o.m_typedRpc; 73 | m_responseHandler = std::move(o.m_responseHandler); 74 | o.m_status = Status::Invalid; 75 | return *this; 76 | } 77 | ~TypedResponse() 78 | { 79 | if (m_status == Status::Started) 80 | sendErrorResponse(int(QJsonRpcProtocol::ErrorCode::InternalError), 81 | QByteArray("Response destroyed before having sent a response"), 82 | nullptr); 83 | } 84 | 85 | template 86 | void sendSuccessfullResponse(const T &result); 87 | template 88 | void sendErrorResponse(int code, const QByteArray &message, const T &data); 89 | void sendErrorResponse(int code, const QByteArray &message); 90 | template 91 | void sendNotification(const QByteArray &method, const Params &...params); 92 | 93 | IdType id() const { return m_id; } 94 | QString idStr() 95 | { 96 | if (const int *iPtr = std::get_if(&m_id)) 97 | return QString::number(*iPtr); 98 | else if (const QByteArray *bPtr = std::get_if(&m_id)) 99 | return QString::fromUtf8(*bPtr); 100 | else 101 | return QString(); 102 | } 103 | using OnCloseAction = std::function; 104 | void addOnCloseAction(const OnCloseAction &act); 105 | 106 | private: 107 | void doOnCloseActions(); 108 | Status m_status = Status::Invalid; 109 | IdType m_id; 110 | TypedRpc *m_typedRpc = nullptr; 111 | QJsonRpcProtocol::ResponseHandler m_responseHandler; 112 | QList m_onCloseActions; 113 | }; 114 | 115 | class Q_JSONRPC_EXPORT TypedHandler : public QJsonRpcProtocol::MessageHandler 116 | { 117 | public: 118 | TypedHandler() = default; // invalid instance 119 | TypedHandler(const QByteArray &method, 120 | const std::function &rHandler, 122 | const std::function &nHandler) 123 | : m_method(method), m_requestHandler(rHandler), m_notificationHandler(nHandler) 124 | { 125 | } 126 | 127 | TypedHandler(const QByteArray &method, 128 | const std::function &rHandler) 130 | : m_method(method), m_requestHandler(rHandler), m_notificationHandler() 131 | { 132 | } 133 | 134 | TypedHandler(const QByteArray &method, 135 | const std::function &nHandler) 136 | : m_method(method), m_requestHandler(), m_notificationHandler(nHandler) 137 | { 138 | } 139 | 140 | ~TypedHandler() = default; 141 | 142 | void handleRequest(const QJsonRpcProtocol::Request &request, 143 | const QJsonRpcProtocol::ResponseHandler &handler) override 144 | { 145 | using namespace Qt::StringLiterals; 146 | 147 | if (m_requestHandler) { 148 | m_requestHandler(request, handler); 149 | return; 150 | } 151 | QString msg; 152 | if (m_notificationHandler) 153 | msg = u"Expected notification with method '%1', not request"_s; 154 | else 155 | msg = u"Reached null handler for method '%1'"_s; 156 | msg = msg.arg(request.method); 157 | handler(MessageHandler::error(int(QJsonRpcProtocol::ErrorCode::InvalidRequest), msg)); 158 | qCWarning(QTypedJson::jsonRpcLog) << msg; 159 | } 160 | 161 | QByteArray method() const { return m_method; } 162 | 163 | void handleNotification(const QJsonRpcProtocol::Notification ¬ification) override 164 | { 165 | if (m_notificationHandler) { 166 | m_notificationHandler(notification); 167 | return; 168 | } 169 | if (m_requestHandler) 170 | qCWarning(QTypedJson::jsonRpcLog) << "Expected Request but got notification for " 171 | << notification.method << ", ignoring it."; 172 | else 173 | qCWarning(QTypedJson::jsonRpcLog) 174 | << "Reached null handler for method " << notification.method; 175 | } 176 | 177 | private: 178 | QByteArray m_method; 179 | std::function 181 | m_requestHandler; 182 | std::function m_notificationHandler; 183 | }; 184 | 185 | class Q_JSONRPC_EXPORT TypedRpc : public QJsonRpcProtocol 186 | { 187 | Q_DISABLE_COPY_MOVE(TypedRpc) 188 | public: 189 | TypedRpc() = default; 190 | 191 | template 192 | void sendRequestId(const std::variant &id, const QByteArray &method, 193 | const QJsonRpcProtocol::Handler &rHandler, 194 | const Params &...params) 195 | { 196 | QJsonRpcProtocol::sendRequest(Request { QTypedJson::toJsonValue(id), 197 | QString::fromUtf8(method), 198 | QTypedJson::toJsonValue(params...) }, 199 | rHandler); 200 | } 201 | 202 | template 203 | void sendRequest(const QByteArray &method, 204 | const QJsonRpcProtocol::Handler &handler, 205 | const Params &...params) 206 | { 207 | sendRequestId(++m_lastId, method, handler, params...); 208 | } 209 | 210 | template 211 | void sendNotification(const QByteArray &method, const Params &...params) 212 | { 213 | QJsonRpcProtocol::sendNotification( 214 | Notification { QString::fromUtf8(method), QTypedJson::toJsonValue(params...) }); 215 | } 216 | 217 | template 218 | void registerRequestHandler( 219 | const QByteArray &method, 220 | const std::function &handler) 221 | { 222 | if (m_handlers.contains(method) && handler) { 223 | qCWarning(QTypedJson::jsonRpcLog) 224 | << "QJsonRpc double registration for method" << QString::fromUtf8(method); 225 | Q_ASSERT(false); 226 | return; 227 | } 228 | TypedHandler *h; 229 | if (handler) 230 | h = new TypedHandler( 231 | method, 232 | [handler, method, this](const QJsonRpcProtocol::Request &req, 233 | const QJsonRpcProtocol::ResponseHandler &rH) { 234 | std::variant id = req.id.toInt(0); 235 | if (req.id.isString()) 236 | id = req.id.toString().toUtf8(); 237 | TypedResponse typedResponse(id, this, rH); 238 | Req tReq; 239 | { 240 | QTypedJson::Reader r(req.params); 241 | QTypedJson::doWalk(r, tReq); 242 | if (!r.errorMessages().isEmpty()) { 243 | qCWarning(QTypedJson::jsonRpcLog) 244 | << "Warnings decoding parameters for Request" << method 245 | << idToString(id) << "from" << req.params << ":\n " 246 | << r.errorMessages().join(u"\n "); 247 | r.clearErrorMessages(); 248 | } 249 | } 250 | Resp myResponse(std::move(typedResponse)); 251 | handler(method, tReq, std::move(myResponse)); 252 | }); 253 | else 254 | h = new TypedHandler; 255 | m_handlers[method] = h; 256 | setMessageHandler(QString::fromUtf8(method), h); 257 | } 258 | 259 | template 260 | void registerNotificationHandler( 261 | const QByteArray &method, 262 | const std::function &handler) 263 | { 264 | if (m_handlers.contains(method) && handler) { 265 | qCWarning(QTypedJson::jsonRpcLog) 266 | << "QJsonRpc double registration for method" << QString::fromUtf8(method); 267 | Q_ASSERT(false); 268 | return; 269 | } 270 | TypedHandler *h; 271 | if (handler) 272 | h = new TypedHandler( 273 | method, [handler, method](const QJsonRpcProtocol::Notification ¬if) { 274 | N tNotif; 275 | { 276 | QTypedJson::Reader r(notif.params); 277 | QTypedJson::doWalk(r, tNotif); 278 | if (!r.errorMessages().isEmpty()) { 279 | qCWarning(QTypedJson::jsonRpcLog) 280 | << "Warnings decoding parameters for Notification" << method 281 | << "from" << notif.params << ":\n " 282 | << r.errorMessages().join(u"\n "); 283 | r.clearErrorMessages(); 284 | } 285 | } 286 | handler(method, tNotif); 287 | }); 288 | else 289 | h = new TypedHandler; 290 | setMessageHandler(QString::fromUtf8(method), h); 291 | m_handlers[method] = h; 292 | } 293 | 294 | void sendBatch( 295 | QJsonRpcProtocol::Batch &&batch, 296 | const QJsonRpcProtocol::Handler &handler) 297 | = delete; // disable batch support 298 | void installOnCloseAction(const TypedResponse::OnCloseAction &closeAction); 299 | TypedResponse::OnCloseAction onCloseAction(); 300 | void doOnCloseAction(TypedResponse::Status, const IdType &); 301 | 302 | private: 303 | QAtomicInt m_lastId; 304 | QHash m_handlers; 305 | TypedResponse::OnCloseAction m_onCloseAction; 306 | }; 307 | 308 | template 309 | void TypedResponse::sendSuccessfullResponse(const T &result) 310 | { 311 | if (m_status == Status::Started) { 312 | m_status = Status::SentSuccess; 313 | m_responseHandler(QJsonRpcProtocol::Response { 314 | QTypedJson::toJsonValue(m_id), 315 | QTypedJson::toJsonValue(result) 316 | }); 317 | doOnCloseActions(); 318 | } else { 319 | qCWarning(QTypedJson::jsonRpcLog) 320 | << "Ignoring response in already answered request" << idStr(); 321 | } 322 | } 323 | 324 | template 325 | void TypedResponse::sendErrorResponse(int code, const QByteArray &message, const T &data) 326 | { 327 | if (m_status == Status::Started) { 328 | m_status = Status::SentError; 329 | m_responseHandler(QJsonRpcProtocol::Response { QTypedJson::toJsonValue(m_id), 330 | QTypedJson::toJsonValue(data), code, 331 | QString::fromUtf8(message) }); 332 | doOnCloseActions(); 333 | } else { 334 | qCWarning(QTypedJson::jsonRpcLog) 335 | << "Ignoring error response" << code << QString::fromUtf8(message) 336 | << "in already answered request" << idStr(); 337 | } 338 | } 339 | 340 | template 341 | void TypedResponse::sendNotification(const QByteArray &method, const Params &...params) 342 | { 343 | m_typedRpc->sendNotification(method, params...); 344 | } 345 | } // namespace QTypedJson 346 | QT_END_NAMESPACE 347 | 348 | #endif // QJSONTYPEDRPC_P_H 349 | -------------------------------------------------------------------------------- /src/jsonrpc/qtjsonrpcglobal.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QTJSONRPCGLOBAL_H 5 | #define QTJSONRPCGLOBAL_H 6 | 7 | #include 8 | 9 | // include qtjsonrpcexports.h here instead 10 | // if module is no longer unconditionally static 11 | #define Q_JSONRPC_EXPORT 12 | 13 | #endif // QTJSONRPCGLOBAL_H 14 | -------------------------------------------------------------------------------- /src/jsonrpc/qtypedjson.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qtypedjson_p.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | QT_BEGIN_NAMESPACE 12 | 13 | namespace QTypedJson { 14 | 15 | Q_LOGGING_CATEGORY(jsonRpcLog, "qt.jsonrpc"); 16 | 17 | Reader::Reader(const QJsonValue &v) 18 | : m_p(new ReaderPrivate { QList({ ValueStack { v, QString(), -1, 0 } }) }) 19 | { 20 | } 21 | 22 | Reader::~Reader() 23 | { 24 | for (const QString &msg : m_p->errorMessages) 25 | qCWarning(jsonRpcLog) << msg; 26 | delete m_p; 27 | } 28 | 29 | QStringList Reader::errorMessages() 30 | { 31 | return m_p->errorMessages; 32 | } 33 | 34 | void Reader::clearErrorMessages() 35 | { 36 | m_p->errorMessages.clear(); 37 | } 38 | 39 | void Reader::handleBasic(bool &el) 40 | { 41 | if (currentValue().isBool()) 42 | el = currentValue().toBool(); 43 | else 44 | warnMissing(u"bool"); 45 | } 46 | 47 | void Reader::handleBasic(QByteArray &el) 48 | { 49 | if (currentValue().isString()) 50 | el = currentValue().toString().toUtf8(); 51 | else 52 | warnMissing(u"string"); 53 | } 54 | 55 | void Reader::handleBasic(int &el) 56 | { 57 | if (currentValue().isDouble()) 58 | el = currentValue().toInt(el); 59 | else 60 | warnMissing(u"int"); 61 | } 62 | 63 | void Reader::handleBasic(double &el) 64 | { 65 | if (currentValue().isDouble()) 66 | el = currentValue().toDouble(); 67 | else 68 | warnMissing(u"double"); 69 | } 70 | 71 | void Reader::handleNullType() 72 | { 73 | if (!currentValue().isNull() && !currentValue().isUndefined()) { 74 | warnNonNull(); 75 | } 76 | } 77 | 78 | bool Reader::startField(const QString &fieldName) 79 | { 80 | int oldWarnLevel = (m_p->valuesStack.isEmpty() ? 0 : m_p->valuesStack.last().warnLevel); 81 | m_p->objectsStack.last().visitedFields.insert(fieldName); 82 | m_p->valuesStack.append(ValueStack { currentValue()[fieldName], fieldName, -1, 83 | (oldWarnLevel ? oldWarnLevel + 1 : 0) }); 84 | return true; 85 | } 86 | 87 | bool Reader::startField(const char *fieldName) 88 | { 89 | QString f = QString::fromUtf8(fieldName); // conversion needed just to set the fieldPath 90 | return startField(f); 91 | } 92 | 93 | void Reader::endField(const QString &fieldName) 94 | { 95 | Q_ASSERT(m_p->valuesStack.last().fieldPath == fieldName); 96 | m_p->valuesStack.removeLast(); 97 | } 98 | 99 | void Reader::endField(const char *fieldName) 100 | { 101 | QString f = QString::fromUtf8(fieldName); 102 | endField(f); 103 | } 104 | 105 | bool Reader::startObjectF(const char *type, ObjectOptions options, quintptr) 106 | { 107 | if (m_p->parseStatus != ParseStatus::Normal) 108 | return false; 109 | if (currentValue().isUndefined()) { 110 | m_p->parseStatus = ParseStatus::Failed; 111 | return false; 112 | } 113 | m_p->objectsStack.append(ObjectStack { type, options, {} }); 114 | return true; 115 | } 116 | 117 | void Reader::endObjectF(const char *type, ObjectOptions, quintptr) 118 | { 119 | Q_ASSERT(std::strcmp(m_p->objectsStack.last().type, type) == 0); 120 | m_p->objectsStack.removeLast(); 121 | } 122 | 123 | void Reader::warnExtra(const QJsonObject &e) 124 | { 125 | if (e.constBegin() != e.constEnd()) 126 | warn(QStringLiteral(u"%1 has extra fields %2") 127 | .arg(currentPath(), QString::fromUtf8(QJsonDocument(e).toJson()))); 128 | } 129 | 130 | void Reader::warnInvalidSize(qint32 size, qint32 expectedSize) 131 | { 132 | if (size != expectedSize) 133 | warn(QStringLiteral(u"%1 expected %1 elements, not %2.") 134 | .arg(currentPath(), QString::number(expectedSize), QString::number(size))); 135 | } 136 | 137 | void Reader::warnMissing(QStringView s) 138 | { 139 | warn(QStringLiteral(u"%1 misses value of type %2").arg(currentPath(), s)); 140 | } 141 | 142 | void Reader::warnNonNull() 143 | { 144 | QByteArray val = QJsonDocument(QJsonArray({ currentValue() })).toJson(); 145 | warn(QStringLiteral(u"%1 is supposed to be null, but is %2") 146 | .arg(currentPath(), QString::fromUtf8(val.mid(1, val.size() - 2)))); 147 | } 148 | 149 | void Reader::warn(const QString &msg) 150 | { 151 | m_p->errorMessages.append(msg); 152 | m_p->parseStatus = ParseStatus::Failed; 153 | } 154 | 155 | void Reader::handleJson(QJsonValue &v) 156 | { 157 | v = currentValue(); 158 | } 159 | 160 | void Reader::handleJson(QJsonObject &v) 161 | { 162 | if (!currentValue().isObject() && !currentValue().isNull() && !currentValue().isUndefined()) { 163 | QByteArray val = QJsonDocument(QJsonArray({ currentValue() })).toJson(); 164 | warn(QStringLiteral(u"Error: expected an object at %1, not %2") 165 | .arg(currentPath(), QString::fromUtf8(val.mid(1, val.size() - 2)))); 166 | } 167 | v = currentValue().toObject(); 168 | } 169 | 170 | void Reader::handleJson(QJsonArray &v) 171 | { 172 | if (!currentValue().isArray() && !currentValue().isNull() && !currentValue().isUndefined()) { 173 | QByteArray val = QJsonDocument(QJsonArray({ currentValue() })).toJson(); 174 | warn(QStringLiteral(u"Error: expected an array at %1, not %2") 175 | .arg(currentPath(), QString::fromUtf8(val.mid(1, val.size() - 2)))); 176 | } 177 | v = currentValue().toArray(); 178 | } 179 | 180 | QJsonObject Reader::getExtraFields() const 181 | { 182 | QJsonObject extraFields; 183 | QJsonObject v = currentValue().toObject(); 184 | auto it = v.constBegin(); 185 | auto end = v.constEnd(); 186 | auto &vField = m_p->objectsStack.last().visitedFields; 187 | while (it != end) { 188 | if (!vField.contains(it.key())) { 189 | extraFields.insert(it.key(), it.value()); 190 | } 191 | ++it; 192 | } 193 | return extraFields; 194 | } 195 | 196 | void Reader::startArrayF(qint32 &size) 197 | { 198 | size = int(currentValue().toArray().size()); 199 | } 200 | 201 | bool Reader::startElement(qint32 index) 202 | { 203 | int oldWarnLevel = (m_p->valuesStack.isEmpty() ? 0 : m_p->valuesStack.last().warnLevel); 204 | m_p->valuesStack.append(ValueStack { currentValue().toArray().at(index), QString(), index, 205 | (oldWarnLevel ? oldWarnLevel + 1 : 0) }); 206 | return true; 207 | } 208 | 209 | void Reader::endElement(qint32 index) 210 | { 211 | Q_ASSERT(m_p->valuesStack.last().indexPath == index); 212 | m_p->valuesStack.removeLast(); 213 | } 214 | 215 | void Reader::endArrayF(qint32 &) { } 216 | 217 | QString Reader::currentPath() const 218 | { 219 | QStringList res; 220 | for (const auto &el : std::as_const(m_p->valuesStack)) { 221 | if (el.indexPath != -1) 222 | res.append(QString::number(el.indexPath)); 223 | else 224 | res.append(el.fieldPath); 225 | } 226 | return res.join(u"."); 227 | } 228 | 229 | bool Reader::startTuple(qint32 size) 230 | { 231 | qint32 expected = qint32(currentValue().toArray().size()); 232 | if (size != expected) { 233 | warnInvalidSize(size, expected); 234 | return false; 235 | }; 236 | return true; 237 | } 238 | 239 | void Reader::endTuple(qint32) { } 240 | 241 | void JsonBuilder::handleBasic(const bool &v) 242 | { 243 | m_values.append(QJsonValue(v)); 244 | } 245 | 246 | void JsonBuilder::handleBasic(const QByteArray &v) 247 | { 248 | m_values.append(QJsonValue(QString::fromUtf8(v))); 249 | } 250 | 251 | void JsonBuilder::handleBasic(const int &v) 252 | { 253 | m_values.append(QJsonValue(v)); 254 | } 255 | 256 | void JsonBuilder::handleBasic(const double &v) 257 | { 258 | m_values.append(QJsonValue(v)); 259 | } 260 | 261 | void JsonBuilder::handleNullType() 262 | { 263 | m_values.append(QJsonValue(QJsonValue::Type::Null)); 264 | } 265 | 266 | void JsonBuilder::handleMissingOptional() 267 | { 268 | if (m_fieldLevel.isEmpty() || m_fieldLevel.last() != m_values.size()) 269 | handleNullType(); 270 | } 271 | 272 | bool JsonBuilder::startField(const QString &) 273 | { 274 | m_fieldLevel.append(m_values.size()); 275 | return true; 276 | } 277 | 278 | bool JsonBuilder::startField(const char *) 279 | { 280 | m_fieldLevel.append(m_values.size()); 281 | return true; 282 | } 283 | 284 | void JsonBuilder::endField(const QString &v) 285 | { 286 | Q_ASSERT(!m_fieldLevel.isEmpty()); 287 | if (m_fieldLevel.last() < m_values.size()) { 288 | Q_ASSERT(m_values.size() > 1); 289 | if (QJsonObject *o = std::get_if(&m_values[m_values.size() - 2])) { 290 | o->insert(v, popLastValue()); 291 | } else { 292 | Q_ASSERT(false); 293 | } 294 | } 295 | Q_ASSERT(!m_fieldLevel.isEmpty() && m_fieldLevel.last() == m_values.size()); 296 | m_fieldLevel.removeLast(); 297 | } 298 | 299 | void JsonBuilder::endField(const char *v) 300 | { 301 | endField(QString::fromUtf8(v)); 302 | } 303 | 304 | bool JsonBuilder::startObjectF(const char *, ObjectOptions, quintptr) 305 | { 306 | m_values.append(QJsonObject()); 307 | return true; 308 | } 309 | 310 | void JsonBuilder::endObjectF(const char *, ObjectOptions, quintptr) { } 311 | 312 | bool JsonBuilder::startArrayF(qint32 &) 313 | { 314 | m_values.append(QJsonArray()); 315 | m_arrayLevel.append(m_values.size()); 316 | return true; 317 | } 318 | 319 | bool JsonBuilder::startElement(qint32) 320 | { 321 | return true; 322 | } 323 | 324 | void JsonBuilder::endElement(qint32) 325 | { 326 | Q_ASSERT(m_values.size() > 1); 327 | if (QJsonArray *a = std::get_if(&m_values[m_values.size() - 2])) { 328 | a->append(popLastValue()); 329 | } else { 330 | Q_ASSERT(false); 331 | } 332 | } 333 | 334 | void JsonBuilder::endArrayF(qint32 &) 335 | { 336 | Q_ASSERT(!m_arrayLevel.isEmpty() && m_arrayLevel.last() == m_values.size()); 337 | m_arrayLevel.removeLast(); 338 | } 339 | 340 | void JsonBuilder::handleJson(QJsonValue &v) 341 | { 342 | m_values.append(v); 343 | } 344 | 345 | void JsonBuilder::handleJson(QJsonObject &v) 346 | { 347 | m_values.append(v); 348 | } 349 | 350 | void JsonBuilder::handleJson(QJsonArray &v) 351 | { 352 | m_values.append(v); 353 | } 354 | 355 | QJsonValue JsonBuilder::popLastValue() 356 | { 357 | if (m_values.isEmpty()) 358 | return QJsonValue(QJsonValue::Type::Undefined); 359 | QJsonValue res = std::visit([](auto &v) { return QJsonValue(v); }, m_values.last()); 360 | m_values.removeLast(); 361 | return res; 362 | } 363 | 364 | bool JsonBuilder::startTuple(qint32 size) 365 | { 366 | return startArrayF(size); 367 | } 368 | 369 | void JsonBuilder::endTuple(qint32 size) 370 | { 371 | endArrayF(size); 372 | } 373 | 374 | } // namespace QTypedJson 375 | 376 | QT_END_NAMESPACE 377 | -------------------------------------------------------------------------------- /src/jsonrpc/qtypedjson_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QTYPEDJSON_H 5 | #define QTYPEDJSON_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | QT_BEGIN_NAMESPACE 37 | 38 | namespace QTypedJson { 39 | QT_DECLARE_EXPORTED_QT_LOGGING_CATEGORY(jsonRpcLog, Q_JSONRPC_EXPORT); 40 | 41 | Q_NAMESPACE 42 | 43 | enum class ObjectOption { None = 0, KeepExtraFields = 1, WarnExtra = 2 }; 44 | Q_ENUM_NS(ObjectOption) 45 | Q_DECLARE_FLAGS(ObjectOptions, ObjectOption) 46 | Q_DECLARE_OPERATORS_FOR_FLAGS(ObjectOptions) 47 | 48 | enum class ParseMode { StopOnError }; 49 | Q_ENUM_NS(ParseMode) 50 | 51 | enum class ParseStatus { Normal, Failed }; 52 | Q_ENUM_NS(ParseStatus) 53 | 54 | template 55 | using void_t = void; 56 | 57 | template 58 | struct HasTypeName : std::false_type 59 | { 60 | }; 61 | 62 | template 63 | struct HasTypeName> : std::true_type 64 | { 65 | }; 66 | 67 | template 68 | struct HasMetaEnum : std::false_type 69 | { 70 | }; 71 | 72 | template 73 | struct HasMetaEnum())>> : std::true_type 74 | { 75 | }; 76 | 77 | template 78 | const char *typeName() 79 | { 80 | if constexpr (HasTypeName::value) 81 | return T::TypeName; 82 | else 83 | return typeid(T).name(); 84 | } 85 | 86 | template 87 | struct JsonObjectOptions 88 | { 89 | static constexpr ObjectOptions value = ObjectOption::None; 90 | }; 91 | 92 | template 93 | struct JsonObjectOptions> : std::true_type 94 | { 95 | static constexpr ObjectOptions value = T::jsonObjectOptions; 96 | }; 97 | 98 | template 99 | struct HasExtraFields : std::false_type 100 | { 101 | }; 102 | 103 | template 104 | struct HasExtraFields().extraFields())>> : std::true_type 105 | { 106 | }; 107 | 108 | template 109 | struct SetExtraFields : std::false_type 110 | { 111 | }; 112 | 113 | template 114 | struct SetExtraFields< 115 | T, void_t().setExtraFields(std::declval()))>> 116 | : std::true_type 117 | { 118 | }; 119 | 120 | template 121 | struct IsList : std::false_type 122 | { 123 | }; 124 | 125 | template 126 | struct IsList> : std::true_type 127 | { 128 | }; 129 | 130 | template 131 | struct IsPointer : std::is_pointer 132 | { 133 | }; 134 | 135 | template 136 | struct IsPointer> : std::true_type 137 | { 138 | }; 139 | 140 | template 141 | struct IsPointer> : std::true_type 142 | { 143 | }; 144 | 145 | template 146 | struct IsVariant : std::false_type 147 | { 148 | }; 149 | 150 | template 151 | struct IsVariant> : std::true_type 152 | { 153 | }; 154 | 155 | template 156 | inline QString enumToString(T value) 157 | { 158 | int iValue = int(value); 159 | if constexpr (HasMetaEnum::value) { 160 | QMetaEnum metaEnum = QMetaEnum::fromType(); 161 | for (int i = 0; i < metaEnum.keyCount(); ++i) { 162 | if (iValue == metaEnum.value(i)) 163 | return QString::fromUtf8(metaEnum.key(i)); 164 | } 165 | } 166 | return QString::number(iValue); 167 | } 168 | 169 | template 170 | inline T enumFromString(const QString &value) 171 | { 172 | bool ok; 173 | int v = value.toInt(&ok); 174 | if (ok) 175 | return T(v); 176 | if constexpr (HasMetaEnum::value) { 177 | QMetaEnum metaEnum = QMetaEnum::fromType(); 178 | for (int i = 0; i < metaEnum.keyCount(); ++i) { 179 | if (value.compare(QLatin1String(metaEnum.key(i)), Qt::CaseInsensitive) == 0) 180 | return T { metaEnum.value(i) }; 181 | } 182 | } 183 | return T {}; 184 | } 185 | 186 | template 187 | inline QString enumToIntString(T value) 188 | { 189 | return QString::number(int(value)); 190 | } 191 | 192 | template 193 | inline T enumFromIntString(const QString &value) 194 | { 195 | bool ok; 196 | int v = value.toInt(&ok); 197 | if (ok) 198 | return T(v); 199 | return T {}; 200 | } 201 | 202 | class Q_JSONRPC_EXPORT ValueStack 203 | { 204 | public: 205 | QJsonValue value; 206 | QString fieldPath; 207 | int indexPath = -1; 208 | int warnLevel = 0; 209 | }; 210 | 211 | class ObjectStack 212 | { 213 | public: 214 | const char *type; 215 | ObjectOptions options; 216 | QSet visitedFields; 217 | }; 218 | 219 | class ReaderPrivate 220 | { 221 | public: 222 | QList valuesStack = {}; 223 | QList objectsStack = {}; 224 | ObjectOptions baseOptions = {}; 225 | ParseMode parseMode = ParseMode::StopOnError; 226 | ParseStatus parseStatus = ParseStatus::Normal; 227 | QStringList errorMessages = {}; 228 | }; 229 | 230 | class Q_JSONRPC_EXPORT Reader 231 | { 232 | public: 233 | Reader(const QJsonValue &v); 234 | ~Reader(); 235 | 236 | QStringList errorMessages(); 237 | void clearErrorMessages(); 238 | 239 | // serialization templates 240 | 241 | template 242 | bool startObject(const char *type, ObjectOptions options, quintptr id, T &) 243 | { 244 | return this->startObjectF(type, options, id); 245 | } 246 | template 247 | void endObject(const char *type, ObjectOptions options, quintptr id, T &obj); 248 | 249 | template 250 | bool startArray(qint32 &size, T &el) 251 | { 252 | startArrayF(size); 253 | using BaseT = std::decay_t; 254 | if constexpr (std::is_base_of_v, BaseT>) { 255 | el.resize(size); 256 | } else { 257 | assert(false); // currently unsupported 258 | } 259 | return true; 260 | } 261 | 262 | template 263 | bool handleOptional(T &el) 264 | { 265 | bool isMissing = currentValue().isUndefined() || currentValue().isNull(); 266 | if (isMissing) 267 | el.reset(); 268 | else 269 | el.emplace(); 270 | return bool(el); 271 | } 272 | 273 | template 274 | bool handlePointer(T &el) 275 | { 276 | bool isMissing = currentValue().isUndefined() || currentValue().isNull(); 277 | if (isMissing) 278 | el = nullptr; 279 | else 280 | el = T(new std::decay_t); 281 | return bool(el); 282 | } 283 | 284 | template 285 | void handleVariant(std::variant &el) 286 | { 287 | std::tuple options; 288 | int status = 0; 289 | ReaderPrivate origStatus = *m_p; 290 | QStringList err; 291 | auto tryRead = [this, &origStatus, &status, &el, &err](auto &x) { 292 | if (status == 2) 293 | return; 294 | if (status == 1) 295 | *this->m_p = origStatus; 296 | else 297 | status = 1; 298 | doWalk(*this, x); 299 | if (m_p->parseStatus == ParseStatus::Normal) { 300 | status = 2; 301 | el = x; 302 | return; 303 | } 304 | err.append(QStringLiteral(u"Type %1 failed with errors:") 305 | .arg(QLatin1String(typeid(decltype(x)).name()))); 306 | err += m_p->errorMessages; 307 | }; 308 | std::apply([&tryRead](auto &...x) { (..., tryRead(x)); }, options); 309 | if (status == 1) { 310 | m_p->errorMessages.clear(); 311 | m_p->errorMessages.append(QStringLiteral(u"All options of variant failed:")); 312 | m_p->errorMessages += err; 313 | } 314 | } 315 | 316 | template 317 | void handleEnum(T &e) 318 | { 319 | if (currentValue().isDouble()) { 320 | e = T(currentValue().toInt()); 321 | } else { 322 | e = enumFromString(currentValue().toString()); 323 | } 324 | } 325 | 326 | template 327 | void endArray(qint32 &size, T &) 328 | { 329 | this->endArrayF(size); 330 | } 331 | 332 | // serialization callbacks 333 | void handleBasic(bool &); 334 | void handleBasic(QByteArray &); 335 | void handleBasic(int &); 336 | void handleBasic(double &); 337 | void handleNullType(); 338 | void handleJson(QJsonValue &v); 339 | void handleJson(QJsonObject &v); 340 | void handleJson(QJsonArray &v); 341 | bool startField(const QString &fieldName); 342 | bool startField(const char *fieldName); 343 | void endField(const QString &fieldName); 344 | void endField(const char *fieldName); 345 | bool startElement(qint32 index); 346 | void endElement(qint32 index); 347 | bool startTuple(qint32 size); 348 | void endTuple(qint32 size); 349 | 350 | private: 351 | void warnExtra(const QJsonObject &e); 352 | void warnMissing(QStringView s); 353 | void warnNonNull(); 354 | void warnInvalidSize(qint32 size, qint32 expectedSize); 355 | void warn(const QString &msg); 356 | QJsonObject getExtraFields() const; 357 | bool startObjectF(const char *type, ObjectOptions options, quintptr id); 358 | void endObjectF(const char *type, ObjectOptions options, quintptr id); 359 | void startArrayF(qint32 &size); 360 | void endArrayF(qint32 &size); 361 | bool hasElement(); 362 | QString currentPath() const; 363 | const QJsonValue ¤tValue() const { return m_p->valuesStack.last().value; } 364 | ReaderPrivate *m_p; 365 | }; 366 | 367 | template 368 | struct HasWalk : std::false_type 369 | { 370 | }; 371 | 372 | template 373 | struct HasWalk))>> : std::true_type 374 | { 375 | }; 376 | 377 | template 378 | void field(W &w, const C &fieldName, T &el) 379 | { 380 | if (w.startField(fieldName)) { 381 | auto guard = qScopeGuard([&w, &fieldName]() { w.endField(fieldName); }); 382 | doWalk(w, el); 383 | } 384 | } 385 | 386 | template 387 | inline void doWalk(W &w, T &el) 388 | { 389 | using BaseT = std::decay_t; 390 | if constexpr ( 391 | std::is_same_v< 392 | BaseT, 393 | int> || std::is_same_v || std::is_same_v || std::is_same_v) { 394 | w.handleBasic(el); 395 | } else if constexpr (HasWalk::value) { 396 | const char *type = typeName(); 397 | ObjectOptions options = JsonObjectOptions::value; 398 | quintptr id = quintptr(&el); 399 | if (w.startObject(type, options, id, el)) { 400 | el.walk(w); 401 | w.endObject(type, options, id, el); 402 | } 403 | } else if constexpr ( 404 | std::is_same_v< 405 | BaseT, 406 | QJsonValue> || std::is_same_v || std::is_same_v) { 407 | w.handleJson(el); 408 | } else if constexpr (std::is_enum_v) { 409 | w.handleEnum(el); 410 | } else if constexpr (std::is_same_v) { 411 | w.handleNullType(); 412 | } else if constexpr (IsPointer::value) { 413 | if (w.handlePointer(el) && el) 414 | doWalk(w, *el); 415 | } else if constexpr (IsVariant::value) { 416 | w.handleVariant(el); 417 | } else if constexpr (IsList::value) { 418 | if constexpr (std::is_same_v, BaseT>) { 419 | if (w.handleOptional(el) && el) 420 | doWalk(w, *el); 421 | } else { 422 | // broken gcc warns about optional being uninitialized, QTBUG-128574 423 | QT_WARNING_PUSH 424 | QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized") 425 | int size = el.size(); 426 | QT_WARNING_POP 427 | if (!w.startArray(size, el)) 428 | return; 429 | int i = 0; 430 | for (auto &subEl : el) { 431 | if (!w.startElement(i)) 432 | break; 433 | doWalk(w, subEl); 434 | w.endElement(i); 435 | ++i; 436 | } 437 | w.endArray(size, el); 438 | } 439 | } else { 440 | qWarning() << "Unhandled type" << typeid(T).name(); 441 | assert(false); 442 | } 443 | } 444 | 445 | template 446 | inline void Reader::endObject(const char *type, ObjectOptions options, quintptr id, T &obj) 447 | { 448 | using BaseT = std::decay_t; 449 | QJsonObject extra; 450 | if (SetExtraFields::value 451 | || (options & (ObjectOption::KeepExtraFields | ObjectOption::WarnExtra))) 452 | extra = this->getExtraFields(); 453 | this->endObjectF(type, options, id); 454 | if constexpr (SetExtraFields::value) 455 | obj.setExtraFields(extra); 456 | else if (extra.constBegin() != extra.constEnd()) 457 | warnExtra(extra); 458 | } 459 | 460 | class Q_JSONRPC_EXPORT JsonBuilder 461 | { 462 | public: 463 | JsonBuilder() = default; 464 | 465 | // public api 466 | QJsonValue popLastValue(); 467 | 468 | // serialization templates 469 | template 470 | bool handleOptional(T &el) 471 | { 472 | if (el) 473 | return true; 474 | this->handleMissingOptional(); 475 | return false; 476 | } 477 | 478 | template 479 | bool handlePointer(T &el) 480 | { 481 | return bool(el); 482 | } 483 | 484 | template 485 | bool startObject(const char *type, ObjectOptions options, quintptr id, T &) 486 | { 487 | return this->startObjectF(type, options, id); 488 | } 489 | 490 | template 491 | void endObject(const char *type, ObjectOptions options, quintptr id, T &) 492 | { 493 | this->endObjectF(type, options, id); 494 | } 495 | 496 | template 497 | bool startArray(qint32 &size, T &el) 498 | { 499 | using BaseT = std::decay_t; 500 | if constexpr (std::is_base_of_v, BaseT>) { 501 | size = el.size(); 502 | } else { 503 | assert(false); // currently unsupported 504 | } 505 | return startArrayF(size); 506 | } 507 | 508 | template 509 | void endArray(qint32 &size, T &) 510 | { 511 | this->endArrayF(size); 512 | } 513 | 514 | template 515 | void handleVariant(T &el) 516 | { 517 | std::visit([this](auto &v) { doWalk(*this, v); }, el); 518 | } 519 | 520 | template 521 | void handleEnum(T &el) 522 | { 523 | QString eVal = enumToString(el); 524 | bool ok; 525 | int value = eVal.toInt(&ok); 526 | if (ok) 527 | this->handleBasic(value); 528 | else 529 | this->handleBasic(eVal.toUtf8()); 530 | } 531 | 532 | // serialization callbacks 533 | void handleBasic(const bool &v); 534 | void handleBasic(const QByteArray &v); 535 | void handleBasic(const int &v); 536 | void handleBasic(const double &v); 537 | void handleNullType(); 538 | void handleJson(QJsonValue &v); 539 | void handleJson(QJsonObject &v); 540 | void handleJson(QJsonArray &v); 541 | bool startField(const QString &fieldName); 542 | bool startField(const char *fieldName); 543 | void endField(const QString &); 544 | void endField(const char *); 545 | bool startElement(qint32 index); 546 | void endElement(qint32); 547 | bool startTuple(qint32 size); 548 | void endTuple(qint32 size); 549 | 550 | private: 551 | void handleMissingOptional(); 552 | bool startObjectF(const char *, ObjectOptions, quintptr); 553 | void endObjectF(const char *, ObjectOptions, quintptr); 554 | bool startArrayF(qint32 &); 555 | void endArrayF(qint32 &); 556 | 557 | QList m_fieldLevel; 558 | QList m_arrayLevel; 559 | QList> m_values; 560 | }; 561 | 562 | template 563 | QJsonValue toJsonValue(Params ...params) 564 | { 565 | if constexpr (sizeof...(Params) == 0) 566 | return QJsonValue(QJsonValue::Type::Undefined); 567 | 568 | JsonBuilder b; 569 | if constexpr (sizeof...(Params) == 1) { 570 | doWalk(b, params...); 571 | } else if (b.startTuple(sizeof...(Params))) { 572 | qint32 i = 0; 573 | bool skipRest = false; 574 | std::apply([&i, &b, &skipRest](auto &el) { 575 | if (skipRest || !b.startElement(i)) { 576 | skipRest = true; 577 | } else { 578 | doWalk(b, el); 579 | b.endElement(i++); 580 | } 581 | }, params...); 582 | b.endTuple(sizeof...(Params)); 583 | } 584 | return b.popLastValue(); 585 | } 586 | 587 | } // namespace QTypedJson 588 | QT_END_NAMESPACE 589 | #endif // QTYPEDJSON_H 590 | -------------------------------------------------------------------------------- /src/languageserver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | ##################################################################### 5 | ## LanguageServer Module: 6 | ##################################################################### 7 | 8 | qt_internal_add_module(LanguageServerPrivate 9 | INTERNAL_MODULE 10 | STATIC 11 | SOURCES 12 | qlanguageserverprespectypes_p.h 13 | qlanguageserverspec_p.h 14 | qlanguageserverspectypes_p.h 15 | qlanguageserverjsonrpctransport_p.h qlanguageserverjsonrpctransport.cpp 16 | qtlanguageserverglobal.h 17 | qlanguageserverbase_p.h qlanguageserverbase_p_p.h qlanguageserverbase.cpp 18 | qlanguageservergen_p.h qlanguageservergen_p_p.h qlanguageservergen.cpp 19 | qlanguageserverprotocol_p.h qlanguageserverprotocol.cpp 20 | qlspnotifysignals_p.h qlspnotifysignals.cpp 21 | DEFINES 22 | QT_BUILD_LANGUAGESERVER_LIB 23 | QT_NO_CONTEXTLESS_CONNECT 24 | PUBLIC_LIBRARIES 25 | Qt::CorePrivate 26 | Qt::JsonRpcPrivate 27 | ) 28 | 29 | if(MSVC) 30 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") 31 | elseif (MINGW) 32 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wa,-mbig-obj") 33 | endif() 34 | -------------------------------------------------------------------------------- /src/languageserver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "ts-node": "^10.2.1", 4 | "typescript": "^4.4.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/languageserver/qlanguageserverbase.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qlanguageserverbase_p_p.h" 5 | 6 | QT_BEGIN_NAMESPACE 7 | 8 | using namespace Qt::StringLiterals; 9 | 10 | Q_LOGGING_CATEGORY(lspLog, "qt.languageserver.protocol"); 11 | 12 | namespace QLspSpecification { 13 | 14 | ProtocolBase::ProtocolBase(std::unique_ptr &&priv) : d_ptr(std::move(priv)) 15 | { 16 | Q_D(ProtocolBase); 17 | d->typedRpc.setTransport(&d->transport); 18 | registerMethods(&d->typedRpc); 19 | } 20 | 21 | ProtocolBase::~ProtocolBase() = default; 22 | 23 | void ProtocolBase::registerMethods(QJsonRpc::TypedRpc *typedRpc) 24 | { 25 | auto defaultHandler = new QJsonRpc::TypedHandler( 26 | QByteArray(), 27 | [this, typedRpc](const QJsonRpcProtocol::Request &req, 28 | const QJsonRpcProtocol::ResponseHandler &handler) { 29 | QJsonRpc::IdType id = 30 | ((req.id.isDouble()) ? QJsonRpc::IdType(req.id.toInt()) 31 | : QJsonRpc::IdType(req.id.toString().toUtf8())); 32 | QByteArray method = req.method.toUtf8(); 33 | QJsonRpc::TypedResponse response(id, typedRpc, handler); 34 | handleUndispatchedRequest(id, method, req.params, std::move(response)); 35 | }, 36 | [this](const QJsonRpcProtocol::Notification ¬if) { 37 | QByteArray method = notif.method.toUtf8(); 38 | handleUndispatchedNotification(method, notif.params); 39 | }); 40 | typedRpc->setDefaultMessageHandler(defaultHandler); // typedRpc gets ownership 41 | typedRpc->setInvalidResponseHandler([this](const QJsonRpcProtocol::Response &response) { 42 | handleResponseError(ResponseError { response.errorCode.toInt(), 43 | response.errorMessage.toUtf8(), response.data }); 44 | }); 45 | } 46 | 47 | void ProtocolBase::defaultUndispatchedRequestHandler(const QJsonRpc::IdType &id, 48 | const QByteArray &method, 49 | const QLspSpecification::RequestParams ¶ms, 50 | QJsonRpc::TypedResponse &&response) 51 | { 52 | Q_UNUSED(id); 53 | Q_UNUSED(params); 54 | QByteArray msg; 55 | QByteArray cppBaseName = requestMethodToBaseCppName(method); 56 | if (cppBaseName.isEmpty()) { 57 | msg.append("Ignoring unknown request with method "); 58 | msg.append(method); 59 | } else { 60 | msg.append("There was no handler registered with register"); 61 | msg.append(cppBaseName); 62 | msg.append("Handler to handle a requests with method "); 63 | msg.append(method); 64 | } 65 | response.sendErrorResponse(int(QJsonRpcProtocol::ErrorCode::MethodNotFound), msg); 66 | qCWarning(lspLog) << QString::fromUtf8(msg); 67 | } 68 | 69 | void ProtocolBase::defaultUndispatchedNotificationHandler( 70 | const QByteArray &method, const QLspSpecification::NotificationParams ¶ms) 71 | { 72 | Q_UNUSED(params); 73 | QByteArray msg; 74 | QByteArray cppBaseName = notificationMethodToBaseCppName(method); 75 | if (cppBaseName.isEmpty()) { 76 | msg.append("Unknown notification with method "); 77 | msg.append(method); 78 | } else { 79 | msg.append("There was no handler registered with register"); 80 | msg.append(cppBaseName); 81 | msg.append("NotificationHandler to handle the \""); 82 | msg.append(method); 83 | msg.append("\" notification"); 84 | } 85 | if (method.startsWith("$")) 86 | qCDebug(lspLog) << QString::fromUtf8(msg); 87 | else 88 | qCWarning(lspLog) << QString::fromUtf8(msg); 89 | } 90 | 91 | void ProtocolBase::defaultResponseErrorHandler(const QLspSpecification::ResponseError &err) 92 | { 93 | qCWarning(lspLog) << u"ERROR" << err.code << u":" << QString::fromUtf8(err.message) 94 | << ((!err.data) ? QString() 95 | : (err.data->isObject()) 96 | ? QString::fromUtf8(QJsonDocument(err.data->toObject()).toJson()) 97 | : (err.data->isArray()) 98 | ? QString::fromUtf8(QJsonDocument(err.data->toArray()).toJson()) 99 | : (err.data->isDouble()) ? QString::number(err.data->toDouble()) 100 | : (err.data->isString()) ? err.data->toString() 101 | : (err.data->isNull()) ? u"null"_s 102 | : QString()); 103 | } 104 | 105 | void ProtocolBase::registerResponseErrorHandler(const ResponseErrorHandler &handler) 106 | { 107 | Q_D(ProtocolBase); 108 | Q_ASSERT(!d->errorHandler || !handler); 109 | d->errorHandler = handler; 110 | } 111 | 112 | void ProtocolBase::registerUndispatchedRequestHandler(const GenericRequestHandler &handler) 113 | { 114 | Q_D(ProtocolBase); 115 | Q_ASSERT(!d->undispachedRequestHandler || !handler); 116 | d->undispachedRequestHandler = handler; 117 | } 118 | 119 | void ProtocolBase::registerUndispatchedNotificationHandler( 120 | const GenericNotificationHandler &handler) 121 | { 122 | Q_D(ProtocolBase); 123 | Q_ASSERT(!d->undispachedNotificationHandler || !handler); 124 | d->undispachedNotificationHandler = handler; 125 | } 126 | 127 | void ProtocolBase::handleUndispatchedRequest(const QJsonRpc::IdType &id, const QByteArray &method, 128 | const QLspSpecification::RequestParams ¶ms, 129 | QJsonRpc::TypedResponse &&response) 130 | { 131 | Q_D(ProtocolBase); 132 | if (d->undispachedRequestHandler) 133 | d->undispachedRequestHandler(id, method, params, std::move(response)); 134 | else 135 | defaultUndispatchedRequestHandler(id, method, params, std::move(response)); 136 | } 137 | 138 | void ProtocolBase::handleUndispatchedNotification(const QByteArray &method, 139 | const NotificationParams ¶ms) 140 | { 141 | Q_D(ProtocolBase); 142 | if (d->undispachedNotificationHandler) 143 | d->undispachedNotificationHandler(method, params); 144 | else 145 | defaultUndispatchedNotificationHandler(method, params); 146 | } 147 | 148 | void ProtocolBase::handleResponseError(const ResponseError &err) 149 | { 150 | Q_D(ProtocolBase); 151 | if (d->errorHandler) 152 | d->errorHandler(err); 153 | else 154 | defaultResponseErrorHandler(err); 155 | } 156 | 157 | QJsonRpc::TypedRpc *ProtocolBase::typedRpc() 158 | { 159 | Q_D(ProtocolBase); 160 | return &d->typedRpc; 161 | } 162 | 163 | QJsonRpcTransport *ProtocolBase::transport() 164 | { 165 | Q_D(ProtocolBase); 166 | return &d->transport; 167 | } 168 | 169 | } // namespace QLspSpecification 170 | QT_END_NAMESPACE 171 | -------------------------------------------------------------------------------- /src/languageserver/qlanguageserverbase_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QLANGUAGESERVERBASE_P_H 5 | #define QLANGUAGESERVERBASE_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | QT_BEGIN_NAMESPACE 29 | 30 | Q_DECLARE_LOGGING_CATEGORY(lspLog); 31 | 32 | namespace QLspSpecification { 33 | 34 | class ProtocolBasePrivate; 35 | class Q_LANGUAGESERVER_EXPORT ProtocolBase 36 | { 37 | Q_DISABLE_COPY_MOVE(ProtocolBase) 38 | public: 39 | ~ProtocolBase(); 40 | using GenericRequestHandler = std::function; 43 | using GenericNotificationHandler = 44 | std::function; 45 | using ResponseErrorHandler = std::function; 46 | 47 | // generated, defined in qlanguageservergen.cpp 48 | static QByteArray requestMethodToBaseCppName(const QByteArray &); 49 | 50 | // generated, defined in qlanguageservergen.cpp 51 | static QByteArray notificationMethodToBaseCppName(const QByteArray &); 52 | 53 | static void defaultUndispatchedRequestHandler( 54 | const QJsonRpc::IdType &id, const QByteArray &method, 55 | const QLspSpecification::RequestParams ¶ms, QJsonRpc::TypedResponse &&response); 56 | static void defaultUndispatchedNotificationHandler( 57 | const QByteArray &method, const QLspSpecification::NotificationParams ¶ms); 58 | static void defaultResponseErrorHandler(const QLspSpecification::ResponseError &err); 59 | 60 | void registerResponseErrorHandler(const ResponseErrorHandler &h); 61 | void registerUndispatchedRequestHandler(const GenericRequestHandler &handler); 62 | void registerUndispatchedNotificationHandler(const GenericNotificationHandler &handler); 63 | 64 | void handleResponseError(const ResponseError &err); 65 | void handleUndispatchedRequest(const QJsonRpc::IdType &id, const QByteArray &method, 66 | const QLspSpecification::RequestParams ¶ms, 67 | QJsonRpc::TypedResponse &&response); 68 | void handleUndispatchedNotification(const QByteArray &method, 69 | const QLspSpecification::NotificationParams ¶ms); 70 | QJsonRpc::TypedRpc *typedRpc(); 71 | 72 | protected: 73 | std::unique_ptr d_ptr; 74 | 75 | ProtocolBase(std::unique_ptr &&priv); 76 | QJsonRpcTransport *transport(); 77 | 78 | private: 79 | void registerMethods(QJsonRpc::TypedRpc *); 80 | Q_DECLARE_PRIVATE(ProtocolBase) 81 | }; 82 | 83 | template 84 | void decodeAndCall(QJsonValue value, F funct, 85 | ProtocolBase::ResponseErrorHandler errorHandler = 86 | &ProtocolBase::defaultResponseErrorHandler) 87 | { 88 | using namespace Qt::StringLiterals; 89 | 90 | T result; 91 | QTypedJson::Reader r(value); 92 | doWalk(r, result); 93 | if (!r.errorMessages().isEmpty()) { 94 | errorHandler(QLspSpecification::ResponseError { 95 | int(QLspSpecification::ErrorCodes::ParseError), 96 | u"Errors decoding data:\n %1"_s.arg(r.errorMessages().join(u"\n ")).toUtf8(), 97 | value }); 98 | r.clearErrorMessages(); 99 | } else { 100 | if constexpr (std::is_same_v && std::is_invocable_v) { 101 | funct(); 102 | } else { 103 | funct(result); 104 | } 105 | } 106 | } 107 | 108 | } // namespace QLspSpecification 109 | QT_END_NAMESPACE 110 | #endif // QLANGUAGESERVERBASE_P_H 111 | -------------------------------------------------------------------------------- /src/languageserver/qlanguageserverbase_p_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QLANGUAGESERVERBASE_P_P_H 5 | #define QLANGUAGESERVERBASE_P_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | 21 | QT_BEGIN_NAMESPACE 22 | namespace QLspSpecification { 23 | 24 | class ProtocolBasePrivate 25 | { 26 | public: 27 | QLanguageServerJsonRpcTransport transport; 28 | QJsonRpc::TypedRpc typedRpc; 29 | ProtocolBase::ResponseErrorHandler errorHandler; 30 | ProtocolBase::GenericRequestHandler undispachedRequestHandler; 31 | ProtocolBase::GenericNotificationHandler undispachedNotificationHandler; 32 | }; 33 | 34 | } // namespace QLspSpecification 35 | QT_END_NAMESPACE 36 | #endif // QLANGUAGESERVERBASE_P_P_H 37 | -------------------------------------------------------------------------------- /src/languageserver/qlanguageservergen_p_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | // this file was generated by the generate.ts script 5 | 6 | #ifndef QLANGUAGESERVERGEN_P_P_H 7 | #define QLANGUAGESERVERGEN_P_P_H 8 | 9 | // 10 | // W A R N I N G 11 | // ------------- 12 | // 13 | // This file is not part of the Qt API. It exists purely as an 14 | // implementation detail. This header file may change from version to 15 | // version without notice, or even be removed. 16 | // 17 | // We mean it. 18 | // 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | QT_BEGIN_NAMESPACE 25 | 26 | namespace QLspSpecification { 27 | 28 | class ProtocolGenPrivate : public ProtocolBasePrivate 29 | { 30 | public: 31 | }; 32 | 33 | } // namespace QLspSpecification 34 | 35 | QT_END_NAMESPACE 36 | 37 | #endif // QLANGUAGESERVERGEN_P_P_H 38 | -------------------------------------------------------------------------------- /src/languageserver/qlanguageserverjsonrpctransport.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include "qlanguageserverjsonrpctransport_p.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | QT_BEGIN_NAMESPACE 11 | 12 | using namespace Qt::StringLiterals; 13 | 14 | static const QByteArray s_contentLengthFieldName = "Content-Length"; 15 | static const QByteArray s_contentTypeFieldName = "Content-Type"; 16 | static const QByteArray s_fieldSeparator = ": "; 17 | static const QByteArray s_headerSeparator = "\r\n"; 18 | static const QByteArray s_headerEnd = "\r\n\r\n"; 19 | static const QByteArray s_utf8 = "utf-8"; 20 | static const QByteArray s_brokenUtf8 = "utf8"; 21 | 22 | QLanguageServerJsonRpcTransport::QLanguageServerJsonRpcTransport() noexcept 23 | : m_messageStreamParser( 24 | [this](const QByteArray &field, const QByteArray &value) { hasHeader(field, value); }, 25 | [this](const QByteArray &body) { hasBody(body); }, 26 | [this](QtMsgType error, QString msg) { 27 | if (auto handler = diagnosticHandler()) { 28 | if (error == QtWarningMsg || error == QtInfoMsg || error == QtDebugMsg) 29 | handler(Warning, msg); 30 | else 31 | handler(Error, msg); 32 | } 33 | }) 34 | { 35 | } 36 | 37 | void QLanguageServerJsonRpcTransport::sendMessage(const QJsonDocument &packet) 38 | { 39 | const QByteArray content = packet.toJson(QJsonDocument::Compact); 40 | if (auto handler = dataHandler()) { 41 | // send all data in one go, this way if handler is threadsafe the whole sendMessage is 42 | // threadsafe 43 | QByteArray msg; 44 | msg.append(s_contentLengthFieldName); 45 | msg.append(s_fieldSeparator); 46 | msg.append(QByteArray::number(content.size())); 47 | msg.append(s_headerSeparator); 48 | msg.append(s_headerSeparator); 49 | msg.append(content); 50 | handler(msg); 51 | } 52 | } 53 | 54 | void QLanguageServerJsonRpcTransport::receiveData(const QByteArray &data) 55 | { 56 | m_messageStreamParser.receiveData(data); 57 | } 58 | 59 | void QLanguageServerJsonRpcTransport::hasHeader(const QByteArray &fieldName, 60 | const QByteArray &fieldValue) 61 | { 62 | if (s_contentLengthFieldName.compare(fieldName, Qt::CaseInsensitive) == 0) { 63 | // already handled by parser 64 | } else if (s_contentTypeFieldName.compare(fieldName, Qt::CaseInsensitive) == 0) { 65 | if (fieldValue != s_utf8 && fieldValue != s_brokenUtf8) { 66 | if (auto handler = diagnosticHandler()) { 67 | handler(Warning, 68 | QString::fromLatin1("Invalid %1: %2") 69 | .arg(QString::fromUtf8(fieldName)) 70 | .arg(QString::fromUtf8(fieldValue))); 71 | } 72 | } 73 | } else if (auto handler = diagnosticHandler()) { 74 | handler(Warning, 75 | QString::fromLatin1("Unknown header field: %1").arg(QString::fromUtf8(fieldName))); 76 | } 77 | } 78 | 79 | void QLanguageServerJsonRpcTransport::hasBody(const QByteArray &body) 80 | { 81 | QJsonParseError error = { 0, QJsonParseError::NoError }; 82 | const QJsonDocument doc = QJsonDocument::fromJson(body, &error); 83 | 84 | if (error.error != QJsonParseError::NoError) { 85 | if (auto handler = diagnosticHandler()) { 86 | handler(Error, 87 | u"Error %1 decoding json: %2"_s.arg(int(error.error)) 88 | .arg(error.errorString())); 89 | } 90 | } 91 | if (auto handler = messageHandler()) { 92 | handler(doc, error); 93 | } 94 | } 95 | 96 | QT_END_NAMESPACE 97 | -------------------------------------------------------------------------------- /src/languageserver/qlanguageserverjsonrpctransport_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QLANGUAGESERVERJSONRPCTRANSPORT_P_H 5 | #define QLANGUAGESERVERJSONRPCTRANSPORT_P_H 6 | 7 | // 8 | // W A R N I N G 9 | // ------------- 10 | // 11 | // This file is not part of the Qt API. It exists purely as an 12 | // implementation detail. This header file may change from version to 13 | // version without notice, or even be removed. 14 | // 15 | // We mean it. 16 | // 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | QT_BEGIN_NAMESPACE 23 | 24 | class Q_LANGUAGESERVER_EXPORT QLanguageServerJsonRpcTransport : public QJsonRpcTransport 25 | { 26 | public: 27 | QLanguageServerJsonRpcTransport() noexcept; 28 | void sendMessage(const QJsonDocument &packet) override; 29 | void receiveData(const QByteArray &data) override; 30 | 31 | private: 32 | void hasHeader(const QByteArray &field, const QByteArray &value); 33 | void hasBody(const QByteArray &body); 34 | 35 | QHttpMessageStreamParser m_messageStreamParser; 36 | }; 37 | 38 | QT_END_NAMESPACE 39 | 40 | #endif // QLANGUAGESERVERJSONRPCTRANSPORT_P_H 41 | -------------------------------------------------------------------------------- /src/languageserver/qlanguageserverprespectypes_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | #ifndef QLANGUAGESERVERPRESPECTYPES_P_H 4 | #define QLANGUAGESERVERPRESPECTYPES_P_H 5 | 6 | // 7 | // W A R N I N G 8 | // ------------- 9 | // 10 | // This file is not part of the Qt API. It exists purely as an 11 | // implementation detail. This header file may change from version to 12 | // version without notice, or even be removed. 13 | // 14 | // We mean it. 15 | // 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | QT_BEGIN_NAMESPACE 28 | namespace QLspSpecification { 29 | 30 | using ProgressToken = std::variant; 31 | 32 | class Q_LANGUAGESERVER_EXPORT StringAndLanguage 33 | { 34 | public: 35 | QString language; 36 | QString value; 37 | 38 | template 39 | void walk(W &w) 40 | { 41 | field(w, "language", language); 42 | field(w, "value", value); 43 | } 44 | }; 45 | 46 | using MarkedString = std::variant; 47 | 48 | template 49 | class LSPResponse : public QJsonRpc::TypedResponse 50 | { 51 | Q_DISABLE_COPY(LSPResponse) 52 | public: 53 | LSPResponse() = default; 54 | LSPResponse(LSPResponse &&o) noexcept = default; 55 | LSPResponse &operator=(LSPResponse &&o) noexcept = default; 56 | using ResponseType = RType; 57 | 58 | LSPResponse(QJsonRpc::TypedResponse &&r) : QJsonRpc::TypedResponse(std::move(r)) { } 59 | 60 | void sendResponse(const RType &r) { sendSuccessfullResponse(r); } 61 | auto sendResponse() 62 | { 63 | if constexpr (std::is_same_v, std::nullptr_t>) 64 | sendSuccessfullResponse(nullptr); 65 | else 66 | Q_ASSERT(false); 67 | } 68 | }; 69 | 70 | template 71 | class LSPPartialResponse : public LSPResponse 72 | { 73 | Q_DISABLE_COPY(LSPPartialResponse) 74 | public: 75 | using PartialResponseType = PRType; 76 | 77 | LSPPartialResponse() = default; 78 | LSPPartialResponse(LSPPartialResponse &&o) noexcept = default; 79 | LSPPartialResponse &operator=(LSPPartialResponse &&o) noexcept = default; 80 | 81 | LSPPartialResponse(QJsonRpc::TypedResponse &&r) : LSPResponse(std::move(r)) { } 82 | 83 | void sendPartialResponse(const PRType &r) 84 | { 85 | // using Notifications::Progress here would require to split out the *RequestType aliases 86 | sendNotification("$/progress", r); 87 | } 88 | }; 89 | 90 | } // namespace QLspSpecification 91 | QT_END_NAMESPACE 92 | #endif // QLANGUAGESERVERPRESPECTYPES_P_H 93 | -------------------------------------------------------------------------------- /src/languageserver/qlanguageserverprotocol.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | QT_BEGIN_NAMESPACE 13 | 14 | using namespace QLspSpecification; 15 | using namespace Qt::StringLiterals; 16 | 17 | /*! 18 | \internal 19 | \class QLanguageServerProtocol 20 | \brief Implements the language server protocol 21 | 22 | QLanguageServerProtocol objects handles the language server 23 | protocol both to send and receive (ask the client and reply to the 24 | client). 25 | 26 | To ask the client you use the requestXX or notifyXX methods. 27 | To reply to client request you have to register your handlers via 28 | registerXXRequestHandler, notifications can be handled connecting the 29 | receivedXXNotification signals. 30 | 31 | The method themselves are implemented in qlanguageservergen* 32 | files which are generated form the specification. 33 | 34 | You have to provide a function to send the data that the protocol 35 | generates to the constructor, and you have to feed the data you 36 | receive to it via the receivedData method. 37 | 38 | Limitations: for the client use case (Creator),the handling of partial 39 | results could be improved. A clean solution should handle the progress 40 | notification, do some extra tracking and give the partial results back 41 | to the call that did the request. 42 | */ 43 | 44 | QLanguageServerProtocol::QLanguageServerProtocol(const QJsonRpcTransport::DataHandler &sender) 45 | : ProtocolGen(std::make_unique()) 46 | { 47 | transport()->setDataHandler(sender); 48 | transport()->setDiagnosticHandler([this](QJsonRpcTransport::DiagnosticLevel l, 49 | const QString &msg) { 50 | handleResponseError( 51 | ResponseError { int(ErrorCodes::InternalError), msg.toUtf8(), 52 | QJsonObject({ { u"errorLevel"_s, 53 | ((l == QJsonRpcTransport::DiagnosticLevel::Error) 54 | ? u"error"_s 55 | : u"warning"_s) } }) }); 56 | }); 57 | } 58 | 59 | void QLanguageServerProtocol::receiveData(const QByteArray &data) 60 | { 61 | transport()->receiveData(data); 62 | } 63 | 64 | QT_END_NAMESPACE 65 | -------------------------------------------------------------------------------- /src/languageserver/qlanguageserverprotocol_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | #ifndef QLANGUAGESERVERPROTOCOL_P_H 4 | #define QLANGUAGESERVERPROTOCOL_P_H 5 | 6 | // 7 | // W A R N I N G 8 | // ------------- 9 | // 10 | // This file is not part of the Qt API. It exists purely as an 11 | // implementation detail. This header file may change from version to 12 | // version without notice, or even be removed. 13 | // 14 | // We mean it. 15 | // 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | QT_BEGIN_NAMESPACE 22 | 23 | class Q_LANGUAGESERVER_EXPORT QLanguageServerProtocol : public QLspSpecification::ProtocolGen 24 | { 25 | public: 26 | QLanguageServerProtocol(const QJsonRpcTransport::DataHandler &sender); 27 | void receiveData(const QByteArray &data); 28 | }; 29 | 30 | QT_END_NAMESPACE 31 | 32 | #endif // QLANGUAGESERVER_P_H 33 | -------------------------------------------------------------------------------- /src/languageserver/qlspnotifysignals.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | // this file was generated by the generate.ts script 5 | 6 | #include 7 | 8 | QT_BEGIN_NAMESPACE 9 | 10 | using namespace QLspSpecification; 11 | 12 | void QLspNotifySignals::registerHandlers(QLanguageServerProtocol *protocol) 13 | { 14 | 15 | protocol->registerCancelNotificationHandler( 16 | [this, protocol](const QByteArray &method, 17 | const QLspSpecification::Notifications::CancelParamsType ¶ms) { 18 | static const QMetaMethod notificationSignal = 19 | QMetaMethod::fromSignal(&QLspNotifySignals::receivedCancelNotification); 20 | if (isSignalConnected(notificationSignal)) 21 | emit receivedCancelNotification(params); 22 | else 23 | protocol->handleUndispatchedNotification(method, params); 24 | }); 25 | 26 | protocol->registerInitializedNotificationHandler( 27 | [this, 28 | protocol](const QByteArray &method, 29 | const QLspSpecification::Notifications::InitializedParamsType ¶ms) { 30 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 31 | &QLspNotifySignals::receivedInitializedNotification); 32 | if (isSignalConnected(notificationSignal)) 33 | emit receivedInitializedNotification(params); 34 | else 35 | protocol->handleUndispatchedNotification(method, params); 36 | }); 37 | 38 | protocol->registerExitNotificationHandler( 39 | [this, protocol](const QByteArray &method, 40 | const QLspSpecification::Notifications::ExitParamsType ¶ms) { 41 | static const QMetaMethod notificationSignal = 42 | QMetaMethod::fromSignal(&QLspNotifySignals::receivedExitNotification); 43 | if (isSignalConnected(notificationSignal)) 44 | emit receivedExitNotification(params); 45 | else 46 | protocol->handleUndispatchedNotification(method, params); 47 | }); 48 | 49 | protocol->registerLogTraceNotificationHandler( 50 | [this, protocol](const QByteArray &method, 51 | const QLspSpecification::Notifications::LogTraceParamsType ¶ms) { 52 | static const QMetaMethod notificationSignal = 53 | QMetaMethod::fromSignal(&QLspNotifySignals::receivedLogTraceNotification); 54 | if (isSignalConnected(notificationSignal)) 55 | emit receivedLogTraceNotification(params); 56 | else 57 | protocol->handleUndispatchedNotification(method, params); 58 | }); 59 | 60 | protocol->registerSetTraceNotificationHandler( 61 | [this, protocol](const QByteArray &method, 62 | const QLspSpecification::Notifications::SetTraceParamsType ¶ms) { 63 | static const QMetaMethod notificationSignal = 64 | QMetaMethod::fromSignal(&QLspNotifySignals::receivedSetTraceNotification); 65 | if (isSignalConnected(notificationSignal)) 66 | emit receivedSetTraceNotification(params); 67 | else 68 | protocol->handleUndispatchedNotification(method, params); 69 | }); 70 | 71 | protocol->registerShowMessageNotificationHandler( 72 | [this, 73 | protocol](const QByteArray &method, 74 | const QLspSpecification::Notifications::ShowMessageParamsType ¶ms) { 75 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 76 | &QLspNotifySignals::receivedShowMessageNotification); 77 | if (isSignalConnected(notificationSignal)) 78 | emit receivedShowMessageNotification(params); 79 | else 80 | protocol->handleUndispatchedNotification(method, params); 81 | }); 82 | 83 | protocol->registerLogMessageNotificationHandler( 84 | [this, protocol](const QByteArray &method, 85 | const QLspSpecification::Notifications::LogMessageParamsType ¶ms) { 86 | static const QMetaMethod notificationSignal = 87 | QMetaMethod::fromSignal(&QLspNotifySignals::receivedLogMessageNotification); 88 | if (isSignalConnected(notificationSignal)) 89 | emit receivedLogMessageNotification(params); 90 | else 91 | protocol->handleUndispatchedNotification(method, params); 92 | }); 93 | 94 | protocol->registerWorkDoneProgressCancelNotificationHandler( 95 | [this, 96 | protocol](const QByteArray &method, 97 | const QLspSpecification::Notifications::WorkDoneProgressCancelParamsType 98 | ¶ms) { 99 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 100 | &QLspNotifySignals::receivedWorkDoneProgressCancelNotification); 101 | if (isSignalConnected(notificationSignal)) 102 | emit receivedWorkDoneProgressCancelNotification(params); 103 | else 104 | protocol->handleUndispatchedNotification(method, params); 105 | }); 106 | 107 | protocol->registerTelemetryEventNotificationHandler( 108 | [this, 109 | protocol](const QByteArray &method, 110 | const QLspSpecification::Notifications::TelemetryEventParamsType ¶ms) { 111 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 112 | &QLspNotifySignals::receivedTelemetryEventNotification); 113 | if (isSignalConnected(notificationSignal)) 114 | emit receivedTelemetryEventNotification(params); 115 | else 116 | protocol->handleUndispatchedNotification(method, params); 117 | }); 118 | 119 | protocol->registerDidChangeWorkspaceFoldersNotificationHandler( 120 | [this, 121 | protocol](const QByteArray &method, 122 | const QLspSpecification::Notifications::DidChangeWorkspaceFoldersParamsType 123 | ¶ms) { 124 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 125 | &QLspNotifySignals::receivedDidChangeWorkspaceFoldersNotification); 126 | if (isSignalConnected(notificationSignal)) 127 | emit receivedDidChangeWorkspaceFoldersNotification(params); 128 | else 129 | protocol->handleUndispatchedNotification(method, params); 130 | }); 131 | 132 | protocol->registerDidChangeConfigurationNotificationHandler( 133 | [this, 134 | protocol](const QByteArray &method, 135 | const QLspSpecification::Notifications::DidChangeConfigurationParamsType 136 | ¶ms) { 137 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 138 | &QLspNotifySignals::receivedDidChangeConfigurationNotification); 139 | if (isSignalConnected(notificationSignal)) 140 | emit receivedDidChangeConfigurationNotification(params); 141 | else 142 | protocol->handleUndispatchedNotification(method, params); 143 | }); 144 | 145 | protocol->registerDidChangeWatchedFilesNotificationHandler( 146 | [this, protocol](const QByteArray &method, 147 | const QLspSpecification::Notifications::DidChangeWatchedFilesParamsType 148 | ¶ms) { 149 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 150 | &QLspNotifySignals::receivedDidChangeWatchedFilesNotification); 151 | if (isSignalConnected(notificationSignal)) 152 | emit receivedDidChangeWatchedFilesNotification(params); 153 | else 154 | protocol->handleUndispatchedNotification(method, params); 155 | }); 156 | 157 | protocol->registerCreateFilesNotificationHandler( 158 | [this, 159 | protocol](const QByteArray &method, 160 | const QLspSpecification::Notifications::CreateFilesParamsType ¶ms) { 161 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 162 | &QLspNotifySignals::receivedCreateFilesNotification); 163 | if (isSignalConnected(notificationSignal)) 164 | emit receivedCreateFilesNotification(params); 165 | else 166 | protocol->handleUndispatchedNotification(method, params); 167 | }); 168 | 169 | protocol->registerRenameFilesNotificationHandler( 170 | [this, 171 | protocol](const QByteArray &method, 172 | const QLspSpecification::Notifications::RenameFilesParamsType ¶ms) { 173 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 174 | &QLspNotifySignals::receivedRenameFilesNotification); 175 | if (isSignalConnected(notificationSignal)) 176 | emit receivedRenameFilesNotification(params); 177 | else 178 | protocol->handleUndispatchedNotification(method, params); 179 | }); 180 | 181 | protocol->registerDeleteFilesNotificationHandler( 182 | [this, 183 | protocol](const QByteArray &method, 184 | const QLspSpecification::Notifications::DeleteFilesParamsType ¶ms) { 185 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 186 | &QLspNotifySignals::receivedDeleteFilesNotification); 187 | if (isSignalConnected(notificationSignal)) 188 | emit receivedDeleteFilesNotification(params); 189 | else 190 | protocol->handleUndispatchedNotification(method, params); 191 | }); 192 | 193 | protocol->registerDidOpenTextDocumentNotificationHandler( 194 | [this, protocol]( 195 | const QByteArray &method, 196 | const QLspSpecification::Notifications::DidOpenTextDocumentParamsType ¶ms) { 197 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 198 | &QLspNotifySignals::receivedDidOpenTextDocumentNotification); 199 | if (isSignalConnected(notificationSignal)) 200 | emit receivedDidOpenTextDocumentNotification(params); 201 | else 202 | protocol->handleUndispatchedNotification(method, params); 203 | }); 204 | 205 | protocol->registerDidChangeTextDocumentNotificationHandler( 206 | [this, protocol](const QByteArray &method, 207 | const QLspSpecification::Notifications::DidChangeTextDocumentParamsType 208 | ¶ms) { 209 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 210 | &QLspNotifySignals::receivedDidChangeTextDocumentNotification); 211 | if (isSignalConnected(notificationSignal)) 212 | emit receivedDidChangeTextDocumentNotification(params); 213 | else 214 | protocol->handleUndispatchedNotification(method, params); 215 | }); 216 | 217 | protocol->registerWillSaveTextDocumentNotificationHandler( 218 | [this, protocol](const QByteArray &method, 219 | const QLspSpecification::Notifications::WillSaveTextDocumentParamsType 220 | ¶ms) { 221 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 222 | &QLspNotifySignals::receivedWillSaveTextDocumentNotification); 223 | if (isSignalConnected(notificationSignal)) 224 | emit receivedWillSaveTextDocumentNotification(params); 225 | else 226 | protocol->handleUndispatchedNotification(method, params); 227 | }); 228 | 229 | protocol->registerDidSaveTextDocumentNotificationHandler( 230 | [this, protocol]( 231 | const QByteArray &method, 232 | const QLspSpecification::Notifications::DidSaveTextDocumentParamsType ¶ms) { 233 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 234 | &QLspNotifySignals::receivedDidSaveTextDocumentNotification); 235 | if (isSignalConnected(notificationSignal)) 236 | emit receivedDidSaveTextDocumentNotification(params); 237 | else 238 | protocol->handleUndispatchedNotification(method, params); 239 | }); 240 | 241 | protocol->registerDidCloseTextDocumentNotificationHandler( 242 | [this, protocol](const QByteArray &method, 243 | const QLspSpecification::Notifications::DidCloseTextDocumentParamsType 244 | ¶ms) { 245 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 246 | &QLspNotifySignals::receivedDidCloseTextDocumentNotification); 247 | if (isSignalConnected(notificationSignal)) 248 | emit receivedDidCloseTextDocumentNotification(params); 249 | else 250 | protocol->handleUndispatchedNotification(method, params); 251 | }); 252 | 253 | protocol->registerPublishDiagnosticsNotificationHandler( 254 | [this, protocol]( 255 | const QByteArray &method, 256 | const QLspSpecification::Notifications::PublishDiagnosticsParamsType ¶ms) { 257 | static const QMetaMethod notificationSignal = QMetaMethod::fromSignal( 258 | &QLspNotifySignals::receivedPublishDiagnosticsNotification); 259 | if (isSignalConnected(notificationSignal)) 260 | emit receivedPublishDiagnosticsNotification(params); 261 | else 262 | protocol->handleUndispatchedNotification(method, params); 263 | }); 264 | } 265 | 266 | QT_END_NAMESPACE 267 | -------------------------------------------------------------------------------- /src/languageserver/qlspnotifysignals_p.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | // this file was generated by the generate.ts script 5 | 6 | #ifndef QLSPNOTIFYSIGNALS_P_H 7 | #define QLSPNOTIFYSIGNALS_P_H 8 | 9 | // 10 | // W A R N I N G 11 | // ------------- 12 | // 13 | // This file is not part of the Qt API. It exists purely as an 14 | // implementation detail. This header file may change from version to 15 | // version without notice, or even be removed. 16 | // 17 | // We mean it. 18 | // 19 | 20 | #include 21 | 22 | QT_BEGIN_NAMESPACE 23 | 24 | class Q_LANGUAGESERVER_EXPORT QLspNotifySignals : public QObject 25 | { 26 | Q_OBJECT 27 | public: 28 | QLspNotifySignals(QObject *parent = nullptr) : QObject(parent) { } 29 | void registerHandlers(QLanguageServerProtocol *protocol); 30 | signals: 31 | void receivedCancelNotification(const QLspSpecification::Notifications::CancelParamsType &); 32 | void receivedInitializedNotification( 33 | const QLspSpecification::Notifications::InitializedParamsType &); 34 | void receivedExitNotification(const QLspSpecification::Notifications::ExitParamsType &); 35 | void receivedLogTraceNotification(const QLspSpecification::Notifications::LogTraceParamsType &); 36 | void receivedSetTraceNotification(const QLspSpecification::Notifications::SetTraceParamsType &); 37 | void receivedShowMessageNotification( 38 | const QLspSpecification::Notifications::ShowMessageParamsType &); 39 | void 40 | receivedLogMessageNotification(const QLspSpecification::Notifications::LogMessageParamsType &); 41 | void receivedWorkDoneProgressCancelNotification( 42 | const QLspSpecification::Notifications::WorkDoneProgressCancelParamsType &); 43 | void receivedTelemetryEventNotification( 44 | const QLspSpecification::Notifications::TelemetryEventParamsType &); 45 | void receivedDidChangeWorkspaceFoldersNotification( 46 | const QLspSpecification::Notifications::DidChangeWorkspaceFoldersParamsType &); 47 | void receivedDidChangeConfigurationNotification( 48 | const QLspSpecification::Notifications::DidChangeConfigurationParamsType &); 49 | void receivedDidChangeWatchedFilesNotification( 50 | const QLspSpecification::Notifications::DidChangeWatchedFilesParamsType &); 51 | void receivedCreateFilesNotification( 52 | const QLspSpecification::Notifications::CreateFilesParamsType &); 53 | void receivedRenameFilesNotification( 54 | const QLspSpecification::Notifications::RenameFilesParamsType &); 55 | void receivedDeleteFilesNotification( 56 | const QLspSpecification::Notifications::DeleteFilesParamsType &); 57 | void receivedDidOpenTextDocumentNotification( 58 | const QLspSpecification::Notifications::DidOpenTextDocumentParamsType &); 59 | void receivedDidChangeTextDocumentNotification( 60 | const QLspSpecification::Notifications::DidChangeTextDocumentParamsType &); 61 | void receivedWillSaveTextDocumentNotification( 62 | const QLspSpecification::Notifications::WillSaveTextDocumentParamsType &); 63 | void receivedDidSaveTextDocumentNotification( 64 | const QLspSpecification::Notifications::DidSaveTextDocumentParamsType &); 65 | void receivedDidCloseTextDocumentNotification( 66 | const QLspSpecification::Notifications::DidCloseTextDocumentParamsType &); 67 | void receivedPublishDiagnosticsNotification( 68 | const QLspSpecification::Notifications::PublishDiagnosticsParamsType &); 69 | }; 70 | 71 | QT_END_NAMESPACE 72 | 73 | #endif // QLSPNOTIFYSIGNALS_P_H 74 | -------------------------------------------------------------------------------- /src/languageserver/qtlanguageserverglobal.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only 3 | 4 | #ifndef QTLANGUAGESERVERGLOBAL_H 5 | #define QTLANGUAGESERVERGLOBAL_H 6 | #include 7 | 8 | // include qtlanguageserverexports.h here instead 9 | // if module is no longer unconditionally static 10 | #define Q_LANGUAGESERVER_EXPORT 11 | 12 | #endif // QTLANGUAGESERVERGLOBAL_H 13 | -------------------------------------------------------------------------------- /src/qtlanguageserver.qdoc: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only 3 | 4 | /*! 5 | \title Qt Language Server 6 | \page qtlanguageserver-index.html 7 | \brief Qt Language Server module provides an implementation of the Language Server Protocol and JSON-RPC. 8 | 9 | Implements the \l 10 | {https://microsoft.github.io/language-server-protocol/specifications/specification-current/} 11 | {Language Server Protocol Specification} (LSP) used to provide features like auto complete, go to definition, find all references. 12 | It also implements the \l 13 | {https://www.jsonrpc.org/specification} {JSON-RPC 2.0} protocol used by the LSP. 14 | 15 | The module does not contain a public API. 16 | 17 | \section1 Licenses and Attributions 18 | 19 | Qt Language Server is available under commercial licenses from \l{The Qt Company}. 20 | In addition, it is available under the 21 | \l{GNU Lesser General Public License, version 3}, or 22 | the \l{GNU General Public License, version 2}. 23 | See \l{Qt Licensing} for further details. 24 | 25 | */ 26 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | if(QT_BUILD_STANDALONE_TESTS) 5 | # Add qt_find_package calls for extra dependencies that need to be found when building 6 | # the standalone tests here. 7 | endif() 8 | qt_build_tests() 9 | -------------------------------------------------------------------------------- /tests/auto/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | add_subdirectory(cmake) 5 | 6 | # For now, don't built auto tests when QT_BUILD_MINIMAL_STATIC_TEST 7 | # is specified and the build is targeting iOS. QT_BUILD_MINIMAL_STATIC_TEST is used in our CI. 8 | # Regular non-cmake build tests shouldn't be built because the CI will try to run them and fail 9 | # due to missing simulator support. 10 | if(IOS AND QT_BUILD_MINIMAL_STATIC_TESTS) 11 | return() 12 | endif() 13 | 14 | add_subdirectory(qjsonrpcprotocol) 15 | add_subdirectory(typedjson) 16 | add_subdirectory(qlanguageserver) 17 | -------------------------------------------------------------------------------- /tests/auto/cmake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | cmake_minimum_required(VERSION 3.16) 5 | 6 | project(qtlanguageserver_cmake_tests) 7 | 8 | enable_testing() 9 | 10 | find_package(Qt6Core REQUIRED) 11 | 12 | include("${_Qt6CTestMacros}") 13 | 14 | _qt_internal_test_expect_pass(test_qtjsonrpc_module) 15 | -------------------------------------------------------------------------------- /tests/auto/cmake/test_qtjsonrpc_module/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | cmake_minimum_required(VERSION 3.16) 5 | 6 | project(test_qtjsonrpc_module) 7 | 8 | find_package(Qt6JsonRpcPrivate REQUIRED) 9 | 10 | add_executable(mainapp main.cpp) 11 | target_link_libraries(mainapp Qt6::JsonRpcPrivate) 12 | -------------------------------------------------------------------------------- /tests/auto/cmake/test_qtjsonrpc_module/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include 5 | 6 | int main(int argc, char **argv) 7 | { 8 | Q_UNUSED(argc); 9 | Q_UNUSED(argv); 10 | 11 | QJsonRpcProtocol protocol; 12 | Q_UNUSED(protocol); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/auto/qjsonrpcprotocol/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | ##################################################################### 5 | ## tst_jsonrpc Test: 6 | ##################################################################### 7 | qt_internal_add_test(tst_qjsonrpcprotocol 8 | SOURCES 9 | tst_qjsonrpcprotocol.cpp 10 | DEFINES 11 | QT_DEPRECATED_WARNINGS 12 | LIBRARIES 13 | Qt::JsonRpcPrivate 14 | Qt::Test 15 | TESTDATA ${test_data} 16 | ) 17 | -------------------------------------------------------------------------------- /tests/auto/qlanguageserver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | ##################################################################### 5 | ## tst_qlanguageserver Test: 6 | ##################################################################### 7 | 8 | qt_internal_add_test(tst_qlanguageserver 9 | SOURCES 10 | tst_qlanguageserver.cpp 11 | qiopipe.h qiopipe.cpp 12 | DEFINES 13 | QT_DEPRECATED_WARNINGS 14 | LIBRARIES 15 | Qt::CorePrivate 16 | Qt::LanguageServerPrivate 17 | Qt::Test 18 | TESTDATA ${test_data} 19 | ) 20 | -------------------------------------------------------------------------------- /tests/auto/qlanguageserver/qiopipe.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #include "qiopipe.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | QT_BEGIN_NAMESPACE 14 | 15 | class QPipeEndPoint : public QIODevice 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | bool isSequential() const override; 21 | qint64 bytesAvailable() const override; 22 | 23 | void setRemoteEndPoint(QPipeEndPoint *other); 24 | 25 | protected: 26 | qint64 readData(char *data, qint64 maxlen) override; 27 | qint64 writeData(const char *data, qint64 len) override; 28 | 29 | private: 30 | QByteArray m_buffer; 31 | QPointer m_remoteEndPoint; 32 | }; 33 | 34 | class QIOPipePrivate final : public QObjectPrivate 35 | { 36 | Q_DECLARE_PUBLIC(QIOPipe) 37 | public: 38 | QIOPipePrivate(); 39 | ~QIOPipePrivate() override; 40 | 41 | std::unique_ptr end1; 42 | std::unique_ptr end2; 43 | }; 44 | 45 | QIOPipe::QIOPipe(QObject *parent) : 46 | QObject(*(new QIOPipePrivate()), parent) 47 | { 48 | } 49 | 50 | bool QIOPipe::open(QIODevice::OpenMode mode) 51 | { 52 | Q_D(QIOPipe); 53 | 54 | if (!d->end1->open(mode)) 55 | return false; 56 | switch (mode & QIODevice::ReadWrite) { 57 | case QIODevice::WriteOnly: 58 | case QIODevice::ReadOnly: 59 | return d->end2->open(mode ^ QIODevice::ReadWrite); 60 | default: 61 | return d->end2->open(mode); 62 | } 63 | } 64 | 65 | QIODevice *QIOPipe::end1() const 66 | { 67 | Q_D(const QIOPipe); 68 | return d->end1.get(); 69 | } 70 | 71 | QIODevice *QIOPipe::end2() const 72 | { 73 | Q_D(const QIOPipe); 74 | return d->end2.get(); 75 | } 76 | 77 | QIOPipePrivate::QIOPipePrivate() : 78 | end1(std::make_unique()), 79 | end2(std::make_unique()) 80 | { 81 | end1->setRemoteEndPoint(end2.get()); 82 | end2->setRemoteEndPoint(end1.get()); 83 | } 84 | 85 | QIOPipePrivate::~QIOPipePrivate() 86 | { 87 | } 88 | 89 | bool QPipeEndPoint::isSequential() const 90 | { 91 | return true; 92 | } 93 | 94 | qint64 QPipeEndPoint::bytesAvailable() const 95 | { 96 | return m_buffer.size() + QIODevice::bytesAvailable(); 97 | } 98 | 99 | void QPipeEndPoint::setRemoteEndPoint(QPipeEndPoint *other) 100 | { 101 | m_remoteEndPoint = other; 102 | } 103 | 104 | qint64 QPipeEndPoint::readData(char *data, qint64 maxlen) 105 | { 106 | maxlen = qMin(maxlen, static_cast(m_buffer.size())); 107 | if (maxlen <= 0) 108 | return 0; 109 | 110 | Q_ASSERT(maxlen > 0); 111 | Q_ASSERT(maxlen <= std::numeric_limits::max()); 112 | memcpy(data, m_buffer.data(), static_cast(maxlen)); 113 | m_buffer = m_buffer.mid(static_cast(maxlen)); 114 | return maxlen; 115 | } 116 | 117 | qint64 QPipeEndPoint::writeData(const char *data, qint64 len) 118 | { 119 | if (!m_remoteEndPoint) 120 | return -1; 121 | 122 | if (len <= 0) 123 | return 0; 124 | 125 | QByteArray &buffer = m_remoteEndPoint->m_buffer; 126 | const qint64 prevLen = buffer.size(); 127 | Q_ASSERT(prevLen >= 0); 128 | len = qMin(len, std::numeric_limits::max() - prevLen); 129 | 130 | if (len == 0) 131 | return 0; 132 | 133 | Q_ASSERT(len > 0); 134 | Q_ASSERT(prevLen + len > 0); 135 | Q_ASSERT(prevLen + len <= std::numeric_limits::max()); 136 | 137 | buffer.resize(static_cast(prevLen + len)); 138 | memcpy(buffer.data() + prevLen, data, static_cast(len)); 139 | emit bytesWritten(len); 140 | emit m_remoteEndPoint->readyRead(); 141 | return len; 142 | } 143 | 144 | QT_END_NAMESPACE 145 | 146 | #include 147 | -------------------------------------------------------------------------------- /tests/auto/qlanguageserver/qiopipe.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | 4 | #ifndef QECHODEVICE_H 5 | #define QECHODEVICE_H 6 | 7 | #include 8 | 9 | QT_BEGIN_NAMESPACE 10 | 11 | class QIOPipePrivate; 12 | class QIOPipe : public QObject 13 | { 14 | Q_OBJECT 15 | Q_DECLARE_PRIVATE(QIOPipe) 16 | 17 | public: 18 | QIOPipe(QObject *parent = nullptr); 19 | 20 | bool open(QIODevice::OpenMode mode); 21 | 22 | QIODevice *end1() const; 23 | QIODevice *end2() const; 24 | }; 25 | 26 | QT_END_NAMESPACE 27 | 28 | #endif // QECHODEVICE_H 29 | -------------------------------------------------------------------------------- /tests/auto/typedjson/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | ##################################################################### 5 | ## tst_typedjson Test: 6 | ##################################################################### 7 | # Collect test data 8 | file(GLOB_RECURSE test_data_glob 9 | RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/ 10 | data/*) 11 | list(APPEND test_data ${test_data_glob}) 12 | 13 | qt_internal_add_test(tst_typedjson 14 | SOURCES 15 | tst_typedjson.cpp tst_typedjson.h 16 | DEFINES 17 | QT_DEPRECATED_WARNINGS 18 | LIBRARIES 19 | Qt::JsonRpcPrivate 20 | Qt::Test 21 | TESTDATA ${test_data} 22 | ) 23 | 24 | qt_internal_extend_target(tst_typedjson CONDITION NOT ANDROID AND NOT IOS 25 | DEFINES 26 | QT_TYPEDJSON_DATADIR="${CMAKE_CURRENT_SOURCE_DIR}/data" 27 | ) 28 | 29 | qt_internal_extend_target(tst_typedjson CONDITION ANDROID OR IOS 30 | DEFINES 31 | QT_TYPEDJSON_DATADIR=":/data" 32 | ) 33 | -------------------------------------------------------------------------------- /tests/auto/typedjson/data/Range.json: -------------------------------------------------------------------------------- 1 | { 2 | "start": { "line": 5, "character": 23 }, 3 | "end" : { "line": 6, "character": 0 } 4 | } 5 | -------------------------------------------------------------------------------- /tests/auto/typedjson/data/ReferenceParams.json: -------------------------------------------------------------------------------- 1 | { 2 | "textDocument": { 3 | "uri": "file:///folder/file.ts" 4 | }, 5 | "position": { 6 | "line": 9, 7 | "character": 5 8 | }, 9 | "context": { 10 | "includeDeclaration": true 11 | }, 12 | "workDoneToken": "1d546990-40a3-4b77-b134-46622995f6ae" 13 | } 14 | -------------------------------------------------------------------------------- /tests/auto/typedjson/tst_typedjson.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | #include "tst_typedjson.h" 4 | 5 | QTEST_MAIN(QTypedJson::TestTypedJson) 6 | -------------------------------------------------------------------------------- /tests/auto/typedjson/tst_typedjson.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2021 The Qt Company Ltd. 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only 3 | #ifndef TST_TYPEDJSON_H 4 | #define TST_TYPEDJSON_H 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace TestSpec { 16 | 17 | class Position 18 | { 19 | public: 20 | int line = {}; 21 | int character = {}; 22 | 23 | template 24 | void walk(W &w) 25 | { 26 | field(w, "line", line); 27 | field(w, "character", character); 28 | } 29 | }; 30 | 31 | class Range 32 | { 33 | public: 34 | Position start = {}; 35 | Position end = {}; 36 | 37 | template 38 | void walk(W &w) 39 | { 40 | field(w, "start", start); 41 | field(w, "end", end); 42 | } 43 | }; 44 | 45 | using ProgressToken = std::variant; 46 | 47 | class ReferenceContext 48 | { 49 | public: 50 | bool includeDeclaration = {}; 51 | 52 | template 53 | void walk(W &w) 54 | { 55 | field(w, "includeDeclaration", includeDeclaration); 56 | } 57 | }; 58 | 59 | class WorkDoneProgressParams 60 | { 61 | public: 62 | std::optional workDoneToken = {}; 63 | 64 | template 65 | void walk(W &w) 66 | { 67 | field(w, "workDoneToken", workDoneToken); 68 | } 69 | }; 70 | 71 | class TextDocumentIdentifier 72 | { 73 | public: 74 | QByteArray uri = {}; 75 | 76 | template 77 | void walk(W &w) 78 | { 79 | field(w, "uri", uri); 80 | } 81 | }; 82 | 83 | class TextDocumentPositionParams 84 | { 85 | public: 86 | TextDocumentIdentifier textDocument = {}; 87 | Position position = {}; 88 | 89 | template 90 | void walk(W &w) 91 | { 92 | field(w, "textDocument", textDocument); 93 | field(w, "position", position); 94 | } 95 | }; 96 | 97 | class PartialResultParams 98 | { 99 | public: 100 | std::optional partialResultToken = {}; 101 | 102 | template 103 | void walk(W &w) 104 | { 105 | field(w, "partialResultToken", partialResultToken); 106 | } 107 | }; 108 | 109 | class ReferenceParams : public TextDocumentPositionParams, 110 | WorkDoneProgressParams, 111 | PartialResultParams 112 | { 113 | public: 114 | ReferenceContext context = {}; 115 | 116 | template 117 | void walk(W &w) 118 | { 119 | TextDocumentPositionParams::walk(w); 120 | WorkDoneProgressParams::walk(w); 121 | PartialResultParams::walk(w); 122 | field(w, "context", context); 123 | } 124 | }; 125 | 126 | class TextDocumentEdit 127 | { 128 | public: 129 | TextDocumentIdentifier textDocument = {}; 130 | 131 | template 132 | void walk(W &w) 133 | { 134 | field(w, "textDocument", textDocument); 135 | } 136 | }; 137 | class WorkspaceEdit 138 | { 139 | public: 140 | std::optional< 141 | std::variant, QList>>> 142 | documentChanges = {}; 143 | 144 | template 145 | void walk(W &w) 146 | { 147 | field(w, "documentChanges", documentChanges); 148 | } 149 | }; 150 | class PatchedWorkspaceEdit 151 | { 152 | public: 153 | std::optional>> documentChanges = {}; 154 | 155 | template 156 | void walk(W &w) 157 | { 158 | field(w, "documentChanges", documentChanges); 159 | } 160 | }; 161 | } // namespace TestSpec 162 | 163 | QT_BEGIN_NAMESPACE 164 | namespace QTypedJson { 165 | using namespace Qt::StringLiterals; 166 | 167 | class TestTypedJson : public QObject 168 | { 169 | Q_OBJECT 170 | QJsonObject loadJson(QString filePath) 171 | { 172 | QFile f(filePath); 173 | if (!f.open(QIODevice::ReadOnly)) { 174 | qWarning() << "Failed opening" << filePath << "due to" << f.errorString(); 175 | Q_ASSERT(false); 176 | } 177 | QByteArray data = f.readAll(); 178 | QJsonParseError error; 179 | QJsonDocument json = QJsonDocument::fromJson(data, &error); 180 | if (json.isNull()) { 181 | qWarning() << "Error reading json from" << filePath << ": " << error.errorString(); 182 | Q_ASSERT(false); 183 | } 184 | return json.object(); 185 | } 186 | template 187 | void testT(QString file, T &value, bool checkJson = true) 188 | { 189 | QJsonObject obj = loadJson(file); 190 | QTypedJson::Reader r(obj); 191 | QTypedJson::doWalk(r, value); 192 | QJsonObject obj2B = toJsonValue(value).toObject(); 193 | QByteArray json2B = QJsonDocument(obj2B).toJson(); 194 | if (checkJson) { 195 | QByteArray json1 = QJsonDocument(obj).toJson(); 196 | QCOMPARE(json1, json2B); 197 | } 198 | QTypedJson::Reader r2B(obj2B); 199 | T value2B; 200 | QTypedJson::doWalk(r2B, value2B); 201 | QJsonObject obj3 = toJsonValue(value2B).toObject(); 202 | QByteArray json3 = QJsonDocument(obj3).toJson(); 203 | QCOMPARE(json2B, json3); 204 | } 205 | private slots: 206 | 207 | void testJson() 208 | { 209 | QString baseDir = QLatin1String(QT_TYPEDJSON_DATADIR); 210 | QString f1 = baseDir + QLatin1String("/Range.json"); 211 | TestSpec::Range value; 212 | testT(f1, value); 213 | QCOMPARE(value.start.line, 5); 214 | QCOMPARE(value.start.character, 23); 215 | QCOMPARE(value.end.line, 6); 216 | QCOMPARE(value.end.character, 0); 217 | QString f2 = baseDir + QLatin1String("/ReferenceParams.json"); 218 | TestSpec::ReferenceParams value2; 219 | testT(f2, value2); 220 | QCOMPARE(value2.textDocument.uri, QByteArray("file:///folder/file.ts")); 221 | QCOMPARE(value2.position.line, 9); 222 | QCOMPARE(value2.position.character, 5); 223 | } 224 | 225 | void qtbug124592() 226 | { 227 | const QString jsonList{ uR"({ 228 | "documentChanges" : [ 229 | { "textDocument": { "uri": "a" } }, 230 | { "textDocument": { "uri": "b" } }, 231 | { "textDocument": { "uri": "c" } } 232 | ] })"_s }; 233 | const QString jsonListOfVariants{ uR"({ 234 | "documentChanges" : [ 235 | { "textDocument": { "uri": "a" } }, 236 | { "textDocument": { "uri": "b" } }, 237 | { "textDocument": { "uri": "c" } }, 238 | { "line": 5, "character": 6 } 239 | ] })"_s }; 240 | 241 | QJsonDocument list = QJsonDocument::fromJson(jsonList.toUtf8()); 242 | QVERIFY(!list.isNull()); 243 | QJsonDocument listOfVariants = QJsonDocument::fromJson(jsonListOfVariants.toUtf8()); 244 | QVERIFY(!listOfVariants.isNull()); 245 | 246 | { 247 | TestSpec::WorkspaceEdit edit; 248 | QTypedJson::Reader r(list.object()); 249 | QTypedJson::doWalk(r, edit); 250 | QVERIFY(edit.documentChanges); 251 | QVERIFY(std::holds_alternative>( 252 | *edit.documentChanges)); 253 | auto list = std::get>(*edit.documentChanges); 254 | QCOMPARE(list.size(), 3); 255 | QCOMPARE(list[0].textDocument.uri, u"a"_s); 256 | QCOMPARE(list[1].textDocument.uri, u"b"_s); 257 | QCOMPARE(list[2].textDocument.uri, u"c"_s); 258 | } 259 | 260 | { 261 | TestSpec::WorkspaceEdit edit; 262 | QTypedJson::Reader r(listOfVariants.object()); 263 | QTypedJson::doWalk(r, edit); 264 | QVERIFY(edit.documentChanges); 265 | auto isVariant = std::holds_alternative< 266 | QList>>( 267 | *edit.documentChanges); 268 | QVERIFY(isVariant); 269 | 270 | auto list = 271 | std::get>>( 272 | *edit.documentChanges); 273 | QCOMPARE(list.size(), 4); 274 | QVERIFY(std::holds_alternative(list[0])); 275 | QCOMPARE(std::get(list[0]).textDocument.uri, u"a"_s); 276 | QVERIFY(std::holds_alternative(list[1])); 277 | QCOMPARE(std::get(list[1]).textDocument.uri, u"b"_s); 278 | QVERIFY(std::holds_alternative(list[2])); 279 | QCOMPARE(std::get(list[2]).textDocument.uri, u"c"_s); 280 | QVERIFY(std::holds_alternative(list[3])); 281 | QCOMPARE(std::get(list[3]).line, 5); 282 | QCOMPARE(std::get(list[3]).character, 6); 283 | } 284 | } 285 | 286 | void equivalenceListAndListOfVariants() 287 | { 288 | const QString jsonList{ uR"({ 289 | "documentChanges" : [ 290 | { "textDocument": { "uri": "a" } }, 291 | { "textDocument": { "uri": "b" } }, 292 | { "textDocument": { "uri": "c" } } 293 | ] })"_s }; 294 | 295 | QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonList.toUtf8()); 296 | QVERIFY(!jsonDocument.isNull()); 297 | 298 | // show that the list of variant can serialize from the list of TextDocumentEdit 299 | TestSpec::PatchedWorkspaceEdit hasListOfVariant; 300 | QTypedJson::Reader r(jsonDocument.object()); 301 | QTypedJson::doWalk(r, hasListOfVariant); 302 | QVERIFY(hasListOfVariant.documentChanges); 303 | auto list = *hasListOfVariant.documentChanges; 304 | QCOMPARE(list.size(), 3); 305 | QVERIFY(std::holds_alternative(list[0])); 306 | QCOMPARE(std::get(list[0]).textDocument.uri, u"a"_s); 307 | QVERIFY(std::holds_alternative(list[1])); 308 | QCOMPARE(std::get(list[1]).textDocument.uri, u"b"_s); 309 | QVERIFY(std::holds_alternative(list[2])); 310 | QCOMPARE(std::get(list[2]).textDocument.uri, u"c"_s); 311 | 312 | // show that the list of variant can deserialize into the list of TextDocumentEdit 313 | TestSpec::WorkspaceEdit hasListOfTextDocuments; 314 | QTypedJson::Reader r2(jsonDocument.object()); 315 | QTypedJson::doWalk(r2, hasListOfTextDocuments); 316 | QCOMPARE(toJsonValue(hasListOfVariant), toJsonValue(hasListOfTextDocuments)); 317 | } 318 | }; 319 | 320 | } // namespace QTypedJson 321 | QT_END_NAMESPACE 322 | 323 | #endif // TST_TYPEDJSON_H 324 | --------------------------------------------------------------------------------