<commit_before>/*
 * Copyright (c) 2020, salesforce.com, inc.
 * All rights reserved.
 * Licensed under the BSD 3-Clause license.
 * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
 */

import { CompletionItemKind, SnippetString } from 'vscode';
import ProtocolCompletionItem from 'vscode-languageclient/lib/protocolCompletionItem';
import { retrieveSObject, retrieveSObjects } from '../sfdx';

import { Middleware } from 'vscode-languageclient';

const EXPANDABLE_ITEM_PATTERN = /__([A-Z_]+)/;

export const middleware: Middleware = {
  // The SOQL LSP server may include special completion items as "placeholders" for
  // the client to expand with information from the users' default Salesforce Org.
  // We do that here as middleware, transforming the server response before passing
  // it up to VSCode.

  provideCompletionItem: async (document, position, context, token, next) => {
    const items = (await next(
      document,
      position,
      context,
      token
    )) as ProtocolCompletionItem[];

    return expandPlaceholders(await filterByContext(items));
  }
};

interface SoqlItemContext {
  sobjectName: string;
  fieldName?: string;
  notNillable?: boolean;
}

async function filterByContext(
  items: ProtocolCompletionItem[]
): Promise<ProtocolCompletionItem[]> {
  const filteredItems: ProtocolCompletionItem[] = [];

  for (const item of items) {
    if (
      !EXPANDABLE_ITEM_PATTERN.test(item.label) &&
      item?.data?.soqlContext?.sobjectName
    ) {
      const objMetadata = await retrieveSObject(
        item.data.soqlContext.sobjectName
      );
      const fieldMeta = objMetadata.fields.find(
        field => field.name === item.data.soqlContext.fieldName
      );
      if (fieldMeta && !operatorsByType[fieldMeta.type].includes(item.label)) {
        continue;
      }
    }

    filteredItems.push(item);
  }

  return filteredItems;
}

async function expandPlaceholders(
  items: ProtocolCompletionItem[]
): Promise<ProtocolCompletionItem[]> {
  const expandedItems = [...items];

  for (const [index, item] of items.entries()) {
    const parsedCommand = item.label.match(EXPANDABLE_ITEM_PATTERN);
    if (parsedCommand) {
      const commandName = parsedCommand[1];

      const handler = expandFunctions[commandName];
      if (!handler) {
        console.log(`Unknown SOQL completion command ${commandName}!`);
      }

      expandedItems.splice(
        index,
        1,
        ...(await handler(item?.data?.soqlContext))
      );
    }
  }

  return expandedItems;
}

const expandFunctions: {
  [key: string]: (
    soqlContext?: SoqlItemContext
  ) => Promise<ProtocolCompletionItem[]>;
} = {
  SOBJECTS_PLACEHOLDER: async (
    soqlContext?: SoqlItemContext
  ): Promise<ProtocolCompletionItem[]> => {
    try {
      const sobjectItems = (await retrieveSObjects()).map(objName => {
        const item = new ProtocolCompletionItem(objName);
        item.kind = CompletionItemKind.Class;
        return item;
      });

      return sobjectItems;
    } catch (metadataErrors) {
      return [];
    }
  },

  SOBJECT_FIELDS_PLACEHOLDER: async (
    soqlContext?: SoqlItemContext
  ): Promise<ProtocolCompletionItem[]> => {
    if (!soqlContext?.sobjectName) {
      console.log('SOBJECT_FIELDS_PLACEHOLDER missing `sobjectName`!');
      return [];
    }

    try {
      const objMetadata = await retrieveSObject(soqlContext.sobjectName);
      const sobjectFields = objMetadata.fields.reduce((fieldItems, field) => {
        fieldItems.push(
          newCompletionItem(
            field.name,
            field.name,
            CompletionItemKind.Field,
            field.type
          )
        );
        if (field.relationshipName) {
          fieldItems.push(
            newCompletionItem(
              `${field.relationshipName}`,
              field.relationshipName + '.',
              CompletionItemKind.Class,
              'Ref. to ' + field.referenceTo
            )
          );
        }

        return fieldItems;
      }, [] as ProtocolCompletionItem[]);
      return sobjectFields;
    } catch (metadataError) {
      return [];
    }
  },
  LITERAL_VALUES_FOR_FIELD: async (
    soqlContext?: SoqlItemContext
  ): Promise<ProtocolCompletionItem[]> => {
    let items: ProtocolCompletionItem[] = [];

    if (!soqlContext?.sobjectName || !soqlContext?.fieldName) {
      console.log('LITERAL_VALUES_FOR_FIELD missing `sobjectName/fieldName`!');
      return [];
    }

    try {
      const objMetadata = await retrieveSObject(soqlContext.sobjectName);
      const nillables = objMetadata.fields
        // .filter(field => field.nillable)
        .map(f => ({ name: f.name, type: f.type, nillable: f.nillable }));
      if (nillables) {
        // debugger;
        console.log('for debugging');
      }
      const fieldMeta = objMetadata.fields.find(
        field => field.name === soqlContext.fieldName
      );
      if (fieldMeta) {
        if (
          ['picklist', 'multipicklist'].includes(fieldMeta.type) &&
          fieldMeta?.picklistValues
        ) {
          items = items.concat(
            fieldMeta.picklistValues
              .filter(v => v.active)
              .map(v =>
                newCompletionItem(<commit_msg>feat(soql): add telemetry to unexpected code-completion errors<commit_after>/*
 * Copyright (c) 2020, salesforce.com, inc.
 * All rights reserved.
 * Licensed under the BSD 3-Clause license.
 * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
 */

import { CompletionItemKind, SnippetString } from 'vscode';
import ProtocolCompletionItem from 'vscode-languageclient/lib/protocolCompletionItem';
import { retrieveSObject, retrieveSObjects } from '../sfdx';

import { Middleware } from 'vscode-languageclient';
import { telemetryService } from '../telemetry';

const EXPANDABLE_ITEM_PATTERN = /__([A-Z_]+)/;

export const middleware: Middleware = {
  // The SOQL LSP server may include special completion items as "placeholders" for
  // the client to expand with information from the users' default Salesforce Org.
  // We do that here as middleware, transforming the server response before passing
  // it up to VSCode.

  provideCompletionItem: async (document, position, context, token, next) => {
    const items = (await next(
      document,
      position,
      context,
      token
    )) as ProtocolCompletionItem[];

    return expandPlaceholders(await filterByContext(items));
  }
};

interface SoqlItemContext {
  sobjectName: string;
  fieldName?: string;
  notNillable?: boolean;
}

async function filterByContext(
  items: ProtocolCompletionItem[]
): Promise<ProtocolCompletionItem[]> {
  const filteredItems: ProtocolCompletionItem[] = [];

  for (const item of items) {
    if (
      !EXPANDABLE_ITEM_PATTERN.test(item.label) &&
      item?.data?.soqlContext?.sobjectName
    ) {
      const objMetadata = await retrieveSObject(
        item.data.soqlContext.sobjectName
      );
      const fieldMeta = objMetadata.fields.find(
        field => field.name === item.data.soqlContext.fieldName
      );
      if (fieldMeta && !operatorsByType[fieldMeta.type].includes(item.label)) {
        continue;
      }
    }

    filteredItems.push(item);
  }

  return filteredItems;
}

async function expandPlaceholders(
  items: ProtocolCompletionItem[]
): Promise<ProtocolCompletionItem[]> {
  const expandedItems = [...items];

  for (const [index, item] of items.entries()) {
    const parsedCommand = item.label.match(EXPANDABLE_ITEM_PATTERN);
    if (parsedCommand) {
      const commandName = parsedCommand[1];

      const handler = expandFunctions[commandName];
      if (handler) {
        expandedItems.splice(
          index,
          1,
          ...(await handler(item?.data?.soqlContext))
        );
      } else {
        telemetryService.sendException(
          'SOQLLanguageServerException',
          `Unknown SOQL LSP completion command ${commandName}!`
        );
      }
    }
  }

  return expandedItems;
}

const expandFunctions: {
  [key: string]: (
    soqlContext?: SoqlItemContext
  ) => Promise<ProtocolCompletionItem[]>;
} = {
  SOBJECTS_PLACEHOLDER: async (
    soqlContext?: SoqlItemContext
  ): Promise<ProtocolCompletionItem[]> => {
    try {
      const sobjectItems = (await retrieveSObjects()).map(objName => {
        const item = new ProtocolCompletionItem(objName);
        item.kind = CompletionItemKind.Class;
        return item;
      });

      return sobjectItems;
    } catch (metadataErrors) {
      return [];
    }
  },

  SOBJECT_FIELDS_PLACEHOLDER: async (
    soqlContext?: SoqlItemContext
  ): Promise<ProtocolCompletionItem[]> => {
    if (!soqlContext?.sobjectName) {
      telemetryService.sendException(
        'SOQLLanguageServerException',
        'SOBJECT_FIELDS_PLACEHOLDER missing `sobjectName`!'
      );
      return [];
    }

    try {
      const objMetadata = await retrieveSObject(soqlContext.sobjectName);
      const sobjectFields = objMetadata.fields.reduce((fieldItems, field) => {
        fieldItems.push(
          newCompletionItem(
            field.name,
            field.name,
            CompletionItemKind.Field,
            field.type
          )
        );
        if (field.relationshipName) {
          fieldItems.push(
            newCompletionItem(
              `${field.relationshipName}`,
              field.relationshipName + '.',
              CompletionItemKind.Class,
              'Ref. to ' + field.referenceTo
            )
          );
        }

        return fieldItems;
      }, [] as ProtocolCompletionItem[]);
      return sobjectFields;
    } catch (metadataError) {
      return [];
    }
  },
  LITERAL_VALUES_FOR_FIELD: async (
    soqlContext?: SoqlItemContext
  ): Promise<ProtocolCompletionItem[]> => {
    let items: ProtocolCompletionItem[] = [];

    if (!soqlContext?.sobjectName || !soqlContext?.fieldName) {
      telemetryService.sendException(
        'SOQLLanguageServerException',
        'LITERAL_VALUES_FOR_FIELD missing `sobjectName/fieldName`!'
      );
      return [];
    }

    try {
      const objMetadata = await retrieveSObject(soqlContext.sobjectName);
      const fieldMeta = objMetadata.fields.find(
        field => field.name === soqlContext.fieldName
      );
      if (fieldMeta) {
        if (
          ['picklist', 'multipicklist'].includes(fieldMeta.type) &&
          fieldMeta?.picklistValues
        ) {
          items = items.concat(
            fieldMeta.picklistValues
              .filter(v => v.active)
              .map(v =>
                newCompletionItem(
{'fraction_non_alphanumeric': 0.11150641325718566, 'fraction_numerical': 0.0014979870798614361, 'mean_word_length': 2.6821785591175455, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 39, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '677111', 'n_tokens_mistral': 3069, 'n_tokens_neox': 3028, 'n_words': 654}