jsonCompletion.js 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. (function (factory) {
  6. if (typeof module === "object" && typeof module.exports === "object") {
  7. var v = factory(require, exports);
  8. if (v !== undefined) module.exports = v;
  9. }
  10. else if (typeof define === "function" && define.amd) {
  11. define(["require", "exports", "../parser/jsonParser", "jsonc-parser", "../utils/json", "../utils/strings", "../utils/objects", "../jsonLanguageTypes", "vscode-nls"], factory);
  12. }
  13. })(function (require, exports) {
  14. "use strict";
  15. Object.defineProperty(exports, "__esModule", { value: true });
  16. exports.JSONCompletion = void 0;
  17. var Parser = require("../parser/jsonParser");
  18. var Json = require("jsonc-parser");
  19. var json_1 = require("../utils/json");
  20. var strings_1 = require("../utils/strings");
  21. var objects_1 = require("../utils/objects");
  22. var jsonLanguageTypes_1 = require("../jsonLanguageTypes");
  23. var nls = require("vscode-nls");
  24. var localize = nls.loadMessageBundle();
  25. var valueCommitCharacters = [',', '}', ']'];
  26. var propertyCommitCharacters = [':'];
  27. var JSONCompletion = /** @class */ (function () {
  28. function JSONCompletion(schemaService, contributions, promiseConstructor, clientCapabilities) {
  29. if (contributions === void 0) { contributions = []; }
  30. if (promiseConstructor === void 0) { promiseConstructor = Promise; }
  31. if (clientCapabilities === void 0) { clientCapabilities = {}; }
  32. this.schemaService = schemaService;
  33. this.contributions = contributions;
  34. this.promiseConstructor = promiseConstructor;
  35. this.clientCapabilities = clientCapabilities;
  36. }
  37. JSONCompletion.prototype.doResolve = function (item) {
  38. for (var i = this.contributions.length - 1; i >= 0; i--) {
  39. var resolveCompletion = this.contributions[i].resolveCompletion;
  40. if (resolveCompletion) {
  41. var resolver = resolveCompletion(item);
  42. if (resolver) {
  43. return resolver;
  44. }
  45. }
  46. }
  47. return this.promiseConstructor.resolve(item);
  48. };
  49. JSONCompletion.prototype.doComplete = function (document, position, doc) {
  50. var _this = this;
  51. var result = {
  52. items: [],
  53. isIncomplete: false
  54. };
  55. var text = document.getText();
  56. var offset = document.offsetAt(position);
  57. var node = doc.getNodeFromOffset(offset, true);
  58. if (this.isInComment(document, node ? node.offset : 0, offset)) {
  59. return Promise.resolve(result);
  60. }
  61. if (node && (offset === node.offset + node.length) && offset > 0) {
  62. var ch = text[offset - 1];
  63. if (node.type === 'object' && ch === '}' || node.type === 'array' && ch === ']') {
  64. // after ] or }
  65. node = node.parent;
  66. }
  67. }
  68. var currentWord = this.getCurrentWord(document, offset);
  69. var overwriteRange;
  70. if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
  71. overwriteRange = jsonLanguageTypes_1.Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
  72. }
  73. else {
  74. var overwriteStart = offset - currentWord.length;
  75. if (overwriteStart > 0 && text[overwriteStart - 1] === '"') {
  76. overwriteStart--;
  77. }
  78. overwriteRange = jsonLanguageTypes_1.Range.create(document.positionAt(overwriteStart), position);
  79. }
  80. var supportsCommitCharacters = false; //this.doesSupportsCommitCharacters(); disabled for now, waiting for new API: https://github.com/microsoft/vscode/issues/42544
  81. var proposed = {};
  82. var collector = {
  83. add: function (suggestion) {
  84. var label = suggestion.label;
  85. var existing = proposed[label];
  86. if (!existing) {
  87. label = label.replace(/[\n]/g, '↵');
  88. if (label.length > 60) {
  89. var shortendedLabel = label.substr(0, 57).trim() + '...';
  90. if (!proposed[shortendedLabel]) {
  91. label = shortendedLabel;
  92. }
  93. }
  94. if (overwriteRange && suggestion.insertText !== undefined) {
  95. suggestion.textEdit = jsonLanguageTypes_1.TextEdit.replace(overwriteRange, suggestion.insertText);
  96. }
  97. if (supportsCommitCharacters) {
  98. suggestion.commitCharacters = suggestion.kind === jsonLanguageTypes_1.CompletionItemKind.Property ? propertyCommitCharacters : valueCommitCharacters;
  99. }
  100. suggestion.label = label;
  101. proposed[label] = suggestion;
  102. result.items.push(suggestion);
  103. }
  104. else {
  105. if (!existing.documentation) {
  106. existing.documentation = suggestion.documentation;
  107. }
  108. if (!existing.detail) {
  109. existing.detail = suggestion.detail;
  110. }
  111. }
  112. },
  113. setAsIncomplete: function () {
  114. result.isIncomplete = true;
  115. },
  116. error: function (message) {
  117. console.error(message);
  118. },
  119. log: function (message) {
  120. console.log(message);
  121. },
  122. getNumberOfProposals: function () {
  123. return result.items.length;
  124. }
  125. };
  126. return this.schemaService.getSchemaForResource(document.uri, doc).then(function (schema) {
  127. var collectionPromises = [];
  128. var addValue = true;
  129. var currentKey = '';
  130. var currentProperty = undefined;
  131. if (node) {
  132. if (node.type === 'string') {
  133. var parent = node.parent;
  134. if (parent && parent.type === 'property' && parent.keyNode === node) {
  135. addValue = !parent.valueNode;
  136. currentProperty = parent;
  137. currentKey = text.substr(node.offset + 1, node.length - 2);
  138. if (parent) {
  139. node = parent.parent;
  140. }
  141. }
  142. }
  143. }
  144. // proposals for properties
  145. if (node && node.type === 'object') {
  146. // don't suggest keys when the cursor is just before the opening curly brace
  147. if (node.offset === offset) {
  148. return result;
  149. }
  150. // don't suggest properties that are already present
  151. var properties = node.properties;
  152. properties.forEach(function (p) {
  153. if (!currentProperty || currentProperty !== p) {
  154. proposed[p.keyNode.value] = jsonLanguageTypes_1.CompletionItem.create('__');
  155. }
  156. });
  157. var separatorAfter_1 = '';
  158. if (addValue) {
  159. separatorAfter_1 = _this.evaluateSeparatorAfter(document, document.offsetAt(overwriteRange.end));
  160. }
  161. if (schema) {
  162. // property proposals with schema
  163. _this.getPropertyCompletions(schema, doc, node, addValue, separatorAfter_1, collector);
  164. }
  165. else {
  166. // property proposals without schema
  167. _this.getSchemaLessPropertyCompletions(doc, node, currentKey, collector);
  168. }
  169. var location_1 = Parser.getNodePath(node);
  170. _this.contributions.forEach(function (contribution) {
  171. var collectPromise = contribution.collectPropertyCompletions(document.uri, location_1, currentWord, addValue, separatorAfter_1 === '', collector);
  172. if (collectPromise) {
  173. collectionPromises.push(collectPromise);
  174. }
  175. });
  176. if ((!schema && currentWord.length > 0 && text.charAt(offset - currentWord.length - 1) !== '"')) {
  177. collector.add({
  178. kind: jsonLanguageTypes_1.CompletionItemKind.Property,
  179. label: _this.getLabelForValue(currentWord),
  180. insertText: _this.getInsertTextForProperty(currentWord, undefined, false, separatorAfter_1),
  181. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet, documentation: '',
  182. });
  183. collector.setAsIncomplete();
  184. }
  185. }
  186. // proposals for values
  187. var types = {};
  188. if (schema) {
  189. // value proposals with schema
  190. _this.getValueCompletions(schema, doc, node, offset, document, collector, types);
  191. }
  192. else {
  193. // value proposals without schema
  194. _this.getSchemaLessValueCompletions(doc, node, offset, document, collector);
  195. }
  196. if (_this.contributions.length > 0) {
  197. _this.getContributedValueCompletions(doc, node, offset, document, collector, collectionPromises);
  198. }
  199. return _this.promiseConstructor.all(collectionPromises).then(function () {
  200. if (collector.getNumberOfProposals() === 0) {
  201. var offsetForSeparator = offset;
  202. if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
  203. offsetForSeparator = node.offset + node.length;
  204. }
  205. var separatorAfter = _this.evaluateSeparatorAfter(document, offsetForSeparator);
  206. _this.addFillerValueCompletions(types, separatorAfter, collector);
  207. }
  208. return result;
  209. });
  210. });
  211. };
  212. JSONCompletion.prototype.getPropertyCompletions = function (schema, doc, node, addValue, separatorAfter, collector) {
  213. var _this = this;
  214. var matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset);
  215. matchingSchemas.forEach(function (s) {
  216. if (s.node === node && !s.inverted) {
  217. var schemaProperties_1 = s.schema.properties;
  218. if (schemaProperties_1) {
  219. Object.keys(schemaProperties_1).forEach(function (key) {
  220. var propertySchema = schemaProperties_1[key];
  221. if (typeof propertySchema === 'object' && !propertySchema.deprecationMessage && !propertySchema.doNotSuggest) {
  222. var proposal = {
  223. kind: jsonLanguageTypes_1.CompletionItemKind.Property,
  224. label: key,
  225. insertText: _this.getInsertTextForProperty(key, propertySchema, addValue, separatorAfter),
  226. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  227. filterText: _this.getFilterTextForValue(key),
  228. documentation: _this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
  229. };
  230. if (propertySchema.suggestSortText !== undefined) {
  231. proposal.sortText = propertySchema.suggestSortText;
  232. }
  233. if (proposal.insertText && strings_1.endsWith(proposal.insertText, "$1" + separatorAfter)) {
  234. proposal.command = {
  235. title: 'Suggest',
  236. command: 'editor.action.triggerSuggest'
  237. };
  238. }
  239. collector.add(proposal);
  240. }
  241. });
  242. }
  243. var schemaPropertyNames_1 = s.schema.propertyNames;
  244. if (typeof schemaPropertyNames_1 === 'object' && !schemaPropertyNames_1.deprecationMessage && !schemaPropertyNames_1.doNotSuggest) {
  245. var propertyNameCompletionItem = function (name, enumDescription) {
  246. if (enumDescription === void 0) { enumDescription = undefined; }
  247. var proposal = {
  248. kind: jsonLanguageTypes_1.CompletionItemKind.Property,
  249. label: name,
  250. insertText: _this.getInsertTextForProperty(name, undefined, addValue, separatorAfter),
  251. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  252. filterText: _this.getFilterTextForValue(name),
  253. documentation: enumDescription || _this.fromMarkup(schemaPropertyNames_1.markdownDescription) || schemaPropertyNames_1.description || '',
  254. };
  255. if (schemaPropertyNames_1.suggestSortText !== undefined) {
  256. proposal.sortText = schemaPropertyNames_1.suggestSortText;
  257. }
  258. if (proposal.insertText && strings_1.endsWith(proposal.insertText, "$1" + separatorAfter)) {
  259. proposal.command = {
  260. title: 'Suggest',
  261. command: 'editor.action.triggerSuggest'
  262. };
  263. }
  264. collector.add(proposal);
  265. };
  266. if (schemaPropertyNames_1.enum) {
  267. for (var i = 0; i < schemaPropertyNames_1.enum.length; i++) {
  268. var enumDescription = undefined;
  269. if (schemaPropertyNames_1.markdownEnumDescriptions && i < schemaPropertyNames_1.markdownEnumDescriptions.length) {
  270. enumDescription = _this.fromMarkup(schemaPropertyNames_1.markdownEnumDescriptions[i]);
  271. }
  272. else if (schemaPropertyNames_1.enumDescriptions && i < schemaPropertyNames_1.enumDescriptions.length) {
  273. enumDescription = schemaPropertyNames_1.enumDescriptions[i];
  274. }
  275. propertyNameCompletionItem(schemaPropertyNames_1.enum[i], enumDescription);
  276. }
  277. }
  278. if (schemaPropertyNames_1.const) {
  279. propertyNameCompletionItem(schemaPropertyNames_1.const);
  280. }
  281. }
  282. }
  283. });
  284. };
  285. JSONCompletion.prototype.getSchemaLessPropertyCompletions = function (doc, node, currentKey, collector) {
  286. var _this = this;
  287. var collectCompletionsForSimilarObject = function (obj) {
  288. obj.properties.forEach(function (p) {
  289. var key = p.keyNode.value;
  290. collector.add({
  291. kind: jsonLanguageTypes_1.CompletionItemKind.Property,
  292. label: key,
  293. insertText: _this.getInsertTextForValue(key, ''),
  294. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  295. filterText: _this.getFilterTextForValue(key),
  296. documentation: ''
  297. });
  298. });
  299. };
  300. if (node.parent) {
  301. if (node.parent.type === 'property') {
  302. // if the object is a property value, check the tree for other objects that hang under a property of the same name
  303. var parentKey_1 = node.parent.keyNode.value;
  304. doc.visit(function (n) {
  305. if (n.type === 'property' && n !== node.parent && n.keyNode.value === parentKey_1 && n.valueNode && n.valueNode.type === 'object') {
  306. collectCompletionsForSimilarObject(n.valueNode);
  307. }
  308. return true;
  309. });
  310. }
  311. else if (node.parent.type === 'array') {
  312. // if the object is in an array, use all other array elements as similar objects
  313. node.parent.items.forEach(function (n) {
  314. if (n.type === 'object' && n !== node) {
  315. collectCompletionsForSimilarObject(n);
  316. }
  317. });
  318. }
  319. }
  320. else if (node.type === 'object') {
  321. collector.add({
  322. kind: jsonLanguageTypes_1.CompletionItemKind.Property,
  323. label: '$schema',
  324. insertText: this.getInsertTextForProperty('$schema', undefined, true, ''),
  325. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet, documentation: '',
  326. filterText: this.getFilterTextForValue("$schema")
  327. });
  328. }
  329. };
  330. JSONCompletion.prototype.getSchemaLessValueCompletions = function (doc, node, offset, document, collector) {
  331. var _this = this;
  332. var offsetForSeparator = offset;
  333. if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
  334. offsetForSeparator = node.offset + node.length;
  335. node = node.parent;
  336. }
  337. if (!node) {
  338. collector.add({
  339. kind: this.getSuggestionKind('object'),
  340. label: 'Empty object',
  341. insertText: this.getInsertTextForValue({}, ''),
  342. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  343. documentation: ''
  344. });
  345. collector.add({
  346. kind: this.getSuggestionKind('array'),
  347. label: 'Empty array',
  348. insertText: this.getInsertTextForValue([], ''),
  349. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  350. documentation: ''
  351. });
  352. return;
  353. }
  354. var separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
  355. var collectSuggestionsForValues = function (value) {
  356. if (value.parent && !Parser.contains(value.parent, offset, true)) {
  357. collector.add({
  358. kind: _this.getSuggestionKind(value.type),
  359. label: _this.getLabelTextForMatchingNode(value, document),
  360. insertText: _this.getInsertTextForMatchingNode(value, document, separatorAfter),
  361. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet, documentation: ''
  362. });
  363. }
  364. if (value.type === 'boolean') {
  365. _this.addBooleanValueCompletion(!value.value, separatorAfter, collector);
  366. }
  367. };
  368. if (node.type === 'property') {
  369. if (offset > (node.colonOffset || 0)) {
  370. var valueNode = node.valueNode;
  371. if (valueNode && (offset > (valueNode.offset + valueNode.length) || valueNode.type === 'object' || valueNode.type === 'array')) {
  372. return;
  373. }
  374. // suggest values at the same key
  375. var parentKey_2 = node.keyNode.value;
  376. doc.visit(function (n) {
  377. if (n.type === 'property' && n.keyNode.value === parentKey_2 && n.valueNode) {
  378. collectSuggestionsForValues(n.valueNode);
  379. }
  380. return true;
  381. });
  382. if (parentKey_2 === '$schema' && node.parent && !node.parent.parent) {
  383. this.addDollarSchemaCompletions(separatorAfter, collector);
  384. }
  385. }
  386. }
  387. if (node.type === 'array') {
  388. if (node.parent && node.parent.type === 'property') {
  389. // suggest items of an array at the same key
  390. var parentKey_3 = node.parent.keyNode.value;
  391. doc.visit(function (n) {
  392. if (n.type === 'property' && n.keyNode.value === parentKey_3 && n.valueNode && n.valueNode.type === 'array') {
  393. n.valueNode.items.forEach(collectSuggestionsForValues);
  394. }
  395. return true;
  396. });
  397. }
  398. else {
  399. // suggest items in the same array
  400. node.items.forEach(collectSuggestionsForValues);
  401. }
  402. }
  403. };
  404. JSONCompletion.prototype.getValueCompletions = function (schema, doc, node, offset, document, collector, types) {
  405. var offsetForSeparator = offset;
  406. var parentKey = undefined;
  407. var valueNode = undefined;
  408. if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
  409. offsetForSeparator = node.offset + node.length;
  410. valueNode = node;
  411. node = node.parent;
  412. }
  413. if (!node) {
  414. this.addSchemaValueCompletions(schema.schema, '', collector, types);
  415. return;
  416. }
  417. if ((node.type === 'property') && offset > (node.colonOffset || 0)) {
  418. var valueNode_1 = node.valueNode;
  419. if (valueNode_1 && offset > (valueNode_1.offset + valueNode_1.length)) {
  420. return; // we are past the value node
  421. }
  422. parentKey = node.keyNode.value;
  423. node = node.parent;
  424. }
  425. if (node && (parentKey !== undefined || node.type === 'array')) {
  426. var separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
  427. var matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset, valueNode);
  428. for (var _i = 0, matchingSchemas_1 = matchingSchemas; _i < matchingSchemas_1.length; _i++) {
  429. var s = matchingSchemas_1[_i];
  430. if (s.node === node && !s.inverted && s.schema) {
  431. if (node.type === 'array' && s.schema.items) {
  432. if (Array.isArray(s.schema.items)) {
  433. var index = this.findItemAtOffset(node, document, offset);
  434. if (index < s.schema.items.length) {
  435. this.addSchemaValueCompletions(s.schema.items[index], separatorAfter, collector, types);
  436. }
  437. }
  438. else {
  439. this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types);
  440. }
  441. }
  442. if (parentKey !== undefined) {
  443. var propertyMatched = false;
  444. if (s.schema.properties) {
  445. var propertySchema = s.schema.properties[parentKey];
  446. if (propertySchema) {
  447. propertyMatched = true;
  448. this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
  449. }
  450. }
  451. if (s.schema.patternProperties && !propertyMatched) {
  452. for (var _a = 0, _b = Object.keys(s.schema.patternProperties); _a < _b.length; _a++) {
  453. var pattern = _b[_a];
  454. var regex = strings_1.extendedRegExp(pattern);
  455. if (regex.test(parentKey)) {
  456. propertyMatched = true;
  457. var propertySchema = s.schema.patternProperties[pattern];
  458. this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
  459. }
  460. }
  461. }
  462. if (s.schema.additionalProperties && !propertyMatched) {
  463. var propertySchema = s.schema.additionalProperties;
  464. this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
  465. }
  466. }
  467. }
  468. }
  469. if (parentKey === '$schema' && !node.parent) {
  470. this.addDollarSchemaCompletions(separatorAfter, collector);
  471. }
  472. if (types['boolean']) {
  473. this.addBooleanValueCompletion(true, separatorAfter, collector);
  474. this.addBooleanValueCompletion(false, separatorAfter, collector);
  475. }
  476. if (types['null']) {
  477. this.addNullValueCompletion(separatorAfter, collector);
  478. }
  479. }
  480. };
  481. JSONCompletion.prototype.getContributedValueCompletions = function (doc, node, offset, document, collector, collectionPromises) {
  482. if (!node) {
  483. this.contributions.forEach(function (contribution) {
  484. var collectPromise = contribution.collectDefaultCompletions(document.uri, collector);
  485. if (collectPromise) {
  486. collectionPromises.push(collectPromise);
  487. }
  488. });
  489. }
  490. else {
  491. if (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null') {
  492. node = node.parent;
  493. }
  494. if (node && (node.type === 'property') && offset > (node.colonOffset || 0)) {
  495. var parentKey_4 = node.keyNode.value;
  496. var valueNode = node.valueNode;
  497. if ((!valueNode || offset <= (valueNode.offset + valueNode.length)) && node.parent) {
  498. var location_2 = Parser.getNodePath(node.parent);
  499. this.contributions.forEach(function (contribution) {
  500. var collectPromise = contribution.collectValueCompletions(document.uri, location_2, parentKey_4, collector);
  501. if (collectPromise) {
  502. collectionPromises.push(collectPromise);
  503. }
  504. });
  505. }
  506. }
  507. }
  508. };
  509. JSONCompletion.prototype.addSchemaValueCompletions = function (schema, separatorAfter, collector, types) {
  510. var _this = this;
  511. if (typeof schema === 'object') {
  512. this.addEnumValueCompletions(schema, separatorAfter, collector);
  513. this.addDefaultValueCompletions(schema, separatorAfter, collector);
  514. this.collectTypes(schema, types);
  515. if (Array.isArray(schema.allOf)) {
  516. schema.allOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
  517. }
  518. if (Array.isArray(schema.anyOf)) {
  519. schema.anyOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
  520. }
  521. if (Array.isArray(schema.oneOf)) {
  522. schema.oneOf.forEach(function (s) { return _this.addSchemaValueCompletions(s, separatorAfter, collector, types); });
  523. }
  524. }
  525. };
  526. JSONCompletion.prototype.addDefaultValueCompletions = function (schema, separatorAfter, collector, arrayDepth) {
  527. var _this = this;
  528. if (arrayDepth === void 0) { arrayDepth = 0; }
  529. var hasProposals = false;
  530. if (objects_1.isDefined(schema.default)) {
  531. var type = schema.type;
  532. var value = schema.default;
  533. for (var i = arrayDepth; i > 0; i--) {
  534. value = [value];
  535. type = 'array';
  536. }
  537. collector.add({
  538. kind: this.getSuggestionKind(type),
  539. label: this.getLabelForValue(value),
  540. insertText: this.getInsertTextForValue(value, separatorAfter),
  541. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  542. detail: localize('json.suggest.default', 'Default value')
  543. });
  544. hasProposals = true;
  545. }
  546. if (Array.isArray(schema.examples)) {
  547. schema.examples.forEach(function (example) {
  548. var type = schema.type;
  549. var value = example;
  550. for (var i = arrayDepth; i > 0; i--) {
  551. value = [value];
  552. type = 'array';
  553. }
  554. collector.add({
  555. kind: _this.getSuggestionKind(type),
  556. label: _this.getLabelForValue(value),
  557. insertText: _this.getInsertTextForValue(value, separatorAfter),
  558. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet
  559. });
  560. hasProposals = true;
  561. });
  562. }
  563. if (Array.isArray(schema.defaultSnippets)) {
  564. schema.defaultSnippets.forEach(function (s) {
  565. var type = schema.type;
  566. var value = s.body;
  567. var label = s.label;
  568. var insertText;
  569. var filterText;
  570. if (objects_1.isDefined(value)) {
  571. var type_1 = schema.type;
  572. for (var i = arrayDepth; i > 0; i--) {
  573. value = [value];
  574. type_1 = 'array';
  575. }
  576. insertText = _this.getInsertTextForSnippetValue(value, separatorAfter);
  577. filterText = _this.getFilterTextForSnippetValue(value);
  578. label = label || _this.getLabelForSnippetValue(value);
  579. }
  580. else if (typeof s.bodyText === 'string') {
  581. var prefix = '', suffix = '', indent = '';
  582. for (var i = arrayDepth; i > 0; i--) {
  583. prefix = prefix + indent + '[\n';
  584. suffix = suffix + '\n' + indent + ']';
  585. indent += '\t';
  586. type = 'array';
  587. }
  588. insertText = prefix + indent + s.bodyText.split('\n').join('\n' + indent) + suffix + separatorAfter;
  589. label = label || insertText,
  590. filterText = insertText.replace(/[\n]/g, ''); // remove new lines
  591. }
  592. else {
  593. return;
  594. }
  595. collector.add({
  596. kind: _this.getSuggestionKind(type),
  597. label: label,
  598. documentation: _this.fromMarkup(s.markdownDescription) || s.description,
  599. insertText: insertText,
  600. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  601. filterText: filterText
  602. });
  603. hasProposals = true;
  604. });
  605. }
  606. if (!hasProposals && typeof schema.items === 'object' && !Array.isArray(schema.items) && arrayDepth < 5 /* beware of recursion */) {
  607. this.addDefaultValueCompletions(schema.items, separatorAfter, collector, arrayDepth + 1);
  608. }
  609. };
  610. JSONCompletion.prototype.addEnumValueCompletions = function (schema, separatorAfter, collector) {
  611. if (objects_1.isDefined(schema.const)) {
  612. collector.add({
  613. kind: this.getSuggestionKind(schema.type),
  614. label: this.getLabelForValue(schema.const),
  615. insertText: this.getInsertTextForValue(schema.const, separatorAfter),
  616. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  617. documentation: this.fromMarkup(schema.markdownDescription) || schema.description
  618. });
  619. }
  620. if (Array.isArray(schema.enum)) {
  621. for (var i = 0, length = schema.enum.length; i < length; i++) {
  622. var enm = schema.enum[i];
  623. var documentation = this.fromMarkup(schema.markdownDescription) || schema.description;
  624. if (schema.markdownEnumDescriptions && i < schema.markdownEnumDescriptions.length && this.doesSupportMarkdown()) {
  625. documentation = this.fromMarkup(schema.markdownEnumDescriptions[i]);
  626. }
  627. else if (schema.enumDescriptions && i < schema.enumDescriptions.length) {
  628. documentation = schema.enumDescriptions[i];
  629. }
  630. collector.add({
  631. kind: this.getSuggestionKind(schema.type),
  632. label: this.getLabelForValue(enm),
  633. insertText: this.getInsertTextForValue(enm, separatorAfter),
  634. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  635. documentation: documentation
  636. });
  637. }
  638. }
  639. };
  640. JSONCompletion.prototype.collectTypes = function (schema, types) {
  641. if (Array.isArray(schema.enum) || objects_1.isDefined(schema.const)) {
  642. return;
  643. }
  644. var type = schema.type;
  645. if (Array.isArray(type)) {
  646. type.forEach(function (t) { return types[t] = true; });
  647. }
  648. else if (type) {
  649. types[type] = true;
  650. }
  651. };
  652. JSONCompletion.prototype.addFillerValueCompletions = function (types, separatorAfter, collector) {
  653. if (types['object']) {
  654. collector.add({
  655. kind: this.getSuggestionKind('object'),
  656. label: '{}',
  657. insertText: this.getInsertTextForGuessedValue({}, separatorAfter),
  658. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  659. detail: localize('defaults.object', 'New object'),
  660. documentation: ''
  661. });
  662. }
  663. if (types['array']) {
  664. collector.add({
  665. kind: this.getSuggestionKind('array'),
  666. label: '[]',
  667. insertText: this.getInsertTextForGuessedValue([], separatorAfter),
  668. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  669. detail: localize('defaults.array', 'New array'),
  670. documentation: ''
  671. });
  672. }
  673. };
  674. JSONCompletion.prototype.addBooleanValueCompletion = function (value, separatorAfter, collector) {
  675. collector.add({
  676. kind: this.getSuggestionKind('boolean'),
  677. label: value ? 'true' : 'false',
  678. insertText: this.getInsertTextForValue(value, separatorAfter),
  679. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  680. documentation: ''
  681. });
  682. };
  683. JSONCompletion.prototype.addNullValueCompletion = function (separatorAfter, collector) {
  684. collector.add({
  685. kind: this.getSuggestionKind('null'),
  686. label: 'null',
  687. insertText: 'null' + separatorAfter,
  688. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet,
  689. documentation: ''
  690. });
  691. };
  692. JSONCompletion.prototype.addDollarSchemaCompletions = function (separatorAfter, collector) {
  693. var _this = this;
  694. var schemaIds = this.schemaService.getRegisteredSchemaIds(function (schema) { return schema === 'http' || schema === 'https'; });
  695. schemaIds.forEach(function (schemaId) { return collector.add({
  696. kind: jsonLanguageTypes_1.CompletionItemKind.Module,
  697. label: _this.getLabelForValue(schemaId),
  698. filterText: _this.getFilterTextForValue(schemaId),
  699. insertText: _this.getInsertTextForValue(schemaId, separatorAfter),
  700. insertTextFormat: jsonLanguageTypes_1.InsertTextFormat.Snippet, documentation: ''
  701. }); });
  702. };
  703. JSONCompletion.prototype.getLabelForValue = function (value) {
  704. return JSON.stringify(value);
  705. };
  706. JSONCompletion.prototype.getFilterTextForValue = function (value) {
  707. return JSON.stringify(value);
  708. };
  709. JSONCompletion.prototype.getFilterTextForSnippetValue = function (value) {
  710. return JSON.stringify(value).replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
  711. };
  712. JSONCompletion.prototype.getLabelForSnippetValue = function (value) {
  713. var label = JSON.stringify(value);
  714. return label.replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
  715. };
  716. JSONCompletion.prototype.getInsertTextForPlainText = function (text) {
  717. return text.replace(/[\\\$\}]/g, '\\$&'); // escape $, \ and }
  718. };
  719. JSONCompletion.prototype.getInsertTextForValue = function (value, separatorAfter) {
  720. var text = JSON.stringify(value, null, '\t');
  721. if (text === '{}') {
  722. return '{$1}' + separatorAfter;
  723. }
  724. else if (text === '[]') {
  725. return '[$1]' + separatorAfter;
  726. }
  727. return this.getInsertTextForPlainText(text + separatorAfter);
  728. };
  729. JSONCompletion.prototype.getInsertTextForSnippetValue = function (value, separatorAfter) {
  730. var replacer = function (value) {
  731. if (typeof value === 'string') {
  732. if (value[0] === '^') {
  733. return value.substr(1);
  734. }
  735. }
  736. return JSON.stringify(value);
  737. };
  738. return json_1.stringifyObject(value, '', replacer) + separatorAfter;
  739. };
  740. JSONCompletion.prototype.getInsertTextForGuessedValue = function (value, separatorAfter) {
  741. switch (typeof value) {
  742. case 'object':
  743. if (value === null) {
  744. return '${1:null}' + separatorAfter;
  745. }
  746. return this.getInsertTextForValue(value, separatorAfter);
  747. case 'string':
  748. var snippetValue = JSON.stringify(value);
  749. snippetValue = snippetValue.substr(1, snippetValue.length - 2); // remove quotes
  750. snippetValue = this.getInsertTextForPlainText(snippetValue); // escape \ and }
  751. return '"${1:' + snippetValue + '}"' + separatorAfter;
  752. case 'number':
  753. case 'boolean':
  754. return '${1:' + JSON.stringify(value) + '}' + separatorAfter;
  755. }
  756. return this.getInsertTextForValue(value, separatorAfter);
  757. };
  758. JSONCompletion.prototype.getSuggestionKind = function (type) {
  759. if (Array.isArray(type)) {
  760. var array = type;
  761. type = array.length > 0 ? array[0] : undefined;
  762. }
  763. if (!type) {
  764. return jsonLanguageTypes_1.CompletionItemKind.Value;
  765. }
  766. switch (type) {
  767. case 'string': return jsonLanguageTypes_1.CompletionItemKind.Value;
  768. case 'object': return jsonLanguageTypes_1.CompletionItemKind.Module;
  769. case 'property': return jsonLanguageTypes_1.CompletionItemKind.Property;
  770. default: return jsonLanguageTypes_1.CompletionItemKind.Value;
  771. }
  772. };
  773. JSONCompletion.prototype.getLabelTextForMatchingNode = function (node, document) {
  774. switch (node.type) {
  775. case 'array':
  776. return '[]';
  777. case 'object':
  778. return '{}';
  779. default:
  780. var content = document.getText().substr(node.offset, node.length);
  781. return content;
  782. }
  783. };
  784. JSONCompletion.prototype.getInsertTextForMatchingNode = function (node, document, separatorAfter) {
  785. switch (node.type) {
  786. case 'array':
  787. return this.getInsertTextForValue([], separatorAfter);
  788. case 'object':
  789. return this.getInsertTextForValue({}, separatorAfter);
  790. default:
  791. var content = document.getText().substr(node.offset, node.length) + separatorAfter;
  792. return this.getInsertTextForPlainText(content);
  793. }
  794. };
  795. JSONCompletion.prototype.getInsertTextForProperty = function (key, propertySchema, addValue, separatorAfter) {
  796. var propertyText = this.getInsertTextForValue(key, '');
  797. if (!addValue) {
  798. return propertyText;
  799. }
  800. var resultText = propertyText + ': ';
  801. var value;
  802. var nValueProposals = 0;
  803. if (propertySchema) {
  804. if (Array.isArray(propertySchema.defaultSnippets)) {
  805. if (propertySchema.defaultSnippets.length === 1) {
  806. var body = propertySchema.defaultSnippets[0].body;
  807. if (objects_1.isDefined(body)) {
  808. value = this.getInsertTextForSnippetValue(body, '');
  809. }
  810. }
  811. nValueProposals += propertySchema.defaultSnippets.length;
  812. }
  813. if (propertySchema.enum) {
  814. if (!value && propertySchema.enum.length === 1) {
  815. value = this.getInsertTextForGuessedValue(propertySchema.enum[0], '');
  816. }
  817. nValueProposals += propertySchema.enum.length;
  818. }
  819. if (objects_1.isDefined(propertySchema.default)) {
  820. if (!value) {
  821. value = this.getInsertTextForGuessedValue(propertySchema.default, '');
  822. }
  823. nValueProposals++;
  824. }
  825. if (Array.isArray(propertySchema.examples) && propertySchema.examples.length) {
  826. if (!value) {
  827. value = this.getInsertTextForGuessedValue(propertySchema.examples[0], '');
  828. }
  829. nValueProposals += propertySchema.examples.length;
  830. }
  831. if (nValueProposals === 0) {
  832. var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
  833. if (!type) {
  834. if (propertySchema.properties) {
  835. type = 'object';
  836. }
  837. else if (propertySchema.items) {
  838. type = 'array';
  839. }
  840. }
  841. switch (type) {
  842. case 'boolean':
  843. value = '$1';
  844. break;
  845. case 'string':
  846. value = '"$1"';
  847. break;
  848. case 'object':
  849. value = '{$1}';
  850. break;
  851. case 'array':
  852. value = '[$1]';
  853. break;
  854. case 'number':
  855. case 'integer':
  856. value = '${1:0}';
  857. break;
  858. case 'null':
  859. value = '${1:null}';
  860. break;
  861. default:
  862. return propertyText;
  863. }
  864. }
  865. }
  866. if (!value || nValueProposals > 1) {
  867. value = '$1';
  868. }
  869. return resultText + value + separatorAfter;
  870. };
  871. JSONCompletion.prototype.getCurrentWord = function (document, offset) {
  872. var i = offset - 1;
  873. var text = document.getText();
  874. while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
  875. i--;
  876. }
  877. return text.substring(i + 1, offset);
  878. };
  879. JSONCompletion.prototype.evaluateSeparatorAfter = function (document, offset) {
  880. var scanner = Json.createScanner(document.getText(), true);
  881. scanner.setPosition(offset);
  882. var token = scanner.scan();
  883. switch (token) {
  884. case 5 /* CommaToken */:
  885. case 2 /* CloseBraceToken */:
  886. case 4 /* CloseBracketToken */:
  887. case 17 /* EOF */:
  888. return '';
  889. default:
  890. return ',';
  891. }
  892. };
  893. JSONCompletion.prototype.findItemAtOffset = function (node, document, offset) {
  894. var scanner = Json.createScanner(document.getText(), true);
  895. var children = node.items;
  896. for (var i = children.length - 1; i >= 0; i--) {
  897. var child = children[i];
  898. if (offset > child.offset + child.length) {
  899. scanner.setPosition(child.offset + child.length);
  900. var token = scanner.scan();
  901. if (token === 5 /* CommaToken */ && offset >= scanner.getTokenOffset() + scanner.getTokenLength()) {
  902. return i + 1;
  903. }
  904. return i;
  905. }
  906. else if (offset >= child.offset) {
  907. return i;
  908. }
  909. }
  910. return 0;
  911. };
  912. JSONCompletion.prototype.isInComment = function (document, start, offset) {
  913. var scanner = Json.createScanner(document.getText(), false);
  914. scanner.setPosition(start);
  915. var token = scanner.scan();
  916. while (token !== 17 /* EOF */ && (scanner.getTokenOffset() + scanner.getTokenLength() < offset)) {
  917. token = scanner.scan();
  918. }
  919. return (token === 12 /* LineCommentTrivia */ || token === 13 /* BlockCommentTrivia */) && scanner.getTokenOffset() <= offset;
  920. };
  921. JSONCompletion.prototype.fromMarkup = function (markupString) {
  922. if (markupString && this.doesSupportMarkdown()) {
  923. return {
  924. kind: jsonLanguageTypes_1.MarkupKind.Markdown,
  925. value: markupString
  926. };
  927. }
  928. return undefined;
  929. };
  930. JSONCompletion.prototype.doesSupportMarkdown = function () {
  931. if (!objects_1.isDefined(this.supportsMarkdown)) {
  932. var completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
  933. this.supportsMarkdown = completion && completion.completionItem && Array.isArray(completion.completionItem.documentationFormat) && completion.completionItem.documentationFormat.indexOf(jsonLanguageTypes_1.MarkupKind.Markdown) !== -1;
  934. }
  935. return this.supportsMarkdown;
  936. };
  937. JSONCompletion.prototype.doesSupportsCommitCharacters = function () {
  938. if (!objects_1.isDefined(this.supportsCommitCharacters)) {
  939. var completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
  940. this.supportsCommitCharacters = completion && completion.completionItem && !!completion.completionItem.commitCharactersSupport;
  941. }
  942. return this.supportsCommitCharacters;
  943. };
  944. return JSONCompletion;
  945. }());
  946. exports.JSONCompletion = JSONCompletion;
  947. });