<commit_before>import humanizeList from 'humanize-list';
import validator from 'validator';
import lodash from 'lodash';
import semver from 'semver';

import buildTargets from './buildTargets';
import DiagnosticList from './DiagnosticList';
import { supportedTags, validateLanguageTag } from './languageTag';
import sdkVersion from './sdkVersion';

const knownBuildTargets = Object.keys(buildTargets);

export enum AppType {
  APP = 'app',
  CLOCKFACE = 'clockface',
}

export const VALID_APP_TYPES = Object.values(AppType);

export type LocalesConfig = { [locale: string]: { name: string } };

export interface BaseProjectConfiguration {
  appType: AppType;
  appDisplayName: string;
  appUUID: string;
  requestedPermissions: string[];
  i18n: LocalesConfig;
  defaultLanguage: string;
  buildTargets: string[];
  // We don't want to accidentally serialize `enableProposedAPI: false`
  // out to users' package.json files.
  enableProposedAPI?: true;
  appClusterID?: string;
  developerID?: string;
}

export interface AppProjectConfiguration extends BaseProjectConfiguration {
  appType: AppType.APP;
  wipeColor: string;
  iconFile: string;
}

export interface ClockProjectConfiguration extends BaseProjectConfiguration {
  appType: AppType.CLOCKFACE;
}

type ProjectConfiguration = AppProjectConfiguration | ClockProjectConfiguration;
export default ProjectConfiguration;

export const MAX_DISPLAY_NAME_LENGTH = 30;

enum Locales {
  'en-US' = 'English (US)',
  'de-DE' = 'German',
  'es-ES' = 'Spanish',
  'fr-FR' = 'French',
  'it-IT' = 'Italian',
  'ja-JP' = 'Japanese',
  'ko-KR' = 'Korean',
  'nl-NL' = 'Dutch',
  'sv-SE' = 'Swedish',
  'zh-CN' = 'Chinese (Simplified)',
  'zh-TW' = 'Chinese (Traditional)',
}

enum Permission {
  ACCESS_ACTIVITY = 'access_activity',
  ACCESS_AOD = 'access_aod',
  ACCESS_APP_CLUSTER_STORAGE = 'access_app_cluster_storage',
  ACCESS_CALENDAR = 'access_calendar',
  ACCESS_EXERCISE = 'access_exercise',
  ACCESS_HEART_RATE = 'access_heart_rate',
  ACCESS_INTERNET = 'access_internet',
  ACCESS_LOCATION = 'access_location',
  ACCESS_SECURE_EXCHANGE = 'access_secure_exchange',
  ACCESS_USER_PROFILE = 'access_user_profile',
  FITBIT_TOKEN = 'fitbit_token',
  RUN_BACKGROUND = 'run_background',
  EXTERNAL_APP_COMMUNICATION = 'external_app_communication',
}

const permissionTypes = [
  {
    key: Permission.ACCESS_ACTIVITY,
    name: 'Activity',
    // tslint:disable-next-line:max-line-length
    description:
      'Read user activities for today (distance, calories, steps, elevation and active minutes), and daily goals.',
  },
  {
    key: Permission.ACCESS_USER_PROFILE,
    name: 'User Profile',
    // tslint:disable-next-line:max-line-length
    description:
      'Read non-identifiable personal information (gender, age, height, weight, resting HR, basal metabolic rate, stride, HR zones).',
  },
  {
    key: Permission.ACCESS_HEART_RATE,
    name: 'Heart Rate',
    description: 'Application may read the heart-rate sensor in real-time.',
  },
  {
    key: Permission.ACCESS_LOCATION,
    name: 'Location',
    description: 'Application and companion may use GPS.',
  },
  {
    key: Permission.ACCESS_INTERNET,
    name: 'Internet',
    description:
      'Companion may communicate with the Internet using your phone data connection.',
  },
  {
    key: Permission.RUN_BACKGROUND,
    name: 'Run in background',
    description:
      'Companion may run even when the application is not actively in use.',
  },
  {
    key: Permission.ACCESS_EXERCISE,
    name: 'Exercise Tracking',
    description: 'Application may track an exercise.',
    sdkVersion: '>=3.0.0',
  },
  {
    key: Permission.ACCESS_APP_CLUSTER_STORAGE,
    name: 'App Cluster Storage',
    description:
      'Application may access storage shared by other applications from the same developer.',
    sdkVersion: '>=4.0.0',
  },
  {
    key: Permission.ACCESS_CALENDAR,
    name: 'Calendars',
    description:
      'Application may access calendar data stored on the mobile device',
    sdkVersion: '>=4.1.0',
  },
];

const restrictedPermissionTypes = [
  {
    key: Permission.FITBIT_TOKEN,
    name: '[Restricted] Fitbit Token',
    description: 'Access Fitbit API token.',
  },
  {
    key: Permission.EXTERNAL_APP_COMMUNICATION,
    name: '[Restricted] External Application Communication',
    description:
      'Allows communication between external mobile applications and companion.',
  },
  {
    key: Permission.ACCESS_SECURE_EXCHANGE,
    name: '[Restricted] Secure Exchange',
    description: 'Allows securing any data and verifying that data was secured',
  },
  {
    key: Permission.ACCESS_AOD,
    name: '[Restricted] Always-on Display',
    description:
      'Application may stay active whilst always-on display mode is active.',
    sdkVersion: '>=4.1.0',
  },
];

function getAllPermissionTypes(enableProposedAPI: boolean) {
  return [
    ...restrictedPermissionTypes,
    ...permissionTypes.filter(
      (permission) =>
        !permission.sdkVersion ||
        semver.satisfies(sdkVersion(), permission.sdkVersion) ||
        enableProposedAPI,
    ),
  ];
}

function constrainedSetDiagnostics({
  actualValues,
  knownValues,
  valueTypeNoun,
  notFoundIsFatal = false,
}: {
  actualValues: ReadonlyArray<any>;
  knownValues: ReadonlyArray<any>;
  valueTypeNoun: string;
  notFoundIsFatal?: boolean;
}) {
  const unknownValues = lodash.without(actualValues, ...knownValues);
  const diagnostics = new DiagnosticList();

  if (unknownValues.length > 0) {<commit_msg>Add new SDK 4.1 permission, standardize grammar on existing ones (#166)<commit_after>import humanizeList from 'humanize-list';
import validator from 'validator';
import lodash from 'lodash';
import semver from 'semver';

import buildTargets from './buildTargets';
import DiagnosticList from './DiagnosticList';
import { supportedTags, validateLanguageTag } from './languageTag';
import sdkVersion from './sdkVersion';

const knownBuildTargets = Object.keys(buildTargets);

export enum AppType {
  APP = 'app',
  CLOCKFACE = 'clockface',
}

export const VALID_APP_TYPES = Object.values(AppType);

export type LocalesConfig = { [locale: string]: { name: string } };

export interface BaseProjectConfiguration {
  appType: AppType;
  appDisplayName: string;
  appUUID: string;
  requestedPermissions: string[];
  i18n: LocalesConfig;
  defaultLanguage: string;
  buildTargets: string[];
  // We don't want to accidentally serialize `enableProposedAPI: false`
  // out to users' package.json files.
  enableProposedAPI?: true;
  appClusterID?: string;
  developerID?: string;
}

export interface AppProjectConfiguration extends BaseProjectConfiguration {
  appType: AppType.APP;
  wipeColor: string;
  iconFile: string;
}

export interface ClockProjectConfiguration extends BaseProjectConfiguration {
  appType: AppType.CLOCKFACE;
}

type ProjectConfiguration = AppProjectConfiguration | ClockProjectConfiguration;
export default ProjectConfiguration;

export const MAX_DISPLAY_NAME_LENGTH = 30;

enum Locales {
  'en-US' = 'English (US)',
  'de-DE' = 'German',
  'es-ES' = 'Spanish',
  'fr-FR' = 'French',
  'it-IT' = 'Italian',
  'ja-JP' = 'Japanese',
  'ko-KR' = 'Korean',
  'nl-NL' = 'Dutch',
  'sv-SE' = 'Swedish',
  'zh-CN' = 'Chinese (Simplified)',
  'zh-TW' = 'Chinese (Traditional)',
}

enum Permission {
  ACCESS_ACTIVITY = 'access_activity',
  ACCESS_AOD = 'access_aod',
  ACCESS_APP_CLUSTER_STORAGE = 'access_app_cluster_storage',
  ACCESS_CALENDAR = 'access_calendar',
  ACCESS_EXERCISE = 'access_exercise',
  ACCESS_HEART_RATE = 'access_heart_rate',
  ACCESS_INTERNET = 'access_internet',
  ACCESS_LOCATION = 'access_location',
  ACCESS_SECURE_EXCHANGE = 'access_secure_exchange',
  ACCESS_USER_PROFILE = 'access_user_profile',
  FITBIT_TOKEN = 'fitbit_token',
  RUN_BACKGROUND = 'run_background',
  EXTERNAL_APP_COMMUNICATION = 'external_app_communication',
  MOBILE_NOTIFICATIONS = 'mobile_notifications',
}

const permissionTypes = [
  {
    key: Permission.ACCESS_ACTIVITY,
    name: 'Activity',
    // tslint:disable-next-line:max-line-length
    description:
      'Read user activities for today (distance, calories, steps, elevation and active minutes), and daily goals',
  },
  {
    key: Permission.ACCESS_USER_PROFILE,
    name: 'User Profile',
    // tslint:disable-next-line:max-line-length
    description:
      'Read non-identifiable personal information (gender, age, height, weight, resting HR, basal metabolic rate, stride, HR zones)',
  },
  {
    key: Permission.ACCESS_HEART_RATE,
    name: 'Heart Rate',
    description: 'Application may read the heart-rate sensor in real-time',
  },
  {
    key: Permission.ACCESS_LOCATION,
    name: 'Location',
    description: 'Application and companion may use GPS',
  },
  {
    key: Permission.ACCESS_INTERNET,
    name: 'Internet',
    description:
      'Companion may communicate with the Internet using your phone data connection',
  },
  {
    key: Permission.RUN_BACKGROUND,
    name: 'Run in background',
    description:
      'Companion may run even when the application is not actively in use',
  },
  {
    key: Permission.ACCESS_EXERCISE,
    name: 'Exercise Tracking',
    description: 'Application may track an exercise',
    sdkVersion: '>=3.0.0',
  },
  {
    key: Permission.ACCESS_APP_CLUSTER_STORAGE,
    name: 'App Cluster Storage',
    description:
      'Application may access storage shared by other applications from the same developer',
    sdkVersion: '>=4.0.0',
  },
  {
    key: Permission.ACCESS_CALENDAR,
    name: 'Calendars',
    description:
      'Application may access calendar data stored on the mobile device',
    sdkVersion: '>=4.1.0',
  },
];

const restrictedPermissionTypes = [
  {
    key: Permission.FITBIT_TOKEN,
    name: '[Restricted] Fitbit Token',
    description: 'Access Fitbit API token',
  },
  {
    key: Permission.EXTERNAL_APP_COMMUNICATION,
    name: '[Restricted] External Application Communication',
    description:
      'Allows communication between external mobile applications and companion',
  },
  {
    key: Permission.ACCESS_SECURE_EXCHANGE,
    name: '[Restricted] Secure Exchange',
    description: 'Allows securing any data and verifying that data was secured',
  },
  {
    key: Permission.ACCESS_AOD,
    name: '[Restricted] Always-on Display',
    description:
      'Application may stay active whilst always-on display mode is active',
    sdkVersion: '>=4.1.0',
  },
  {
    key: Permission.MOBILE_NOTIFICATIONS,
    name: '[Restricted] Mobile Notifications',
    description: 'Application may display notifications on the mobile device',
    sdkVersion: '>=4.1.0',
  },
];

function getAllPermissionTypes(enableProposedAPI: boolean) {
  return [
    ...restrictedPermissionTypes,
    ...permissionTypes.filter(
      (permission) =>
        !permission.sdkVersion ||
        semver.satisfies(sdkVersion(), permission.sdkVersion) ||
        enableProposedAPI,
    ),
  ];
}

function constrainedSetDiagnostics({
  actualValues,
  knownValues,
  valueTypeNoun,
  notFoundIsFatal = false,
}: {
  actualValues: ReadonlyArray<any>;
  knownValues: ReadonlyArray<any>;
  valueTypeNoun: string;
  notFoundIsFatal?: boolean;
}) {
  const unknownValues = lodash.without(actualValues, ...knownValues);
  const diagnostics = new DiagnosticList();

  if (unknownValues.length > 0) {
{'fraction_non_alphanumeric': 0.11246254186497444, 'fraction_numerical': 0.0037017451084082496, 'mean_word_length': 4.148366606170599, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 20, 'https://': 0, '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': '2200094', 'n_tokens_mistral': 3593, 'n_tokens_neox': 3423, 'n_words': 895}