commit fbbf8cf16bda19d766b25565a9735a10817e4ac8 Author: Daniel Büchele Date: Fri Apr 13 08:38:06 2018 -0700 Initial commit 🎉 fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2 Co-authored-by: Sebastian McKenzie Co-authored-by: John Knox Co-authored-by: Emil Sjölander Co-authored-by: Pritesh Nandgaonkar diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..71caa58fe --- /dev/null +++ b/.eslintignore @@ -0,0 +1,8 @@ +src/fb/plugins/relaydevtools/relay-devtools/* +latest +resources +templates +node_modules +flow-typed +lib +!.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..841ea8d90 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,30 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +const fbjs = require('eslint-config-fbjs'); + +// enforces copyright header and @format directive to be present in every file +const pattern = /^\*\n \* Copyright 2018-present Facebook\.\n \* This source code is licensed under the MIT license found in the\n \* LICENSE file in the root directory of this source tree\.\n \* @format\n./; + +module.exports = { + extends: 'fbjs', + plugins: [...fbjs.plugins, 'header', 'prettier'], + rules: { + // disable rules from eslint-config-fbjs + 'react/react-in-jsx-scope': 0, // not needed with our metro implementation + 'no-new': 0, // new keyword needed e.g. new Notification + 'no-catch-shadow': 0, // only relevant for IE8 and below + 'no-bitwise': 0, // bitwise operations needed in some places + 'consistent-return': 0, + 'max-len': 0, // let's take prettier take care of this + indent: 0, // let's take prettier take care of this + + // additional rules for this project + 'header/header': [2, 'block', {pattern}], + 'prettier/prettier': [2, 'fb', '@format'], + }, +}; diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 000000000..a065c3782 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,24 @@ +[ignore] +.*/scripts/.* +.*/coverage/.* +.*/node_modules/.* +.*/build/.* +.*/dist/.* +.*/static/.* +/src/fb/plugins/relaydevtools/relay-devtools/DevtoolsUI.js$ +.*/website/.* + +[libs] +lib +flow-typed + +[options] +esproposal.export_star_as=enable +module.use_strict=true +emoji=true +all=true +include_warnings=true +module.name_mapper='sonar' -> '/src/index.js' + +[version] +0.69.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..660a55ca5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist +website/build +*.xcworkspace +**/Pods/ +**/xcuserdata/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..849454e73 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libs/folly"] + path = libs/folly + url = https://github.com/facebook/folly.git diff --git a/.sonarhandles b/.sonarhandles new file mode 100644 index 000000000..b2518ae20 --- /dev/null +++ b/.sonarhandles @@ -0,0 +1 @@ +{"osx": "GICWmAAS05ZnyDsCAAAAAABHIak5bnw7AAAg", "linux": "GICWmACHSiVuyDsCAAAAAABVAX1Bbnw7AAAg", "windows": "GICWmAAn5YB0yDsCAAAAAAAMdghhbnw7AAAg"} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..91a50b339 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,56 @@ +os: osx +osx_image: xcode9.3 + +matrix: + include: + - language: node_js + node_js: + - "8" + + install: + - yarn + - cd website + - yarn + - cd .. + + script: + - yarn lint + - yarn build macOnly build-number=$TRAVIS_BUILD_NUMBER + - cd website + - yarn build + - cd .. + + before_deploy: + - export SONAR_VERSION="v$(plutil -p $TRAVIS_BUILD_DIR/dist/mac/Sonar.app/Contents/Info.plist | awk '/CFBundleShortVersionString/ {print substr($3, 2, length($3)-2)}')" + + deploy: + - provider: pages + skip-cleanup: true + github-token: $GITHUB_TOKEN + fqdn: fbsonar.com + local-dir: website/build/sonar + keep-history: true + on: + branch: master + - provider: releases + api_key: $GITHUB_TOKEN + file: dist/Sonar.zip + name: $SONAR_VERSION + draft: true + skip_cleanup: true + on: + branch: master + + - language: objective-c + + before_install: + - pod repo update + + install: + - cd iOS/Sample + - pod install + - cd ../../ + + script: + - cd iOS/Sample + - xcodebuild clean build -workspace Sample.xcworkspace -scheme Pods-Sample \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..3e2f26fa9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Running Renderer", + "type": "chrome", + "request": "attach", + "port": 9222, + "webRoot": "${workspaceRoot}" + } + ] +} diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 000000000..6a904ab64 --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +--install.mutex network diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..0a45f9bd5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..45b5f7a72 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing to Sonar + +We want to make contributing to this project as easy and transparent as +possible. + +## Our Development Process + +Changes from Facebook employees are synced to the GitHub repo automatically. +PRs from the community are imported into our internal source control and then +pushed to GitHub. + +For changes affecting both, native code and JavaScript, make sure to only create +a single PR containing both parts of the code. + +Although the Sonar desktop app is only released for macOS right now, it is +possible to create Windows and Linux builds of the app. Please keep this in mind +when dealing with platform-specific code. + +## Pull Requests + +We actively welcome your pull requests. + +1. Fork the repo and create your branch from `master`. +2. If you've added code that should be tested, add tests. +3. If you've changed APIs, update the documentation. +4. Ensure the test suite passes. +5. Make sure your code lints. +6. If you haven't already, complete the Contributor License Agreement ("CLA"). + +## Contributor License Agreement ("CLA") + +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Issues + +We use GitHub issues to track public bugs. Please ensure your description is +clear and has sufficient instructions to be able to reproduce the issue. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe +disclosure of security bugs. In those cases, please go through the process +outlined on that page and do not file a public issue. + +## Coding Style + +We are using Prettier to format our source code. The styles are enforced via +eslint. Make sure everything is well formatted before creating a PR. Therefore, +run `yarn lint` and `yarn fix` to apply formatting fixes. + +## License + +By contributing to Sonar, you agree that your contributions will be licensed +under the LICENSE file in the root directory of this source tree. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..86642be60 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2004-present, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..326dec991 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +Sonar is a desktop app and client API for real time debugging of mobile apps. Sonar enables developers to quickly build plugins which expose runtime information from Android and iOS apps in a easy to use desktop interface. + +# Why should I build tools on top of Sonar? + +Sonar provides you with everything you need to quickly get your tools into people hands. Sonar provides a simple API for communicating with both Android and iOS devices, reconnecting if connection is lost as well as managing multiple connections. With Sonar's built in set of components it is easy to make good looking, easy to use, and powerful developer tools. + +# In this repo + +This repository includes all parts of Sonar. This includes: + +* Sonar's desktop app built using Electron (`/src`) +* native Sonar SDKs for iOS +* native Sonar SDKs for Android +* Plugins: + * Logs (`/src/device-plugins/logs`) + * Layout inspector (`/src/plugins/layout`) + * Network inspector (`/src/plugins/network`) +* website and documentation (`/website` / `/docs`) + +# Getting started + +## Requirements + +* macOS (while Sonar is buildable using other systems as well, only macOS is officially supported) +* node >= 8 +* yarn >= 1.5 +* iOS developer tools (for developing iOS plugins) +* Android SDK and adb + +## Starting the desktop app + +``` +git clone https://github.com/facebook/Sonar.git +cd Sonar +yarn +yarn start +``` + +## Building the desktop app + +``` +yarn build [macOnly] [build-number=$buildNumber] +``` + +A binary for macOS is created in `dist/mac`. `macOnly` and `build-number` are optional params. + +## Documentation + +Find the full documentation for this project at [fbsonar.com](https://fbsonar.com/). + +## Contributing and license + +See the CONTRIBUTING file for how to help out. +Sonar is MIT licensed, as found in the LICENSE file. diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 000000000..af9aaeea8 --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt new file mode 100644 index 000000000..496069fe9 --- /dev/null +++ b/android/CMakeLists.txt @@ -0,0 +1,63 @@ +cmake_minimum_required (VERSION 3.6.0) +project(sonar CXX C) +set(CMAKE_VERBOSE_MAKEFILE on) + +set(PACKAGE_NAME "sonar") + +add_compile_options(-DFOLLY_NO_CONFIG + -DSONAR_JNI_EXTERNAL=1 + -DFB_SONARKIT_ENABLED=1 + -DFOLLY_HAVE_MEMRCHR + -DFOLLY_MOBILE=1 + -DFOLLY_USE_LIBCPP=1 + -DFOLLY_HAVE_LIBJEMALLOC=0 + -DFOLLY_HAVE_PREADV=0 + -frtti + -fexceptions + -std=c++14 + -Wno-error + -Wno-unused-local-typedefs + -Wno-unused-variable + -Wno-sign-compare + -Wno-comment + -Wno-return-type + -Wno-tautological-constant-compare + ) + +file(GLOB SOURCES android/sonar.cpp) +add_library(${PACKAGE_NAME} SHARED ${SOURCES}) +target_include_directories(${PACKAGE_NAME} PUBLIC "./") + +set(libjnihack_DIR ${CMAKE_SOURCE_DIR}/../libs/jni-hack/) +set(libfbjni_DIR ${CMAKE_SOURCE_DIR}/../libs/fbjni/src/main/cpp/include/) +set(libsonar_DIR ${CMAKE_SOURCE_DIR}/../xplat/) +set(third_party_ndk build/third-party-ndk) +set(libfolly_DIR ${third_party_ndk}/folly/) +set(glog_DIR ${third_party_ndk}/glog) +set(BOOST_DIR ${third_party_ndk}/boost/boost_1_63_0/) + + +set(build_DIR ${CMAKE_SOURCE_DIR}/build) + +set(fbjni_build_DIR ${build_DIR}/fbjni/${ANDROID_ABI}) +set(libsonar_build_DIR ${build_DIR}/libsonar/${ANDROID_ABI}) +set(libfolly_build_DIR ${build_DIR}/libfolly/${ANDROID_ABI}) + +file(MAKE_DIRECTORY ${build_DIR}) + +add_subdirectory(${libsonar_DIR} ${libsonar_build_DIR}) +add_subdirectory(${libfbjni_DIR}/../ ${fbjni_build_DIR}) + +target_include_directories(${PACKAGE_NAME} PRIVATE + ${libjnihack_DIR} + ${libfbjni_DIR} + ${libsonar_DIR} + ${libfolly_DIR} + ${glog_DIR} + ${glog_DIR}/../ + ${glog_DIR}/glog-0.3.5/src/ + ${BOOST_DIR} + ${BOOST_DIR}/../ + ) + +target_link_libraries(${PACKAGE_NAME} fb sonarcpp) diff --git a/android/android/AndroidSonarClient.java b/android/android/AndroidSonarClient.java new file mode 100644 index 000000000..963799c9d --- /dev/null +++ b/android/android/AndroidSonarClient.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.android; + +import android.content.Context; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import com.facebook.sonar.core.SonarClient; + +public final class AndroidSonarClient { + private static boolean sIsInitialized = false; + private static SonarThread sSonarThread; + + public static synchronized SonarClient getInstance(Context context) { + if (!sIsInitialized) { + sSonarThread = new SonarThread(); + sSonarThread.start(); + + final Context app = context.getApplicationContext(); + SonarClientImpl.init( + sSonarThread.getEventBase(), + getServerHost(app), + "Android", + getFriendlyDeviceName(), + getId(), + getRunningAppName(app), + getPackageName(app), + context.getFilesDir().getAbsolutePath()); + sIsInitialized = true; + } + return SonarClientImpl.getInstance(); + } + + static boolean isRunningOnGenymotion() { + return Build.FINGERPRINT.contains("vbox"); + } + + static boolean isRunningOnStockEmulator() { + return Build.FINGERPRINT.contains("generic") && !Build.FINGERPRINT.contains("vbox"); + } + + static String getId() { + return Build.SERIAL; + } + + static String getFriendlyDeviceName() { + if (isRunningOnGenymotion()) { + // Genymotion already has a friendly name by default + return Build.MODEL; + } else { + return Build.MODEL + " - " + Build.VERSION.RELEASE + " - API " + Build.VERSION.SDK_INT; + } + } + + static String getServerHost(Context context) { + if (isRunningOnStockEmulator()) { + return "10.0.2.2"; + } else if (isRunningOnGenymotion()) { + // This is hand-wavy but works on but ipv4 and ipv6 genymotion + final WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + final WifiInfo info = wifi.getConnectionInfo(); + final int ip = info.getIpAddress(); + return String.format("%d.%d.%d.2", (ip & 0xff), (ip >> 8 & 0xff), (ip >> 16 & 0xff)); + } else { + // Running on physical device. Sonar desktop will run `adb reverse tcp:8088 tcp:8088` + return "localhost"; + } + } + + static String getRunningAppName(Context context) { + return context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); + } + + static String getPackageName(Context context) { + return context.getPackageName(); + } +} diff --git a/android/android/EventBase.java b/android/android/EventBase.java new file mode 100644 index 000000000..db90b35f2 --- /dev/null +++ b/android/android/EventBase.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.android; + +import com.facebook.jni.HybridClassBase; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; +import com.facebook.sonar.BuildConfig; + +@DoNotStrip +class EventBase extends HybridClassBase { + static { + if (BuildConfig.IS_INTERNAL_BUILD) { + SoLoader.loadLibrary("sonar"); + } + } + + EventBase() { + initHybrid(); + } + + @DoNotStrip + native void loopForever(); + + @DoNotStrip + private native void initHybrid(); +} diff --git a/android/android/SonarClientImpl.java b/android/android/SonarClientImpl.java new file mode 100644 index 000000000..eae290a4d --- /dev/null +++ b/android/android/SonarClientImpl.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.android; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; +import com.facebook.sonar.BuildConfig; +import com.facebook.sonar.core.SonarClient; +import com.facebook.sonar.core.SonarPlugin; + +@DoNotStrip +class SonarClientImpl implements SonarClient { + static { + if (BuildConfig.IS_INTERNAL_BUILD) { + SoLoader.loadLibrary("sonar"); + } + } + + private final HybridData mHybridData; + + private SonarClientImpl(HybridData hd) { + mHybridData = hd; + } + + public static native void init( + EventBase eventBase, + String host, + String os, + String device, + String deviceId, + String app, + String appId, + String privateAppDirectory); + + public static native SonarClientImpl getInstance(); + + @Override + public native void addPlugin(SonarPlugin plugin); + + @Override + public native T getPlugin(String id); + + @Override + public native void removePlugin(SonarPlugin plugin); + + @Override + public native void start(); + + @Override + public native void stop(); +} diff --git a/android/android/SonarConnectionImpl.java b/android/android/SonarConnectionImpl.java new file mode 100644 index 000000000..7b4b49984 --- /dev/null +++ b/android/android/SonarConnectionImpl.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.android; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; +import com.facebook.sonar.BuildConfig; +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarReceiver; + +@DoNotStrip +class SonarConnectionImpl implements SonarConnection { + static { + if (BuildConfig.IS_INTERNAL_BUILD) { + SoLoader.loadLibrary("sonar"); + } + } + + private final HybridData mHybridData; + + private SonarConnectionImpl(HybridData hd) { + mHybridData = hd; + } + + @Override + public void send(String method, SonarObject params) { + sendObject(method, params); + } + + @Override + public void send(String method, SonarArray params) { + sendArray(method, params); + } + + public native void sendObject(String method, SonarObject params); + + public native void sendArray(String method, SonarArray params); + + @Override + public native void reportError(Throwable throwable); + + @Override + public native void receive(String method, SonarReceiver receiver); +} diff --git a/android/android/SonarResponderImpl.java b/android/android/SonarResponderImpl.java new file mode 100644 index 000000000..e9902fd66 --- /dev/null +++ b/android/android/SonarResponderImpl.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.android; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; +import com.facebook.sonar.BuildConfig; +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarResponder; + +@DoNotStrip +class SonarResponderImpl implements SonarResponder { + static { + if (BuildConfig.IS_INTERNAL_BUILD) { + SoLoader.loadLibrary("sonar"); + } + } + + private final HybridData mHybridData; + + private SonarResponderImpl(HybridData hd) { + mHybridData = hd; + } + + @Override + public void success(SonarObject params) { + successObject(params); + } + + @Override + public void success(SonarArray params) { + successArray(params); + } + + @Override + public void success() { + successObject(new SonarObject.Builder().build()); + } + + public native void successObject(SonarObject response); + + public native void successArray(SonarArray response); + + @Override + public native void error(SonarObject response); +} diff --git a/android/android/SonarThread.java b/android/android/SonarThread.java new file mode 100644 index 000000000..608fb5a62 --- /dev/null +++ b/android/android/SonarThread.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.android; + +import android.os.Process; +import javax.annotation.Nullable; + +class SonarThread extends Thread { + private @Nullable EventBase mEventBase; + + SonarThread() { + super("SonarEventBaseThread"); + } + + @Override + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + synchronized (this) { + try { + mEventBase = new EventBase(); + } finally { + notifyAll(); + } + } + + mEventBase.loopForever(); + } + + synchronized EventBase getEventBase() { + while (mEventBase == null) { + try { + wait(); + } catch (InterruptedException e) { + // ignore + } + } + return mEventBase; + } +} diff --git a/android/android/sonar.cpp b/android/android/sonar.cpp new file mode 100644 index 000000000..f7a279e57 --- /dev/null +++ b/android/android/sonar.cpp @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +using namespace facebook; +using namespace facebook::sonar; + +namespace { + +class JEventBase : public jni::HybridClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/android/EventBase;"; + + static void registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", JEventBase::initHybrid), + makeNativeMethod("loopForever", JEventBase::loopForever), + }); + } + + folly::EventBase* eventBase() { + return &eventBase_; + } + + private: + friend HybridBase; + + JEventBase() {} + + void loopForever() { + folly::EventBaseManager::get()->setEventBase(&eventBase_, false); + eventBase_.loopForever(); + } + + static void initHybrid(jni::alias_ref o) { + return setCxxInstance(o); + } + + folly::EventBase eventBase_; +}; + +class JSonarObject : public jni::JavaClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/core/SonarObject;"; + + static jni::local_ref create(const folly::dynamic& json) { + return newInstance(folly::toJson(json)); + } + + std::string toJsonString() { + static const auto method = javaClassStatic()->getMethod("toJsonString"); + return method(self())->toStdString(); + } +}; + +class JSonarArray : public jni::JavaClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/core/SonarArray;"; + + static jni::local_ref create(const folly::dynamic& json) { + return newInstance(folly::toJson(json)); + } + + std::string toJsonString() { + static const auto method = javaClassStatic()->getMethod("toJsonString"); + return method(self())->toStdString(); + } +}; + +class JSonarResponder : public jni::JavaClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/core/SonarResponder;"; +}; + +class JSonarResponderImpl : public jni::HybridClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/android/SonarResponderImpl;"; + + static void registerNatives() { + registerHybrid({ + makeNativeMethod("successObject", JSonarResponderImpl::successObject), + makeNativeMethod("successArray", JSonarResponderImpl::successArray), + makeNativeMethod("error", JSonarResponderImpl::error), + }); + } + + void successObject(jni::alias_ref json) { + _responder->success(json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); + } + + void successArray(jni::alias_ref json) { + _responder->success(json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); + } + + void error(jni::alias_ref json) { + _responder->error(json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); + } + + private: + friend HybridBase; + std::shared_ptr _responder; + + JSonarResponderImpl(std::shared_ptr responder): _responder(std::move(responder)) {} +}; + +class JSonarReceiver : public jni::JavaClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/core/SonarReceiver;"; + + void receive(const folly::dynamic params, std::shared_ptr responder) const { + static const auto method = javaClassStatic()->getMethod, jni::alias_ref)>("onReceive"); + method(self(), JSonarObject::create(std::move(params)), JSonarResponderImpl::newObjectCxxArgs(responder)); + } +}; + +class JSonarConnection : public jni::JavaClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/core/SonarConnection;"; +}; + +class JSonarConnectionImpl : public jni::HybridClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/android/SonarConnectionImpl;"; + + static void registerNatives() { + registerHybrid({ + makeNativeMethod("sendObject", JSonarConnectionImpl::sendObject), + makeNativeMethod("sendArray", JSonarConnectionImpl::sendArray), + makeNativeMethod("reportError", JSonarConnectionImpl::reportError), + makeNativeMethod("receive", JSonarConnectionImpl::receive), + }); + } + + void sendObject(const std::string method, jni::alias_ref json) { + _connection->send(std::move(method), json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); + } + + void sendArray(const std::string method, jni::alias_ref json) { + _connection->send(std::move(method), json ? folly::parseJson(json->toJsonString()) : folly::dynamic::object()); + } + + void reportError(jni::alias_ref throwable) { + _connection->error(throwable->toString(), throwable->getStackTrace()->toString()); + } + + void receive(const std::string method, jni::alias_ref receiver) { + auto global = make_global(receiver); + _connection->receive(std::move(method), [global] (const folly::dynamic& params, std::unique_ptr responder) { + global->receive(params, std::move(responder)); + }); + } + + private: + friend HybridBase; + std::shared_ptr _connection; + + JSonarConnectionImpl(std::shared_ptr connection): _connection(std::move(connection)) {} +}; + +class JSonarPlugin : public jni::JavaClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/core/SonarPlugin;"; + + std::string identifier() const { + static const auto method = javaClassStatic()->getMethod("getId"); + return method(self())->toStdString(); + } + + void didConnect(std::shared_ptr conn) { + static const auto method = javaClassStatic()->getMethod)>("onConnect"); + method(self(), JSonarConnectionImpl::newObjectCxxArgs(conn)); + } + + void didDisconnect() { + static const auto method = javaClassStatic()->getMethod("onDisconnect"); + method(self()); + } +}; + +class JSonarPluginWrapper : public SonarPlugin { + public: + jni::global_ref jplugin; + + virtual std::string identifier() const override { + return jplugin->identifier(); + } + + virtual void didConnect(std::shared_ptr conn) override { + jplugin->didConnect(conn); + } + + virtual void didDisconnect() override { + jplugin->didDisconnect(); + } + + JSonarPluginWrapper(jni::global_ref plugin): jplugin(plugin) {} +}; + +class JSonarClient : public jni::HybridClass { + public: + constexpr static auto kJavaDescriptor = "Lcom/facebook/sonar/android/SonarClientImpl;"; + + static void registerNatives() { + registerHybrid({ + makeNativeMethod("init", JSonarClient::init), + makeNativeMethod("getInstance", JSonarClient::getInstance), + makeNativeMethod("start", JSonarClient::start), + makeNativeMethod("stop", JSonarClient::stop), + makeNativeMethod("addPlugin", JSonarClient::addPlugin), + makeNativeMethod("removePlugin", JSonarClient::removePlugin), + makeNativeMethod("getPlugin", JSonarClient::getPlugin), + }); + } + + static jni::alias_ref getInstance(jni::alias_ref) { + static auto client = make_global(newObjectCxxArgs()); + return client; + } + + void start() { + SonarClient::instance()->start(); + } + + void stop() { + SonarClient::instance()->stop(); + } + + void addPlugin(jni::alias_ref plugin) { + auto wrapper = std::make_shared(make_global(plugin)); + SonarClient::instance()->addPlugin(wrapper); + } + + void removePlugin(jni::alias_ref plugin) { + auto client = SonarClient::instance(); + client->removePlugin(client->getPlugin(plugin->identifier())); + } + + jni::alias_ref getPlugin(const std::string& identifier) { + auto plugin = SonarClient::instance()->getPlugin(identifier); + if (plugin) { + auto wrapper = std::static_pointer_cast(plugin); + return wrapper->jplugin; + } else { + return nullptr; + } + } + + static void init( + jni::alias_ref, + JEventBase* jEventBase, + const std::string host, + const std::string os, + const std::string device, + const std::string deviceId, + const std::string app, + const std::string appId, + const std::string privateAppDirectory) { + + SonarClient::init({ + { + std::move(host), + std::move(os), + std::move(device), + std::move(deviceId), + std::move(app), + std::move(appId), + std::move(privateAppDirectory) + }, + jEventBase->eventBase(), + }); + } + + private: + friend HybridBase; + + JSonarClient() {} +}; + +} // namespace + +jint JNI_OnLoad(JavaVM* vm, void*) { + return jni::initialize(vm, [] { + JSonarClient::registerNatives(); + JSonarConnectionImpl::registerNatives(); + JSonarResponderImpl::registerNatives(); + JEventBase::registerNatives(); + }); +} diff --git a/android/android/utils/SonarUtils.java b/android/android/utils/SonarUtils.java new file mode 100644 index 000000000..6150c273c --- /dev/null +++ b/android/android/utils/SonarUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.android.utils; + +import android.app.ActivityManager; +import android.content.Context; +import com.facebook.sonar.BuildConfig; +import java.util.List; + +public final class SonarUtils { + + private SonarUtils() {} + + public static boolean shouldEnableSonar(Context context) { + return BuildConfig.IS_INTERNAL_BUILD && !isEndToEndTest() && isMainProcess(context); + } + + private static boolean isEndToEndTest() { + final String value = System.getenv("BUDDY_SONAR_DISABLED"); + if (value == null || value.length() == 0) { + return false; + } + + try { + return Boolean.parseBoolean(value); + } catch (NumberFormatException e) { + return false; + } + } + + private static boolean isMainProcess(Context context) { + final int pid = android.os.Process.myPid(); + final ActivityManager manager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + final List infoList = manager.getRunningAppProcesses(); + + String processName = null; + if (infoList != null) { + for (ActivityManager.RunningAppProcessInfo info : infoList) { + if (info.pid == pid) { + processName = info.processName; + break; + } + } + } + + return context.getPackageName().equals(processName); + } +} diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 000000000..92f11a0f5 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,172 @@ +apply plugin: 'com.android.library' + +apply plugin: 'de.undercouch.download' + +import de.undercouch.gradle.tasks.download.Download +import org.apache.tools.ant.taskdefs.condition.Os +import org.apache.tools.ant.filters.ReplaceTokens + +def downloadsDir = new File("$buildDir/downloads") +def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") + +task createNativeDepsDirectories { + downloadsDir.mkdirs() + thirdPartyNdkDir.mkdirs() +} + +task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/google/glog/archive/v0.3.5.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'glog-0.3.5.tar.gz') +} + +task prepareGlog(dependsOn: [downloadGlog], type: Copy) { + from tarTree(downloadGlog.dest) + from './third-party/glog/' + include 'glog-0.3.5/src/**/*', 'Android.mk', 'config.h', 'build.gradle', 'CMakeLists.txt', 'ApplicationManifest.xml' + includeEmptyDirs = false + filesMatching('**/*.h.in') { + filter(ReplaceTokens, tokens: [ + ac_cv_have_unistd_h: '1', + ac_cv_have_stdint_h: '1', + ac_cv_have_systypes_h: '1', + ac_cv_have_inttypes_h: '1', + ac_cv_have_libgflags: '0', + ac_google_start_namespace: 'namespace google {', + ac_cv_have_uint16_t: '1', + ac_cv_have_u_int16_t: '1', + ac_cv_have___uint16: '0', + ac_google_end_namespace: '}', + ac_cv_have___builtin_expect: '1', + ac_google_namespace: 'google', + ac_cv___attribute___noinline: '__attribute__ ((noinline))', + ac_cv___attribute___noreturn: '__attribute__ ((noreturn))', + ac_cv___attribute___printf_4_5: '__attribute__((__format__ (__printf__, 4, 5)))' + ]) + it.path = (it.name - '.in') + } + into "$thirdPartyNdkDir/glog" +} + +task finalizeGlog(dependsOn: [prepareGlog], type: Copy) { + from './third-party/glog/' + include 'logging.cc' + includeEmptyDirs = false + into "$thirdPartyNdkDir/glog/glog-0.3.5/src/" +} + +task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/google/double-conversion/archive/v3.0.0.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'double-conversion-3.0.0.tar.gz') +} + +task prepareDoubleConversion(dependsOn: [downloadDoubleConversion], type: Copy) { + from tarTree(downloadDoubleConversion.dest) + from './third-party/DoubleConversion/' + include 'double-conversion-3.0.0/**/*', 'build.gradle', 'CMakeLists.txt', 'ApplicationManifest.xml' + includeEmptyDirs = false + into "$thirdPartyNdkDir/double-conversion" +} + +task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/react-native-community/boost-for-react-native/releases/download/v1.63.0-0/boost_1_63_0.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'boost_1_63_0.tar.gz') +} + +task prepareBoost(dependsOn: [downloadBoost], type: Copy) { + from tarTree(resources.gzip(downloadBoost.dest)) + include 'boost_1_63_0/boost/**/*.hpp', 'boost/boost/**/*.hpp' + includeEmptyDirs = false + into "$thirdPartyNdkDir/boost" + doLast { + file("$thirdPartyNdkDir/boost/boost").renameTo("$thirdPartyNdkDir/boost/boost_1_63_0") + } +} + +task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) { + src 'https://github.com/facebook/folly/archive/v2018.05.21.00.tar.gz' + onlyIfNewer true + overwrite false + dest new File(downloadsDir, 'folly-2018.05.21.00.tar.gz'); +} + +task prepareFolly(dependsOn: [downloadFolly], type: Copy) { + from tarTree(downloadFolly.dest) + from './third-party/folly/' + include 'folly-2018.05.21.00/folly/**/*', 'build.gradle', 'CMakeLists.txt', 'ApplicationManifest.xml' + eachFile {fname -> fname.path = (fname.path - "folly-2018.05.21.00/")} + includeEmptyDirs = false + into "$thirdPartyNdkDir/folly" +} + +task prepareAllLibs() { + dependsOn finalizeGlog + dependsOn prepareDoubleConversion + dependsOn prepareBoost + dependsOn prepareFolly +} + +android { + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + buildConfigField "boolean", "IS_INTERNAL_BUILD", 'true' + ndk { + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + } + + externalNativeBuild { + cmake { + arguments '-DANDROID_TOOLCHAIN=clang' + } + } + } + lintOptions { + abortOnError false + } + sourceSets { + main { + manifest.srcFile './AndroidManifest.xml' + java { + srcDir 'android' + srcDir 'core' + srcDir 'plugins' + } + res { + srcDir 'res' + } + } + } + externalNativeBuild { + cmake { + path './CMakeLists.txt' + } + } + + dependencies { + implementation project(':fbjni') + implementation deps.soloader + compileOnly deps.lithoAnnotations + implementation 'org.glassfish:javax.annotation:10.0-b28' + implementation deps.guava + implementation deps.jsr305 + implementation deps.supportAppCompat + implementation deps.stetho + implementation deps.okhttp3 + implementation 'com.facebook.litho:litho-core:0.15.0' + implementation 'com.facebook.litho:litho-widget:0.15.0' + implementation fileTree(dir: 'plugins/console/dependencies', include: ['*.jar']) + } +} + +project.afterEvaluate { + preBuild.dependsOn prepareAllLibs +} diff --git a/android/core/ErrorReportingRunnable.java b/android/core/ErrorReportingRunnable.java new file mode 100644 index 000000000..0fc3e246a --- /dev/null +++ b/android/core/ErrorReportingRunnable.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +public abstract class ErrorReportingRunnable implements Runnable { + + private final SonarConnection mConnection; + + public ErrorReportingRunnable(SonarConnection connection) { + mConnection = connection; + } + + @Override + public final void run() { + try { + runOrThrow(); + } catch (Exception e) { + if (mConnection != null) { + mConnection.reportError(e); + } + } finally { + doFinally(); + } + } + + protected void doFinally() {} + + protected abstract void runOrThrow() throws Exception; +} diff --git a/android/core/SonarArray.java b/android/core/SonarArray.java new file mode 100644 index 000000000..12b0973a2 --- /dev/null +++ b/android/core/SonarArray.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +import java.util.ArrayList; +import java.util.List; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class SonarArray { + final JSONArray mJson; + + SonarArray(JSONArray json) { + mJson = (json != null ? json : new JSONArray()); + } + + SonarArray(String json) { + try { + mJson = new JSONArray(json); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public SonarDynamic getDynamic(int index) { + return new SonarDynamic(mJson.opt(index)); + } + + public String getString(int index) { + return mJson.optString(index); + } + + public int getInt(int index) { + return mJson.optInt(index); + } + + public long getLong(int index) { + return mJson.optLong(index); + } + + public float getFloat(int index) { + return (float) mJson.optDouble(index); + } + + public double getDouble(int index) { + return mJson.optDouble(index); + } + + public boolean getBoolean(int index) { + return mJson.optBoolean(index); + } + + public SonarObject getObject(int index) { + final Object o = mJson.opt(index); + return new SonarObject((JSONObject) o); + } + + public SonarArray getArray(int index) { + final Object o = mJson.opt(index); + return new SonarArray((JSONArray) o); + } + + public int length() { + return mJson.length(); + } + + public List toStringList() { + final int length = length(); + final List list = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + list.add(getString(i)); + } + return list; + } + + public String toJsonString() { + return toString(); + } + + @Override + public String toString() { + return mJson.toString(); + } + + @Override + public boolean equals(Object o) { + return mJson.toString().equals(o.toString()); + } + + @Override + public int hashCode() { + return mJson.hashCode(); + } + + public static class Builder { + private final JSONArray mJson; + + public Builder() { + mJson = new JSONArray(); + } + + public Builder put(String s) { + mJson.put(s); + return this; + } + + public Builder put(Integer i) { + mJson.put(i); + return this; + } + + public Builder put(Long l) { + mJson.put(l); + return this; + } + + public Builder put(Float f) { + mJson.put(Float.isNaN(f) ? null : f); + return this; + } + + public Builder put(Double d) { + mJson.put(Double.isNaN(d) ? null : d); + return this; + } + + public Builder put(Boolean b) { + mJson.put(b); + return this; + } + + public Builder put(SonarValue v) { + return put(v.toSonarObject()); + } + + public Builder put(SonarArray a) { + mJson.put(a == null ? null : a.mJson); + return this; + } + + public Builder put(SonarArray.Builder b) { + return put(b.build()); + } + + public Builder put(SonarObject o) { + mJson.put(o == null ? null : o.mJson); + return this; + } + + public Builder put(SonarObject.Builder b) { + return put(b.build()); + } + + public SonarArray build() { + return new SonarArray(mJson); + } + } +} diff --git a/android/core/SonarClient.java b/android/core/SonarClient.java new file mode 100644 index 000000000..8c91c3599 --- /dev/null +++ b/android/core/SonarClient.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +public interface SonarClient { + void addPlugin(SonarPlugin plugin); + + T getPlugin(String id); + + void removePlugin(SonarPlugin plugin); + + void start(); + + void stop(); +} diff --git a/android/core/SonarConnection.java b/android/core/SonarConnection.java new file mode 100644 index 000000000..bd9ce9dd8 --- /dev/null +++ b/android/core/SonarConnection.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +/** + * A connection between a SonarPlugin and the desktop Sonar application. Register request handlers + * to respond to calls made by the desktop application or directly send messages to the desktop + * application. + */ +public interface SonarConnection { + + /** + * Call a remote method on the Sonar desktop application, passing an optional JSON object as a + * parameter. + */ + void send(String method, SonarObject params); + + /** + * Call a remote method on the Sonar desktop application, passing an optional JSON array as a + * parameter. + */ + void send(String method, SonarArray params); + + /** Report client error */ + void reportError(Throwable throwable); + + /** + * Register a receiver for a remote method call issued by the Sonar desktop application. The + * SonarReceiver is passed a responder to respond back to the desktop application. + */ + void receive(String method, SonarReceiver receiver); +} diff --git a/android/core/SonarDynamic.java b/android/core/SonarDynamic.java new file mode 100644 index 000000000..ffda260ee --- /dev/null +++ b/android/core/SonarDynamic.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +import javax.annotation.Nullable; +import org.json.JSONArray; +import org.json.JSONObject; + +public class SonarDynamic { + private final Object mObject; + + public SonarDynamic(Object object) { + mObject = object; + } + + public @Nullable String asString() { + if (mObject == null) { + return null; + } + return mObject.toString(); + } + + public int asInt() { + if (mObject == null) { + return 0; + } + if (mObject instanceof Integer) { + return (Integer) mObject; + } + if (mObject instanceof Long) { + return ((Long) mObject).intValue(); + } + if (mObject instanceof Float) { + return ((Float) mObject).intValue(); + } + if (mObject instanceof Double) { + return ((Double) mObject).intValue(); + } + return 0; + } + + public long asLong() { + if (mObject == null) { + return 0; + } + if (mObject instanceof Integer) { + return (Integer) mObject; + } + if (mObject instanceof Long) { + return (Long) mObject; + } + if (mObject instanceof Float) { + return ((Float) mObject).longValue(); + } + if (mObject instanceof Double) { + return ((Double) mObject).longValue(); + } + return 0; + } + + public float asFloat() { + if (mObject == null) { + return 0; + } + if (mObject instanceof Integer) { + return (Integer) mObject; + } + if (mObject instanceof Long) { + return (Long) mObject; + } + if (mObject instanceof Float) { + return (Float) mObject; + } + if (mObject instanceof Double) { + return ((Double) mObject).floatValue(); + } + return 0; + } + + public double asDouble() { + if (mObject == null) { + return 0; + } + if (mObject instanceof Integer) { + return (Integer) mObject; + } + if (mObject instanceof Long) { + return (Long) mObject; + } + if (mObject instanceof Float) { + return (Float) mObject; + } + if (mObject instanceof Double) { + return (Double) mObject; + } + return 0; + } + + public boolean asBoolean() { + if (mObject == null) { + return false; + } + return (Boolean) mObject; + } + + public SonarObject asObject() { + if (mObject instanceof JSONObject) { + return new SonarObject((JSONObject) mObject); + } + return (SonarObject) mObject; + } + + public SonarArray asArray() { + if (mObject instanceof JSONArray) { + return new SonarArray((JSONArray) mObject); + } + return (SonarArray) mObject; + } +} diff --git a/android/core/SonarObject.java b/android/core/SonarObject.java new file mode 100644 index 000000000..4c0081735 --- /dev/null +++ b/android/core/SonarObject.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +import java.util.Arrays; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class SonarObject { + final JSONObject mJson; + + public SonarObject(JSONObject json) { + mJson = (json != null ? json : new JSONObject()); + } + + public SonarObject(String json) { + try { + mJson = new JSONObject(json); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public SonarDynamic getDynamic(String name) { + return new SonarDynamic(mJson.opt(name)); + } + + public String getString(String name) { + if (mJson.isNull(name)) { + return null; + } + return mJson.optString(name); + } + + public int getInt(String name) { + return mJson.optInt(name); + } + + public long getLong(String name) { + return mJson.optLong(name); + } + + public float getFloat(String name) { + return (float) mJson.optDouble(name); + } + + public double getDouble(String name) { + return mJson.optDouble(name); + } + + public boolean getBoolean(String name) { + return mJson.optBoolean(name); + } + + public SonarObject getObject(String name) { + final Object o = mJson.opt(name); + return new SonarObject((JSONObject) o); + } + + public SonarArray getArray(String name) { + final Object o = mJson.opt(name); + return new SonarArray((JSONArray) o); + } + + public boolean contains(String name) { + return mJson.has(name); + } + + public String toJsonString() { + return toString(); + } + + @Override + public String toString() { + return mJson.toString(); + } + + @Override + public boolean equals(Object o) { + return mJson.toString().equals(o.toString()); + } + + @Override + public int hashCode() { + return mJson.hashCode(); + } + + public static class Builder { + private final JSONObject mJson; + + public Builder() { + mJson = new JSONObject(); + } + + public Builder put(String name, Object obj) { + if (obj == null) { + return put(name, (String) null); + } else if (obj instanceof Integer) { + return put(name, (Integer) obj); + } else if (obj instanceof Long) { + return put(name, (Long) obj); + } else if (obj instanceof Float) { + return put(name, (Float) obj); + } else if (obj instanceof Double) { + return put(name, (Double) obj); + } else if (obj instanceof String) { + return put(name, (String) obj); + } else if (obj instanceof Boolean) { + return put(name, (Boolean) obj); + } else if (obj instanceof Object[]) { + return put(name, Arrays.deepToString((Object[]) obj)); + } else if (obj instanceof SonarObject) { + return put(name, (SonarObject) obj); + } else if (obj instanceof SonarObject.Builder) { + return put(name, (SonarObject.Builder) obj); + } else if (obj instanceof SonarArray) { + return put(name, (SonarArray) obj); + } else if (obj instanceof SonarArray.Builder) { + return put(name, (SonarArray.Builder) obj); + } else if (obj instanceof SonarValue) { + return put(name, ((SonarValue) obj).toSonarObject()); + } else { + return put(name, obj.toString()); + } + } + + public Builder put(String name, String s) { + try { + mJson.put(name, s); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return this; + } + + public Builder put(String name, Integer i) { + try { + mJson.put(name, i); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return this; + } + + public Builder put(String name, Long l) { + try { + mJson.put(name, l); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return this; + } + + public Builder put(String name, Float f) { + try { + mJson.put(name, Float.isNaN(f) ? null : f); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return this; + } + + public Builder put(String name, Double d) { + try { + mJson.put(name, Double.isNaN(d) ? null : d); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return this; + } + + public Builder put(String name, Boolean b) { + try { + mJson.put(name, b); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return this; + } + + public Builder put(String name, SonarValue v) { + return put(name, v.toSonarObject()); + } + + public Builder put(String name, SonarArray a) { + try { + mJson.put(name, a == null ? null : a.mJson); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return this; + } + + public Builder put(String name, SonarArray.Builder b) { + return put(name, b.build()); + } + + public Builder put(String name, SonarObject o) { + try { + mJson.put(name, o == null ? null : o.mJson); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return this; + } + + public Builder put(String name, SonarObject.Builder b) { + return put(name, b.build()); + } + + public SonarObject build() { + return new SonarObject(mJson); + } + } +} diff --git a/android/core/SonarPlugin.java b/android/core/SonarPlugin.java new file mode 100644 index 000000000..f524f2037 --- /dev/null +++ b/android/core/SonarPlugin.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +/** + * A SonarPlugin is an object which exposes an API to the Desktop Sonar application. When a + * connection is established the plugin is given a SonarConnection on which it can register request + * handlers and send messages. When the SonarConnection is invalid onDisconnect is called. onConnect + * may be called again on the same plugin object if Sonar re-connects, this will provide a new + * SonarConnection, do not attempt to re-use the previous connection. + */ +public interface SonarPlugin { + + /** + * @return The id of this plugin. This is the namespace which Sonar desktop plugins will call + * methods on to route them to your plugin. This should match the id specified in your React + * plugin. + */ + String getId(); + + /** + * Called when a connection has been established. The connection passed to this method is valid + * until {@link SonarPlugin#onDisconnect()} is called. + */ + void onConnect(SonarConnection connection) throws Exception; + + /** + * Called when the connection passed to {@link SonarPlugin#onConnect(SonarConnection)} is no + * longer valid. Do not try to use the connection in or after this method has been called. + */ + void onDisconnect() throws Exception; +} diff --git a/android/core/SonarReceiver.java b/android/core/SonarReceiver.java new file mode 100644 index 000000000..08d1c7384 --- /dev/null +++ b/android/core/SonarReceiver.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +/** + * A receiver of a remote method call issued by the Sonar desktop application. If the given + * responder is present it means the Sonar desktop application is expecting a response. + */ +public interface SonarReceiver { + + /** + * Reciver for a request sent from the Sonar desktop client. + * + * @param params Optional set of parameters sent with the request. + * @param responder Optional responder for request. Some requests don't warrant a response + * through. In this case the request should be made from the desktop using send() instead of + * call(). + */ + void onReceive(SonarObject params, SonarResponder responder) throws Exception; +} diff --git a/android/core/SonarResponder.java b/android/core/SonarResponder.java new file mode 100644 index 000000000..3af6d4b2a --- /dev/null +++ b/android/core/SonarResponder.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +/** + * SonarResponder is used to asyncronously response to a messaged recieved from the Sonar desktop + * app. The Sonar Responder will automatically wrap the response in an approriate object depending + * on if it is an error or not. + */ +public interface SonarResponder { + + /** Deliver a successful response to the Sonar desktop app. */ + void success(SonarObject response); + + /** Deliver a successful response to the Sonar desktop app. */ + void success(SonarArray response); + + /** Deliver a successful response to the Sonar desktop app. */ + void success(); + + /** Inform the Sonar desktop app of an error in handling the request. */ + void error(SonarObject response); +} diff --git a/android/core/SonarValue.java b/android/core/SonarValue.java new file mode 100644 index 000000000..07126d00b --- /dev/null +++ b/android/core/SonarValue.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.core; + +public interface SonarValue { + SonarObject toSonarObject(); +} diff --git a/android/plugins/common/BufferingSonarPlugin.java b/android/plugins/common/BufferingSonarPlugin.java new file mode 100644 index 000000000..3881bddc8 --- /dev/null +++ b/android/plugins/common/BufferingSonarPlugin.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.common; + +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarPlugin; +import javax.annotation.Nullable; + +/** + * Sonar plugin that keeps events in a buffer until a connection is available. + * + *

In order to send data to the {@link SonarConnection}, use {@link #send(String, SonarObject)} + * instead of {@link SonarConnection#send(String, SonarObject)}. + */ +public abstract class BufferingSonarPlugin implements SonarPlugin { + + private static final int BUFFER_SIZE = 500; + + private @Nullable RingBuffer mEventQueue; + private @Nullable SonarConnection mConnection; + + @Override + public synchronized void onConnect(SonarConnection connection) { + mConnection = connection; + + sendBufferedEvents(); + } + + @Override + public synchronized void onDisconnect() { + mConnection = null; + } + + public synchronized SonarConnection getConnection() { + return mConnection; + } + + public synchronized boolean isConnected() { + return mConnection != null; + } + + public synchronized void send(String method, SonarObject sonarObject) { + if (mEventQueue == null) { + mEventQueue = new RingBuffer<>(BUFFER_SIZE); + } + mEventQueue.enqueue(new CachedSonarEvent(method, sonarObject)); + if (mConnection != null) { + mConnection.send(method, sonarObject); + } + } + + private synchronized void sendBufferedEvents() { + if (mEventQueue != null && mConnection != null) { + for (CachedSonarEvent cachedSonarEvent : mEventQueue.asList()) { + mConnection.send(cachedSonarEvent.method, cachedSonarEvent.sonarObject); + } + } + } + + private static class CachedSonarEvent { + final String method; + final SonarObject sonarObject; + + private CachedSonarEvent(String method, SonarObject sonarObject) { + this.method = method; + this.sonarObject = sonarObject; + } + } +} diff --git a/android/plugins/common/MainThreadSonarReceiver.java b/android/plugins/common/MainThreadSonarReceiver.java new file mode 100644 index 000000000..d793af421 --- /dev/null +++ b/android/plugins/common/MainThreadSonarReceiver.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.common; + +import android.os.Handler; +import android.os.Looper; +import com.facebook.sonar.core.ErrorReportingRunnable; +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarReceiver; +import com.facebook.sonar.core.SonarResponder; + +public abstract class MainThreadSonarReceiver implements SonarReceiver { + + public MainThreadSonarReceiver(SonarConnection connection) { + this.mConnection = connection; + } + + private final SonarConnection mConnection; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + @Override + public final void onReceive(final SonarObject params, final SonarResponder responder) { + mHandler.post( + new ErrorReportingRunnable(mConnection) { + @Override + public void runOrThrow() throws Exception { + onReceiveOnMainThread(params, responder); + } + }); + } + + public abstract void onReceiveOnMainThread(SonarObject params, SonarResponder responder) + throws Exception; +} diff --git a/android/plugins/common/RingBuffer.java b/android/plugins/common/RingBuffer.java new file mode 100644 index 000000000..872082683 --- /dev/null +++ b/android/plugins/common/RingBuffer.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.common; + +import java.util.LinkedList; +import java.util.List; + +final class RingBuffer { + final int mBufferSize; + final List mBuffer = new LinkedList<>(); + + RingBuffer(int bufferSize) { + mBufferSize = bufferSize; + } + + void enqueue(T item) { + if (mBuffer.size() >= mBufferSize) { + mBuffer.remove(0); + } + mBuffer.add(item); + } + + List asList() { + return mBuffer; + } +} diff --git a/android/plugins/console/ConsoleSonarPlugin.java b/android/plugins/console/ConsoleSonarPlugin.java new file mode 100644 index 000000000..53b818799 --- /dev/null +++ b/android/plugins/console/ConsoleSonarPlugin.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.console; + +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarPlugin; +import com.facebook.sonar.plugins.console.iface.ConsoleCommandReceiver; + +public class ConsoleSonarPlugin implements SonarPlugin { + + private final JavascriptEnvironment mJavascriptEnvironment; + private JavascriptSession mJavascriptSession; + + public ConsoleSonarPlugin(JavascriptEnvironment jsEnvironment) { + this.mJavascriptEnvironment = jsEnvironment; + } + + @Override + public String getId() { + return "Console"; + } + + @Override + public void onConnect(SonarConnection connection) throws Exception { + ConsoleCommandReceiver.listenForCommands(connection, mJavascriptEnvironment); + } + + public void onDisconnect() throws Exception { + } +} diff --git a/android/plugins/console/JavascriptEnvironment.java b/android/plugins/console/JavascriptEnvironment.java new file mode 100644 index 000000000..7aa0177c9 --- /dev/null +++ b/android/plugins/console/JavascriptEnvironment.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.console; + +import com.facebook.sonar.plugins.console.iface.ScriptingEnvironment; +import java.util.HashMap; +import java.util.Map; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.ContextFactory; + +public class JavascriptEnvironment implements ScriptingEnvironment { + + private final Map mBoundVariables; + private final ContextFactory mContextFactory; + + public JavascriptEnvironment() { + mBoundVariables = new HashMap<>(); + mContextFactory = + new ContextFactory() { + @Override + public boolean hasFeature(Context cx, int featureIndex) { + return featureIndex == Context.FEATURE_ENHANCED_JAVA_ACCESS; + } + }; + } + + @Override + public JavascriptSession startSession() { + return new JavascriptSession(mContextFactory, mBoundVariables); + } + + /** + * Method for other plugins to register objects to a name, so that they can be accessed in all + * console sessions. + * + * @param name The variable name to bind the object to. + * @param object The reference to bind. + */ + @Override + public void registerGlobalObject(String name, Object object) { + if (mBoundVariables.containsKey(name)) { + throw new IllegalStateException( + String.format( + "Variable %s is already reserved for %s", name, mBoundVariables.get(name))); + } + mBoundVariables.put(name, object); + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/android/plugins/console/JavascriptSession.java b/android/plugins/console/JavascriptSession.java new file mode 100644 index 000000000..d5a46372e --- /dev/null +++ b/android/plugins/console/JavascriptSession.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.console; + +import com.facebook.sonar.plugins.console.iface.ScriptingSession; +import java.io.Closeable; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.ContextFactory; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeJSON; +import org.mozilla.javascript.NativeJavaMethod; +import org.mozilla.javascript.NativeJavaObject; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Undefined; + +public class JavascriptSession implements Closeable, ScriptingSession { + + private static final String TYPE = "type"; + private static final String VALUE = "value"; + public static final String JSON = "json"; + private final Context mContext; + private final ContextFactory mContextFactory; + private final Scriptable mScope; + private final AtomicInteger lineNumber = new AtomicInteger(0); + + JavascriptSession(ContextFactory contextFactory, Map globals) { + mContextFactory = contextFactory; + mContext = contextFactory.enterContext(); + + // Interpreted mode, or it will produce Dalvik incompatible bytecode. + mContext.setOptimizationLevel(-1); + mScope = mContext.initStandardObjects(); + + for (Map.Entry entry : globals.entrySet()) { + final Object value = entry.getValue(); + + if (value instanceof Number || value instanceof String) { + ScriptableObject.putConstProperty(mScope, entry.getKey(), entry.getValue()); + } else { + // Calling java methods in the VM produces objects wrapped in NativeJava*. + // So passing in wrapped objects keeps them consistent. + ScriptableObject.putConstProperty( + mScope, + entry.getKey(), + new NativeJavaObject(mScope, entry.getValue(), entry.getValue().getClass())); + } + } + } + + @Override + public JSONObject evaluateCommand(String userScript) throws JSONException { + return evaluateCommand(userScript, mScope); + } + + @Override + public JSONObject evaluateCommand(String userScript, Object context) throws JSONException { + Scriptable scope = new NativeJavaObject(mScope, context, context.getClass()); + return evaluateCommand(userScript, scope); + } + + private JSONObject evaluateCommand(String command, Scriptable scope) throws JSONException { + try { + // This may be called by any thread, and contexts have to be entered in the current thread + // before being used, so enter/exit every time. + mContextFactory.enterContext(); + return toJson( + mContext.evaluateString( + scope, command, "sonar-console", lineNumber.incrementAndGet(), null)); + } finally { + Context.exit(); + } + } + + private JSONObject toJson(Object result) throws JSONException { + + if (result instanceof String) { + return new JSONObject().put(TYPE, JSON).put(VALUE, result); + } + + if (result instanceof Class) { + return new JSONObject().put(TYPE, "class").put(VALUE, ((Class) result).getName()); + } + + if (result instanceof NativeJavaObject + && ((NativeJavaObject) result).unwrap() instanceof String) { + return new JSONObject().put(TYPE, JSON).put(VALUE, ((NativeJavaObject) result).unwrap()); + } + + if (result instanceof NativeJavaObject + && ((NativeJavaObject) result).unwrap() instanceof Class) { + return new JSONObject() + .put(TYPE, "class") + .put(VALUE, ((NativeJavaObject) result).unwrap().toString()); + } + + if (result instanceof NativeJavaObject) { + final JSONObject o = new JSONObject(); + o.put("toString", ((NativeJavaObject) result).unwrap().toString()); + for (Object id : ((NativeJavaObject) result).getIds()) { + if (id instanceof String) { + final String name = (String) id; + final Object value = ((NativeJavaObject) result).get(name, (NativeJavaObject) result); + if (value != null && value instanceof NativeJavaMethod) { + continue; + } + final String valueString = value == null ? null : safeUnwrap(value).toString(); + o.put(name, valueString); + } + } + return new JSONObject().put(TYPE, "javaObject").put(VALUE, o); + } + + if (result instanceof NativeJavaMethod) { + final JSONObject o = new JSONObject(); + o.put(TYPE, "method"); + o.put("name", ((NativeJavaMethod) result).getFunctionName()); + return o; + } + + if (result == null || result instanceof Undefined) { + return new JSONObject().put(TYPE, "null"); + } + + if (result instanceof Function) { + final JSONObject o = new JSONObject(); + o.put(TYPE, "function"); + o.put(VALUE, Context.toString(result)); + return o; + } + + if (result instanceof ScriptableObject) { + return new JSONObject() + .put(TYPE, JSON) + .put( + VALUE, + new JSONTokener(NativeJSON.stringify(mContext, mScope, result, null, null).toString()) + .nextValue()); + } + + if (result instanceof Number) { + return new JSONObject().put(TYPE, JSON).put(VALUE, result); + } + + return new JSONObject().put(TYPE, "unknown").put(VALUE, result.toString()); + } + + @Override + public void close() { + Context.exit(); + } + + private static Object safeUnwrap(Object o) { + if (o instanceof NativeJavaObject) { + return ((NativeJavaObject) o).unwrap(); + } + return o; + } +} diff --git a/android/plugins/console/iface/ConsoleCommandReceiver.java b/android/plugins/console/iface/ConsoleCommandReceiver.java new file mode 100644 index 000000000..1d28d81b6 --- /dev/null +++ b/android/plugins/console/iface/ConsoleCommandReceiver.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.console.iface; + +import android.support.annotation.Nullable; +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarReceiver; +import com.facebook.sonar.core.SonarResponder; +import com.facebook.sonar.plugins.common.MainThreadSonarReceiver; +import org.json.JSONObject; + +/** + * Convenience class for adding console execution to a Sonar Plugin. Calling {@link + * ConsoleCommandReceiver#listenForCommands(SonarConnection, ScriptingEnvironment, ContextProvider)} + * will add the necessary listeners for responding to command execution calls. + */ +public class ConsoleCommandReceiver { + + /** + * Incoming command execution calls may reference a context ID that means something to your + * plugin. Implement {@link ContextProvider} to provide a mapping from context ID to java object. + * This will allow your sonar plugin to control the execution context of the command. + */ + public interface ContextProvider { + @Nullable + Object getObjectForId(String id); + } + + public static void listenForCommands( + final SonarConnection connection, + final ScriptingEnvironment scriptingEnvironment, + final ContextProvider contextProvider) { + + final ScriptingSession session = scriptingEnvironment.startSession(); + final SonarReceiver executeCommandReceiver = + new MainThreadSonarReceiver(connection) { + @Override + public void onReceiveOnMainThread(SonarObject params, SonarResponder responder) + throws Exception { + final String command = params.getString("command"); + final String contextObjectId = params.getString("context"); + final Object contextObject = contextProvider.getObjectForId(contextObjectId); + try { + JSONObject o = + contextObject == null + ? session.evaluateCommand(command) + : session.evaluateCommand(command, contextObject); + responder.success(new SonarObject(o)); + } catch (Exception e) { + responder.error(new SonarObject.Builder().put("message", e.getMessage()).build()); + } + } + }; + final SonarReceiver isEnabledReceiver = + new SonarReceiver() { + @Override + public void onReceive(SonarObject params, SonarResponder responder) throws Exception { + responder.success( + new SonarObject.Builder() + .put("isEnabled", scriptingEnvironment.isEnabled()) + .build()); + } + }; + + connection.receive("executeCommand", executeCommandReceiver); + connection.receive("isConsoleEnabled", isEnabledReceiver); + } + + public static void listenForCommands( + SonarConnection connection, ScriptingEnvironment scriptingEnvironment) { + listenForCommands(connection, scriptingEnvironment, nullContextProvider); + } + + private static final ContextProvider nullContextProvider = + new ContextProvider() { + @Override + @Nullable + public Object getObjectForId(String id) { + return null; + } + }; +} diff --git a/android/plugins/console/iface/NullScriptingEnvironment.java b/android/plugins/console/iface/NullScriptingEnvironment.java new file mode 100644 index 000000000..940893e22 --- /dev/null +++ b/android/plugins/console/iface/NullScriptingEnvironment.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.console.iface; + +import org.json.JSONException; +import org.json.JSONObject; + +public class NullScriptingEnvironment implements ScriptingEnvironment { + + @Override + public ScriptingSession startSession() { + return new NoOpScriptingSession(); + } + + @Override + public void registerGlobalObject(String name, Object object) {} + + static class NoOpScriptingSession implements ScriptingSession { + + @Override + public JSONObject evaluateCommand(String userScript) throws JSONException { + throw new UnsupportedOperationException("Console plugin not enabled in this app"); + } + + @Override + public JSONObject evaluateCommand(String userScript, Object context) throws JSONException { + return evaluateCommand(userScript); + } + + @Override + public void close() {} + } + + @Override + public boolean isEnabled() { + return false; + } +} diff --git a/android/plugins/console/iface/ScriptingEnvironment.java b/android/plugins/console/iface/ScriptingEnvironment.java new file mode 100644 index 000000000..3f5c8c17f --- /dev/null +++ b/android/plugins/console/iface/ScriptingEnvironment.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.console.iface; + +public interface ScriptingEnvironment { + + ScriptingSession startSession(); + + void registerGlobalObject(String name, Object object); + + boolean isEnabled(); +} diff --git a/android/plugins/console/iface/ScriptingSession.java b/android/plugins/console/iface/ScriptingSession.java new file mode 100644 index 000000000..89b37d165 --- /dev/null +++ b/android/plugins/console/iface/ScriptingSession.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.console.iface; + +import org.json.JSONException; +import org.json.JSONObject; + +public interface ScriptingSession { + + JSONObject evaluateCommand(String userScript) throws JSONException; + + JSONObject evaluateCommand(String userScript, Object context) throws JSONException; + + void close(); +} diff --git a/android/plugins/inspector/ApplicationWrapper.java b/android/plugins/inspector/ApplicationWrapper.java new file mode 100644 index 000000000..c3137d736 --- /dev/null +++ b/android/plugins/inspector/ApplicationWrapper.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.View; +import com.facebook.sonar.plugins.inspector.descriptors.utils.AndroidRootResolver; +import com.facebook.sonar.plugins.inspector.descriptors.utils.AndroidRootResolver.Root; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public class ApplicationWrapper implements Application.ActivityLifecycleCallbacks { + + public interface ActivityStackChangedListener { + void onActivityStackChanged(List stack); + } + + private final Application mApplication; + private final AndroidRootResolver mAndroidRootsResolver; + private final List> mActivities; + private final Handler mHandler; + private ActivityStackChangedListener mListener; + + public ApplicationWrapper(Application application) { + mApplication = application; + mAndroidRootsResolver = new AndroidRootResolver(); + mApplication.registerActivityLifecycleCallbacks(this); + mActivities = new ArrayList<>(); + mHandler = new Handler(Looper.getMainLooper()); + } + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + mActivities.add(new WeakReference<>(activity)); + notifyListener(); + } + + @Override + public void onActivityStarted(Activity activity) {} + + @Override + public void onActivityResumed(Activity activity) {} + + @Override + public void onActivityPaused(Activity activity) { + if (activity.isFinishing()) { + final Iterator> activityIterator = mActivities.iterator(); + + while (activityIterator.hasNext()) { + if (activityIterator.next().get() == activity) { + activityIterator.remove(); + } + } + + notifyListener(); + } + } + + @Override + public void onActivityStopped(Activity activity) {} + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + + @Override + public void onActivityDestroyed(Activity activity) {} + + private void notifyListener() { + if (mListener != null) { + mListener.onActivityStackChanged(getActivityStack()); + } + } + + public void setListener(ActivityStackChangedListener listener) { + mListener = listener; + } + + public Application getApplication() { + return mApplication; + } + + public List getActivityStack() { + final List activities = new ArrayList<>(mActivities.size()); + final Iterator> activityIterator = mActivities.iterator(); + + while (activityIterator.hasNext()) { + final Activity activity = activityIterator.next().get(); + if (activity == null) { + activityIterator.remove(); + } else { + activities.add(activity); + } + } + + return activities; + } + + public List getViewRoots() { + final List roots = mAndroidRootsResolver.listActiveRoots(); + if (roots == null) { + return Collections.EMPTY_LIST; + } + + final List viewRoots = new ArrayList<>(roots.size()); + for (Root root : roots) { + viewRoots.add(root.view); + } + + return viewRoots; + } + + public void postDelayed(Runnable r, long delayMillis) { + mHandler.postDelayed(r, delayMillis); + } +} diff --git a/android/plugins/inspector/BoundsDrawable.java b/android/plugins/inspector/BoundsDrawable.java new file mode 100644 index 000000000..01c2ebd8b --- /dev/null +++ b/android/plugins/inspector/BoundsDrawable.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.Drawable; +import android.text.TextPaint; +import javax.annotation.Nullable; + +public class BoundsDrawable extends Drawable { + public static final int COLOR_HIGHLIGHT_CONTENT = 0x888875c5; + public static final int COLOR_HIGHLIGHT_PADDING = 0x889dd185; + public static final int COLOR_HIGHLIGHT_MARGIN = 0x88f7b77b; + private static @Nullable BoundsDrawable sInstance; + + private final TextPaint mTextPaint; + private final Paint mMarginPaint; + private final Paint mPaddingPaint; + private final Paint mContentPaint; + private final Rect mWorkRect; + private final Rect mMarginBounds; + private final Rect mPaddingBounds; + private final Rect mContentBounds; + + private final int mStrokeWidth; + private final float mAscentOffset; + private final float mDensity; + + public static BoundsDrawable getInstance( + float density, Rect marginBounds, Rect paddingBounds, Rect contentBounds) { + final BoundsDrawable drawable = getInstance(density); + drawable.setBounds(marginBounds, paddingBounds, contentBounds); + return drawable; + } + + public static BoundsDrawable getInstance(float density) { + if (sInstance == null) { + sInstance = new BoundsDrawable(density); + } + return sInstance; + } + + private BoundsDrawable(float density) { + mWorkRect = new Rect(); + mMarginBounds = new Rect(); + mPaddingBounds = new Rect(); + mContentBounds = new Rect(); + + mDensity = density; + + mTextPaint = new TextPaint(); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextAlign(Paint.Align.CENTER); + mTextPaint.setTextSize(dpToPx(8f)); + mAscentOffset = -mTextPaint.ascent() / 2f; + mStrokeWidth = dpToPx(2f); + + mPaddingPaint = new Paint(); + mPaddingPaint.setStyle(Paint.Style.FILL); + mPaddingPaint.setColor(COLOR_HIGHLIGHT_PADDING); + + mContentPaint = new Paint(); + mContentPaint.setStyle(Paint.Style.FILL); + mContentPaint.setColor(COLOR_HIGHLIGHT_CONTENT); + + mMarginPaint = new Paint(); + mMarginPaint.setStyle(Paint.Style.FILL); + mMarginPaint.setColor(COLOR_HIGHLIGHT_MARGIN); + } + + public void setBounds(Rect marginBounds, Rect paddingBounds, Rect contentBounds) { + mMarginBounds.set(marginBounds); + mPaddingBounds.set(paddingBounds); + mContentBounds.set(contentBounds); + setBounds(marginBounds); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawRect(mContentBounds, mContentPaint); + + int saveCount = canvas.save(); + canvas.clipRect(mContentBounds, Region.Op.DIFFERENCE); + canvas.drawRect(mPaddingBounds, mPaddingPaint); + canvas.restoreToCount(saveCount); + + saveCount = canvas.save(); + canvas.clipRect(mPaddingBounds, Region.Op.DIFFERENCE); + canvas.drawRect(mMarginBounds, mMarginPaint); + canvas.restoreToCount(saveCount); + + drawBoundsDimensions(canvas, mContentBounds); + + // Disabled for now since Sonar doesn't support options too well at this point in time. + // Once options are supported, we should re-enable the calls below + // drawCardinalDimensionsBetween(canvas, mContentBounds, mPaddingBounds); + // drawCardinalDimensionsBetween(canvas, mPaddingBounds, mMarginBounds); + } + + @Override + public void setAlpha(int alpha) { + // No-op + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + // No-op + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + private void drawCardinalDimensionsBetween(Canvas canvas, Rect inner, Rect outer) { + mWorkRect.set(inner); + mWorkRect.left = outer.left; + mWorkRect.right = inner.left; + drawBoundsDimension(canvas, mWorkRect, mWorkRect.width()); + mWorkRect.left = inner.right; + mWorkRect.right = outer.right; + drawBoundsDimension(canvas, mWorkRect, mWorkRect.width()); + mWorkRect.set(outer); + mWorkRect.bottom = inner.top; + drawBoundsDimension(canvas, mWorkRect, mWorkRect.height()); + mWorkRect.bottom = outer.bottom; + mWorkRect.top = inner.bottom; + drawBoundsDimension(canvas, mWorkRect, mWorkRect.height()); + } + + private void drawBoundsDimension(Canvas canvas, Rect bounds, int value) { + if (value <= 0) { + return; + } + int saveCount = canvas.save(); + canvas.translate(bounds.centerX(), bounds.centerY()); + drawOutlinedText(canvas, value + "px"); + canvas.restoreToCount(saveCount); + } + + private void drawBoundsDimensions(Canvas canvas, Rect bounds) { + int saveCount = canvas.save(); + canvas.translate(bounds.centerX(), bounds.centerY()); + drawOutlinedText(canvas, bounds.width() + "px \u00D7 " + bounds.height() + "px"); + canvas.restoreToCount(saveCount); + } + + private void drawOutlinedText(Canvas canvas, String text) { + mTextPaint.setColor(Color.BLACK); + mTextPaint.setStrokeWidth(mStrokeWidth); + mTextPaint.setStyle(Paint.Style.STROKE); + canvas.drawText(text, 0f, mAscentOffset, mTextPaint); + + mTextPaint.setColor(Color.WHITE); + mTextPaint.setStrokeWidth(0f); + mTextPaint.setStyle(Paint.Style.FILL); + canvas.drawText(text, 0f, mAscentOffset, mTextPaint); + } + + private int dpToPx(float dp) { + return (int) (dp * mDensity); + } +} diff --git a/android/plugins/inspector/DescriptorMapping.java b/android/plugins/inspector/DescriptorMapping.java new file mode 100644 index 000000000..cdf906570 --- /dev/null +++ b/android/plugins/inspector/DescriptorMapping.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +import android.app.Activity; +import android.app.Dialog; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.TextView; +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.plugins.inspector.descriptors.ActivityDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.ApplicationDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.DialogDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.DialogFragmentDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.DrawableDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.FragmentDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.ObjectDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.SupportDialogFragmentDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.SupportFragmentDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.TextViewDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.ViewDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.ViewGroupDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.WindowDescriptor; +import java.util.HashMap; +import java.util.Map; + +/** + * A mapping from classes to the object use to describe instances of a class. When looking for a + * descriptor to describe an object this classs will traverse the object's class hierarchy until it + * finds a matching descriptor instance. + */ +public class DescriptorMapping { + private Map, NodeDescriptor> mMapping = new HashMap<>(); + + /** + * @return A DescriptorMapping initialized with default descriptors for java and Android classes. + */ + public static DescriptorMapping withDefaults() { + final DescriptorMapping mapping = new DescriptorMapping(); + mapping.register(Object.class, new ObjectDescriptor()); + mapping.register(ApplicationWrapper.class, new ApplicationDescriptor()); + mapping.register(Activity.class, new ActivityDescriptor()); + mapping.register(Window.class, new WindowDescriptor()); + mapping.register(ViewGroup.class, new ViewGroupDescriptor()); + mapping.register(View.class, new ViewDescriptor()); + mapping.register(TextView.class, new TextViewDescriptor()); + mapping.register(Drawable.class, new DrawableDescriptor()); + mapping.register(Dialog.class, new DialogDescriptor()); + mapping.register(android.app.Fragment.class, new FragmentDescriptor()); + mapping.register(android.support.v4.app.Fragment.class, new SupportFragmentDescriptor()); + mapping.register(android.app.DialogFragment.class, new DialogFragmentDescriptor()); + mapping.register( + android.support.v4.app.DialogFragment.class, new SupportDialogFragmentDescriptor()); + return mapping; + } + + /** Register a descriptor for a given class. */ + public void register(Class clazz, NodeDescriptor descriptor) { + mMapping.put(clazz, descriptor); + } + + NodeDescriptor descriptorForClass(Class clazz) { + while (!mMapping.containsKey(clazz)) { + clazz = clazz.getSuperclass(); + } + return mMapping.get(clazz); + } + + void onConnect(SonarConnection connection) { + for (NodeDescriptor descriptor : mMapping.values()) { + descriptor.setConnection(connection); + descriptor.setDescriptorMapping(this); + } + } + + void onDisconnect() { + for (NodeDescriptor descriptor : mMapping.values()) { + descriptor.setConnection(null); + } + } +} diff --git a/android/plugins/inspector/HiddenNode.java b/android/plugins/inspector/HiddenNode.java new file mode 100644 index 000000000..5e54f5bd9 --- /dev/null +++ b/android/plugins/inspector/HiddenNode.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +/** Marker interface to identify nodes which should not be traversed. */ +public interface HiddenNode {} diff --git a/android/plugins/inspector/HighlightedOverlay.java b/android/plugins/inspector/HighlightedOverlay.java new file mode 100644 index 000000000..b887feee9 --- /dev/null +++ b/android/plugins/inspector/HighlightedOverlay.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.View; + +/** + * A singleton instance of a overlay drawable used for highlighting node bounds. See {@link + * NodeDescriptor#setHighlighted(Object, boolean)}. + */ +public class HighlightedOverlay { + private static final boolean VIEW_OVERLAY_SUPPORT = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; + + /** + * Highlights a particular view with its content bounds, padding and margin dimensions + * + * @param targetView The view to apply the highlight on + * @param margin A {@link Rect} containing the margin values + * @param padding A {@link Rect} containing the padding values + * @param contentBounds The {@link Rect} bounds of the content, which includes padding + */ + public static void setHighlighted( + View targetView, Rect margin, Rect padding, Rect contentBounds) { + if (!VIEW_OVERLAY_SUPPORT) { + return; + } + + contentBounds.set( + contentBounds.left + padding.left, + contentBounds.top + padding.top, + contentBounds.right - padding.right, + contentBounds.bottom - padding.bottom); + + padding = enclose(padding, contentBounds); + margin = enclose(margin, padding); + + final float density = targetView.getContext().getResources().getDisplayMetrics().density; + final Drawable overlay = BoundsDrawable.getInstance(density, margin, padding, contentBounds); + targetView.getOverlay().add(overlay); + } + + public static void removeHighlight(View targetView) { + if (!VIEW_OVERLAY_SUPPORT) { + return; + } + + final float density = targetView.getContext().getResources().getDisplayMetrics().density; + final Drawable overlay = BoundsDrawable.getInstance(density); + targetView.getOverlay().remove(overlay); + } + + private static Rect enclose(Rect parent, Rect child) { + return new Rect( + child.left - parent.left, + child.top - parent.top, + child.right + parent.right, + child.bottom + parent.bottom); + } +} diff --git a/android/plugins/inspector/InspectorSonarPlugin.java b/android/plugins/inspector/InspectorSonarPlugin.java new file mode 100644 index 000000000..a502b4587 --- /dev/null +++ b/android/plugins/inspector/InspectorSonarPlugin.java @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +import android.app.Application; +import android.content.Context; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import com.facebook.sonar.core.ErrorReportingRunnable; +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarPlugin; +import com.facebook.sonar.core.SonarReceiver; +import com.facebook.sonar.core.SonarResponder; +import com.facebook.sonar.plugins.common.MainThreadSonarReceiver; +import com.facebook.sonar.plugins.console.iface.ConsoleCommandReceiver; +import com.facebook.sonar.plugins.console.iface.NullScriptingEnvironment; +import com.facebook.sonar.plugins.console.iface.ScriptingEnvironment; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +public class InspectorSonarPlugin implements SonarPlugin { + + private ApplicationWrapper mApplication; + private DescriptorMapping mDescriptorMapping; + private ObjectTracker mObjectTracker; + private ScriptingEnvironment mScriptingEnvironment; + private String mHighlightedId; + private TouchOverlayView mTouchOverlay; + private SonarConnection mConnection; + + public InspectorSonarPlugin( + Context context, + DescriptorMapping descriptorMapping, + ScriptingEnvironment scriptingEnvironment) { + this( + new ApplicationWrapper((Application) context.getApplicationContext()), + descriptorMapping, + scriptingEnvironment); + } + + public InspectorSonarPlugin(Context context, DescriptorMapping descriptorMapping) { + this(context, descriptorMapping, new NullScriptingEnvironment()); + } + + // Package visible for testing + InspectorSonarPlugin( + ApplicationWrapper wrapper, + DescriptorMapping descriptorMapping, + ScriptingEnvironment scriptingEnvironment) { + mDescriptorMapping = descriptorMapping; + + mObjectTracker = new ObjectTracker(); + mApplication = wrapper; + mScriptingEnvironment = scriptingEnvironment; + } + + @Override + public String getId() { + return "Inspector"; + } + + @Override + public void onConnect(SonarConnection connection) throws Exception { + mConnection = connection; + mDescriptorMapping.onConnect(connection); + + ConsoleCommandReceiver.listenForCommands( + connection, + mScriptingEnvironment, + new ConsoleCommandReceiver.ContextProvider() { + @Override + @Nullable + public Object getObjectForId(String id) { + return mObjectTracker.get(id); + } + }); + connection.receive("getRoot", mGetRoot); + connection.receive("getNodes", mGetNodes); + connection.receive("setData", mSetData); + connection.receive("setHighlighted", mSetHighlighted); + connection.receive("setSearchActive", mSetSearchActive); + connection.receive("getSearchResults", mGetSearchResults); + } + + @Override + public void onDisconnect() throws Exception { + if (mHighlightedId != null) { + setHighlighted(mHighlightedId, false); + mHighlightedId = null; + } + + mObjectTracker.clear(); + mDescriptorMapping.onDisconnect(); + mConnection = null; + } + + final SonarReceiver mGetRoot = + new MainThreadSonarReceiver(mConnection) { + @Override + public void onReceiveOnMainThread(SonarObject params, SonarResponder responder) + throws Exception { + responder.success(getNode(trackObject(mApplication))); + } + }; + + final SonarReceiver mGetNodes = + new MainThreadSonarReceiver(mConnection) { + @Override + public void onReceiveOnMainThread(final SonarObject params, final SonarResponder responder) + throws Exception { + final SonarArray ids = params.getArray("ids"); + final SonarArray.Builder result = new SonarArray.Builder(); + + for (int i = 0, count = ids.length(); i < count; i++) { + final String id = ids.getString(i); + final SonarObject node = getNode(id); + if (node != null) { + result.put(node); + } else { + responder.error( + new SonarObject.Builder() + .put("message", "No node with given id") + .put("id", id) + .build()); + return; + } + } + + responder.success(new SonarObject.Builder().put("elements", result).build()); + } + }; + + final SonarReceiver mSetData = + new MainThreadSonarReceiver(mConnection) { + @Override + public void onReceiveOnMainThread(final SonarObject params, SonarResponder responder) + throws Exception { + final String nodeId = params.getString("id"); + final SonarArray keyPath = params.getArray("path"); + final SonarDynamic value = params.getDynamic("value"); + + final Object obj = mObjectTracker.get(nodeId); + if (obj == null) { + return; + } + + final NodeDescriptor descriptor = descriptorForObject(obj); + if (descriptor == null) { + return; + } + + final int count = keyPath.length(); + final String[] path = new String[count]; + for (int i = 0; i < count; i++) { + path[i] = keyPath.getString(i); + } + + descriptor.setValue(obj, path, value); + } + }; + + final SonarReceiver mSetHighlighted = + new MainThreadSonarReceiver(mConnection) { + @Override + public void onReceiveOnMainThread(final SonarObject params, SonarResponder responder) + throws Exception { + final String nodeId = params.getString("id"); + + if (mHighlightedId != null) { + setHighlighted(mHighlightedId, false); + } + + if (nodeId != null) { + setHighlighted(nodeId, true); + } + + mHighlightedId = nodeId; + } + }; + + final SonarReceiver mSetSearchActive = + new MainThreadSonarReceiver(mConnection) { + @Override + public void onReceiveOnMainThread(final SonarObject params, SonarResponder responder) + throws Exception { + final boolean active = params.getBoolean("active"); + final List roots = mApplication.getViewRoots(); + + ViewGroup root = null; + for (int i = roots.size() - 1; i >= 0; i--) { + if (roots.get(i) instanceof ViewGroup) { + root = (ViewGroup) roots.get(i); + break; + } + } + + if (root != null) { + if (active) { + mTouchOverlay = new TouchOverlayView(root.getContext()); + root.addView(mTouchOverlay); + root.bringChildToFront(mTouchOverlay); + } else { + root.removeView(mTouchOverlay); + mTouchOverlay = null; + } + } + } + }; + + final SonarReceiver mGetSearchResults = + new MainThreadSonarReceiver(mConnection) { + @Override + public void onReceiveOnMainThread(SonarObject params, SonarResponder responder) + throws Exception { + final String query = params.getString("query"); + final SearchResultNode matchTree = searchTree(query.toLowerCase(), mApplication); + final SonarObject results = matchTree == null ? null : matchTree.toSonarObject(); + final SonarObject response = + new SonarObject.Builder().put("results", results).put("query", query).build(); + responder.success(response); + } + }; + + class TouchOverlayView extends View implements HiddenNode { + public TouchOverlayView(Context context) { + super(context); + setBackgroundColor(BoundsDrawable.COLOR_HIGHLIGHT_CONTENT); + } + + @Override + public boolean onTouchEvent(final MotionEvent event) { + if (event.getAction() != MotionEvent.ACTION_UP) { + return true; + } + + new ErrorReportingRunnable(mConnection) { + @Override + public void runOrThrow() throws Exception { + hitTest((int) event.getX(), (int) event.getY()); + } + }.run(); + + return true; + } + } + + void hitTest(final int touchX, final int touchY) throws Exception { + final SonarArray.Builder path = new SonarArray.Builder(); + path.put(trackObject(mApplication)); + + final Touch touch = + new Touch() { + int x = touchX; + int y = touchY; + Object node = mApplication; + + @Override + public void finish() { + mConnection.send("select", new SonarObject.Builder().put("path", path).build()); + } + + @Override + public void continueWithOffset( + final int childIndex, final int offsetX, final int offsetY) { + final Touch touch = this; + + new ErrorReportingRunnable(mConnection) { + @Override + protected void runOrThrow() throws Exception { + x -= offsetX; + y -= offsetY; + + node = assertNotNull(descriptorForObject(node).getChildAt(node, childIndex)); + path.put(trackObject(node)); + + final NodeDescriptor descriptor = descriptorForObject(node); + descriptor.hitTest(node, touch); + } + }.run(); + } + + @Override + public boolean containedIn(int l, int t, int r, int b) { + return x >= l && x <= r && y >= t && y <= b; + } + }; + + final NodeDescriptor descriptor = descriptorForObject(mApplication); + descriptor.hitTest(mApplication, touch); + } + + private void setHighlighted(final String id, final boolean highlighted) throws Exception { + final Object obj = mObjectTracker.get(id); + if (obj == null) { + return; + } + + final NodeDescriptor descriptor = descriptorForObject(obj); + if (descriptor == null) { + return; + } + + descriptor.setHighlighted(obj, highlighted); + } + + public SearchResultNode searchTree(String query, Object obj) throws Exception { + final NodeDescriptor descriptor = descriptorForObject(obj); + List childTrees = null; + boolean isMatch = descriptor.matches(query, obj); + + for (int i = 0; i < descriptor.getChildCount(obj); i++) { + Object child = descriptor.getChildAt(obj, i); + SearchResultNode childNode = searchTree(query, child); + if (childNode != null) { + if (childTrees == null) { + childTrees = new ArrayList<>(); + } + childTrees.add(childNode); + } + } + + if (isMatch || childTrees != null) { + final String id = trackObject(obj); + return new SearchResultNode(id, isMatch, getNode(id), childTrees); + } + return null; + } + + private @Nullable SonarObject getNode(String id) throws Exception { + final Object obj = mObjectTracker.get(id); + if (obj == null) { + return null; + } + + final NodeDescriptor descriptor = descriptorForObject(obj); + if (descriptor == null) { + return null; + } + + final SonarArray.Builder children = new SonarArray.Builder(); + new ErrorReportingRunnable(mConnection) { + @Override + protected void runOrThrow() throws Exception { + for (int i = 0, count = descriptor.getChildCount(obj); i < count; i++) { + final Object child = assertNotNull(descriptor.getChildAt(obj, i)); + children.put(trackObject(child)); + } + } + }.run(); + + final SonarObject.Builder data = new SonarObject.Builder(); + new ErrorReportingRunnable(mConnection) { + @Override + protected void runOrThrow() throws Exception { + for (Named props : descriptor.getData(obj)) { + data.put(props.getName(), props.getValue()); + } + } + }.run(); + + final SonarArray.Builder attributes = new SonarArray.Builder(); + new ErrorReportingRunnable(mConnection) { + @Override + protected void runOrThrow() throws Exception { + for (Named attribute : descriptor.getAttributes(obj)) { + attributes.put( + new SonarObject.Builder() + .put("name", attribute.getName()) + .put("value", attribute.getValue()) + .build()); + } + } + }.run(); + + return new SonarObject.Builder() + .put("id", descriptor.getId(obj)) + .put("name", descriptor.getName(obj)) + .put("data", data) + .put("children", children) + .put("attributes", attributes) + .put("decoration", descriptor.getDecoration(obj)) + .build(); + } + + private String trackObject(Object obj) throws Exception { + final NodeDescriptor descriptor = descriptorForObject(obj); + final String id = descriptor.getId(obj); + final Object curr = mObjectTracker.get(id); + if (obj != curr) { + mObjectTracker.put(id, obj); + descriptor.init(obj); + } + return id; + } + + private NodeDescriptor descriptorForObject(Object obj) { + final Class c = assertNotNull(obj).getClass(); + return (NodeDescriptor) mDescriptorMapping.descriptorForClass(c); + } + + private static Object assertNotNull(@Nullable Object o) { + if (o == null) { + throw new AssertionError("Unexpected null value"); + } + return o; + } +} diff --git a/android/plugins/inspector/InspectorValue.java b/android/plugins/inspector/InspectorValue.java new file mode 100644 index 000000000..15a937c36 --- /dev/null +++ b/android/plugins/inspector/InspectorValue.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarValue; + +public class InspectorValue implements SonarValue { + + /** + * Descrive the type of data this value contains. This will influence how values are parsed and + * displayed by the Sonar desktop app. For example colors will be parse as integers and displayed + * using hex values and be editable using a color picker. + * + *

Do not extends this list of types without adding support for the type in the desktop + * Inspector. + */ + public static class Type { + + public static final Type Auto = new Type<>("auto"); + public static final Type Text = new Type<>("text"); + public static final Type Number = new Type<>("number"); + public static final Type Boolean = new Type<>("boolean"); + public static final Type Enum = new Type<>("enum"); + public static final Type Color = new Type<>("color"); + + private final String mName; + + Type(String name) { + mName = name; + } + + @Override + public String toString() { + return mName; + } + } + + final Type mType; + final T mValue; + final boolean mMutable; + + private InspectorValue(Type type, T value, boolean mutable) { + mType = type; + mValue = value; + mMutable = mutable; + } + + public static InspectorValue mutable(Type type, T value) { + return new InspectorValue<>(type, value, true); + } + + public static InspectorValue immutable(Type type, T value) { + return new InspectorValue<>(type, value, false); + } + + public static InspectorValue mutable(Object value) { + return new InspectorValue<>(Type.Auto, value, true); + } + + public static InspectorValue immutable(Object value) { + return new InspectorValue<>(Type.Auto, value, false); + } + + @Override + public SonarObject toSonarObject() { + return new SonarObject.Builder() + .put("__type__", mType) + .put("__mutable__", mMutable) + .put("value", mValue) + .build(); + } +} diff --git a/android/plugins/inspector/Named.java b/android/plugins/inspector/Named.java new file mode 100644 index 000000000..c0cda4395 --- /dev/null +++ b/android/plugins/inspector/Named.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +public class Named { + private final String mName; + private final ValueType mValue; + + public Named(String name, ValueType value) { + mName = name; + mValue = value; + } + + public String getName() { + return mName; + } + + public ValueType getValue() { + return mValue; + } +} diff --git a/android/plugins/inspector/NodeDescriptor.java b/android/plugins/inspector/NodeDescriptor.java new file mode 100644 index 000000000..914a8f3fb --- /dev/null +++ b/android/plugins/inspector/NodeDescriptor.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import java.util.List; + +/** + * A NodeDescriptor is an object which known how to expose an Object of type T to the ew Inspector. + * This class is the extension point for the Sonar inspector plugin and is how custom classes and + * data can be exposed to the inspector. + */ +public abstract class NodeDescriptor { + private SonarConnection mConnection; + private DescriptorMapping mDescriptorMapping; + + void setConnection(SonarConnection connection) { + mConnection = connection; + } + + void setDescriptorMapping(DescriptorMapping descriptorMapping) { + mDescriptorMapping = descriptorMapping; + } + + /** + * @return The descriptor for a given class. This is useful for when a descriptor wants to + * delegate parts of its implementation to another descriptor, say the super class of the + * object it describes. This is highly encouraged instead of subclassing another descriptor + * class. + */ + protected final NodeDescriptor descriptorForClass(Class clazz) { + return mDescriptorMapping.descriptorForClass(clazz); + } + + /** + * Invalidate a node. This tells Sonar that this node is no longer valid and its properties and/or + * children have changed. This will trigger Sonar to re-query this node getting any new data. + */ + protected final void invalidate(final T node) { + if (mConnection != null) { + new ErrorReportingRunnable() { + @Override + protected void runOrThrow() throws Exception { + SonarArray array = + new SonarArray.Builder() + .put(new SonarObject.Builder().put("id", getId(node)).build()) + .build(); + SonarObject params = new SonarObject.Builder().put("nodes", array).build(); + mConnection.send("invalidate", params); + } + }.run(); + } + } + + protected final boolean connected() { + return mConnection != null; + } + + protected abstract class ErrorReportingRunnable + extends com.facebook.sonar.core.ErrorReportingRunnable { + public ErrorReportingRunnable() { + super(mConnection); + } + } + + /** + * Initialize a node. This implementation usually consists of setting up listeners to know when to + * call {@link NodeDescriptor#invalidate(Object)}. + */ + public abstract void init(T node) throws Exception; + + /** + * A globally unique ID used to identify a node in a hierarchy. If your node does not have a + * globally unique ID it is fine to rely on {@link System#identityHashCode(Object)}. + */ + public abstract String getId(T node) throws Exception; + + /** + * The name used to identify this node in the inspector. Does not need to be unique. A good + * default is to use the class name of the node. + */ + public abstract String getName(T node) throws Exception; + + /** @return The number of children this node exposes in the inspector. */ + public abstract int getChildCount(T node) throws Exception; + + /** @return The child at index. */ + public abstract Object getChildAt(T node, int index) throws Exception; + + /** + * Get the data to show for this node in the sidebar of the inspector. The object will be showen + * in order and with a header matching the given name. + */ + public abstract List> getData(T node) throws Exception; + + /** + * Set a value on the provided node at the given path. The path will match a key path in the data + * provided by {@link this#getData(Object)} and the value will be of the same type as the value + * mathcing that path in the returned object. + */ + public abstract void setValue(T node, String[] path, SonarDynamic value) throws Exception; + + /** + * Get the attributes for this node. This is a list of read-only string:string mapping which show + * up inline in the elements inspector. See {@link Named} for more information. + */ + public abstract List> getAttributes(T node) throws Exception; + + /** + * Highlight this node. Use {@link HighlightedOverlay} if possible. This is used to highlight a + * node which is selected in the inspector. The plugin automatically takes care of de-selecting + * the previously highlighted node. + */ + public abstract void setHighlighted(T node, boolean selected) throws Exception; + + /** + * Perform hit testing on the given node. Either continue the search in a child with {@link + * Touch#continueWithOffset(int, int, int)} or finish the hit testing on this node with {@link + * Touch#finish()} + */ + public abstract void hitTest(T node, Touch touch) throws Exception; + + /** + * @return A string indicating how this element should be decorated. Check with the Sonar desktop + * app to see what values are supported. + */ + public abstract String getDecoration(T node) throws Exception; + + /** + * Test this node against a given query to see if it matches. This is used for finding search + * results. + */ + public abstract boolean matches(String query, T node) throws Exception; +} diff --git a/android/plugins/inspector/ObjectTracker.java b/android/plugins/inspector/ObjectTracker.java new file mode 100644 index 000000000..994699042 --- /dev/null +++ b/android/plugins/inspector/ObjectTracker.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +class ObjectTracker { + private final Map> mObjects = new HashMap<>(); + + void put(String id, Object obj) { + mObjects.put(id, new WeakReference<>(obj)); + } + + @Nullable + Object get(String id) { + final WeakReference weakObj = mObjects.get(id); + if (weakObj == null) { + return null; + } + + final Object obj = weakObj.get(); + if (obj == null) { + mObjects.remove(id); + } + + return obj; + } + + void clear() { + mObjects.clear(); + } + + boolean contains(String id) { + return mObjects.containsKey(id); + } +} diff --git a/android/plugins/inspector/SearchResultNode.java b/android/plugins/inspector/SearchResultNode.java new file mode 100644 index 000000000..2c6ef5ad4 --- /dev/null +++ b/android/plugins/inspector/SearchResultNode.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +import android.support.annotation.Nullable; +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarObject; +import java.util.List; + +public class SearchResultNode { + + private final String id; + private final boolean isMatch; + private final SonarObject element; + @Nullable + private final List children; + + SearchResultNode( + String id, boolean isMatch, SonarObject element, @Nullable List children) { + this.id = id; + this.isMatch = isMatch; + this.element = element; + this.children = children; + } + + SonarObject toSonarObject() { + final SonarArray childArray; + if (children != null) { + final SonarArray.Builder builder = new SonarArray.Builder(); + for (SearchResultNode child : children) { + builder.put(child.toSonarObject()); + } + childArray = builder.build(); + } else { + childArray = null; + } + + return new SonarObject.Builder() + .put("id", this.id) + .put("isMatch", this.isMatch) + .put("element", this.element) + .put("children", childArray) + .build(); + } +} diff --git a/android/plugins/inspector/SelfRegisteringNodeDescriptor.java b/android/plugins/inspector/SelfRegisteringNodeDescriptor.java new file mode 100644 index 000000000..7095cad6a --- /dev/null +++ b/android/plugins/inspector/SelfRegisteringNodeDescriptor.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +public abstract class SelfRegisteringNodeDescriptor extends NodeDescriptor { + + public abstract void register(DescriptorMapping descriptorMapping); +} diff --git a/android/plugins/inspector/Touch.java b/android/plugins/inspector/Touch.java new file mode 100644 index 000000000..9d44bad99 --- /dev/null +++ b/android/plugins/inspector/Touch.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector; + +/** + * Used to collect the path from the root node to the node at point [x, y]. This is used for + * creating a node path to the targeting node from the root when performing hit testing. + */ +public interface Touch { + + /** + * Call this when the path has reached its destination. This should be called from the descriptor + * which described the leaf node containing [x, y]. + */ + void finish(); + + /** + * Continue hit testing in child at the given index. Offseting the touch location to the child's + * coordinate system. + */ + void continueWithOffset(int childIndex, int offsetX, int offsetY); + + /** @return Whether or not this Touch is contained within the provided bounds. */ + boolean containedIn(int l, int t, int r, int b); +} diff --git a/android/plugins/inspector/descriptors/ActivityDescriptor.java b/android/plugins/inspector/descriptors/ActivityDescriptor.java new file mode 100644 index 000000000..12998afe0 --- /dev/null +++ b/android/plugins/inspector/descriptors/ActivityDescriptor.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import android.app.Activity; +import android.view.Window; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import com.facebook.stetho.common.android.FragmentActivityAccessor; +import com.facebook.stetho.common.android.FragmentCompat; +import com.facebook.stetho.common.android.FragmentManagerAccessor; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class ActivityDescriptor extends NodeDescriptor { + + @Override + public void init(Activity node) {} + + @Override + public String getId(Activity node) { + return Integer.toString(System.identityHashCode(node)); + } + + @Override + public String getName(Activity node) { + return node.getClass().getSimpleName(); + } + + @Override + public int getChildCount(Activity node) { + return (node.getWindow() != null ? 1 : 0) + + getDialogFragments(FragmentCompat.getSupportLibInstance(), node).size() + + getDialogFragments(FragmentCompat.getFrameworkInstance(), node).size(); + } + + @Override + public Object getChildAt(Activity node, int index) { + if (node.getWindow() != null) { + if (index == 0) { + return node.getWindow(); + } else { + index--; + } + } + + final List dialogs = getDialogFragments(FragmentCompat.getSupportLibInstance(), node); + if (index < dialogs.size()) { + return dialogs.get(index); + } else { + final List supportDialogs = getDialogFragments(FragmentCompat.getFrameworkInstance(), node); + return supportDialogs.get(index - dialogs.size()); + } + } + + @Override + public List> getData(Activity node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setValue(Activity node, String[] path, SonarDynamic value) throws Exception {} + + @Override + public List> getAttributes(Activity node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setHighlighted(Activity node, boolean selected) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Window.class); + descriptor.setHighlighted(node.getWindow(), selected); + } + + @Override + public void hitTest(Activity node, Touch touch) { + touch.continueWithOffset(0, 0, 0); + } + + @Override + public @Nullable String getDecoration(Activity obj) { + return null; + } + + @Override + public boolean matches(String query, Activity node) throws Exception { + NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } + + private static List getDialogFragments(FragmentCompat compat, Activity activity) { + if (compat == null || !compat.getFragmentActivityClass().isInstance(activity)) { + return Collections.EMPTY_LIST; + } + + FragmentActivityAccessor activityAccessor = compat.forFragmentActivity(); + Object fragmentManager = activityAccessor.getFragmentManager(activity); + if (fragmentManager == null) { + return Collections.EMPTY_LIST; + } + + FragmentManagerAccessor fragmentManagerAccessor = compat.forFragmentManager(); + List addedFragments = fragmentManagerAccessor.getAddedFragments(fragmentManager); + if (addedFragments == null) { + return Collections.EMPTY_LIST; + } + + final List dialogFragments = new ArrayList<>(); + for (int i = 0, N = addedFragments.size(); i < N; ++i) { + final Object fragment = addedFragments.get(i); + if (compat.getDialogFragmentClass().isInstance(fragment)) { + dialogFragments.add(fragment); + } + } + + return dialogFragments; + } +} diff --git a/android/plugins/inspector/descriptors/ApplicationDescriptor.java b/android/plugins/inspector/descriptors/ApplicationDescriptor.java new file mode 100644 index 000000000..00f83be34 --- /dev/null +++ b/android/plugins/inspector/descriptors/ApplicationDescriptor.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.ApplicationWrapper; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class ApplicationDescriptor extends NodeDescriptor { + + private class NodeKey { + private int[] mKey; + + boolean set(ApplicationWrapper node) { + final List roots = node.getViewRoots(); + final int childCount = roots.size(); + final int[] key = new int[childCount]; + + for (int i = 0; i < childCount; i++) { + final View child = roots.get(i); + key[i] = System.identityHashCode(child); + } + + boolean changed = false; + if (mKey == null) { + changed = true; + } else if (mKey.length != key.length) { + changed = true; + } else { + for (int i = 0; i < childCount; i++) { + if (mKey[i] != key[i]) { + changed = true; + break; + } + } + } + + mKey = key; + return changed; + } + } + + @Override + public void init(final ApplicationWrapper node) { + node.setListener( + new ApplicationWrapper.ActivityStackChangedListener() { + @Override + public void onActivityStackChanged(List stack) { + invalidate(node); + } + }); + + final NodeKey key = new NodeKey(); + final Runnable maybeInvalidate = + new NodeDescriptor.ErrorReportingRunnable() { + @Override + public void runOrThrow() throws Exception { + if (connected()) { + if (key.set(node)) { + invalidate(node); + } + node.postDelayed(this, 1000); + } + } + }; + + node.postDelayed(maybeInvalidate, 1000); + } + + @Override + public String getId(ApplicationWrapper node) { + return node.getApplication().getPackageName(); + } + + @Override + public String getName(ApplicationWrapper node) { + return node.getApplication().getPackageName(); + } + + @Override + public int getChildCount(ApplicationWrapper node) { + return node.getViewRoots().size(); + } + + @Override + public Object getChildAt(ApplicationWrapper node, int index) { + final View view = node.getViewRoots().get(index); + + for (Activity activity : node.getActivityStack()) { + if (activity.getWindow().getDecorView() == view) { + return activity; + } + } + + return view; + } + + @Override + public List> getData(ApplicationWrapper node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setValue(ApplicationWrapper node, String[] path, SonarDynamic value) {} + + @Override + public List> getAttributes(ApplicationWrapper node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setHighlighted(ApplicationWrapper node, boolean selected) throws Exception { + final int childCount = getChildCount(node); + if (childCount > 0) { + final Object topChild = getChildAt(node, childCount - 1); + final NodeDescriptor descriptor = descriptorForClass(topChild.getClass()); + descriptor.setHighlighted(topChild, selected); + } + } + + @Override + public void hitTest(ApplicationWrapper node, Touch touch) { + final int childCount = getChildCount(node); + + for (int i = childCount - 1; i >= 0; i--) { + final Object child = getChildAt(node, i); + if (child instanceof Activity || child instanceof ViewGroup) { + touch.continueWithOffset(i, 0, 0); + return; + } + } + + touch.finish(); + } + + @Override + public @Nullable String getDecoration(ApplicationWrapper obj) { + return null; + } + + @Override + public boolean matches(String query, ApplicationWrapper node) throws Exception { + NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/descriptors/DialogDescriptor.java b/android/plugins/inspector/descriptors/DialogDescriptor.java new file mode 100644 index 000000000..4e16cc21a --- /dev/null +++ b/android/plugins/inspector/descriptors/DialogDescriptor.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import android.app.Dialog; +import android.view.Window; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class DialogDescriptor extends NodeDescriptor { + + @Override + public void init(Dialog node) {} + + @Override + public String getId(Dialog node) { + return Integer.toString(System.identityHashCode(node)); + } + + @Override + public String getName(Dialog node) { + return node.getClass().getSimpleName(); + } + + @Override + public int getChildCount(Dialog node) { + return node.getWindow() == null ? 0 : 1; + } + + @Override + public Object getChildAt(Dialog node, int index) { + return node.getWindow(); + } + + @Override + public List> getData(Dialog node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setValue(Dialog node, String[] path, SonarDynamic value) {} + + @Override + public List> getAttributes(Dialog node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setHighlighted(Dialog node, boolean selected) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Window.class); + descriptor.setHighlighted(node.getWindow(), selected); + } + + @Override + public void hitTest(Dialog node, Touch touch) { + touch.continueWithOffset(0, 0, 0); + } + + @Override + public @Nullable String getDecoration(Dialog obj) { + return null; + } + + @Override + public boolean matches(String query, Dialog node) throws Exception { + NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/descriptors/DialogFragmentDescriptor.java b/android/plugins/inspector/descriptors/DialogFragmentDescriptor.java new file mode 100644 index 000000000..fbc6d04a9 --- /dev/null +++ b/android/plugins/inspector/descriptors/DialogFragmentDescriptor.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.Fragment; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import java.util.List; +import javax.annotation.Nullable; + +public class DialogFragmentDescriptor extends NodeDescriptor { + + @Override + public void init(DialogFragment node) {} + + @Override + public String getId(DialogFragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + return descriptor.getId(node); + } + + @Override + public String getName(DialogFragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + return descriptor.getName(node); + } + + @Override + public int getChildCount(DialogFragment node) { + return node.getDialog() == null ? 0 : 1; + } + + @Override + public Object getChildAt(DialogFragment node, int index) { + return node.getDialog(); + } + + @Override + public List> getData(DialogFragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + return descriptor.getData(node); + } + + @Override + public void setValue(DialogFragment node, String[] path, SonarDynamic value) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + descriptor.setValue(node, path, value); + } + + @Override + public List> getAttributes(DialogFragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + return descriptor.getAttributes(node); + } + + @Override + public void setHighlighted(DialogFragment node, boolean selected) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Dialog.class); + if (node.getDialog() != null) { + descriptor.setHighlighted(node.getDialog(), selected); + } + } + + @Override + public void hitTest(DialogFragment node, Touch touch) { + touch.continueWithOffset(0, 0, 0); + } + + @Override + public @Nullable String getDecoration(DialogFragment obj) { + return null; + } + + @Override + public boolean matches(String query, DialogFragment node) throws Exception { + NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/descriptors/DrawableDescriptor.java b/android/plugins/inspector/descriptors/DrawableDescriptor.java new file mode 100644 index 000000000..eaba20d4b --- /dev/null +++ b/android/plugins/inspector/descriptors/DrawableDescriptor.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.View; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.HighlightedOverlay; +import com.facebook.sonar.plugins.inspector.InspectorValue; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class DrawableDescriptor extends NodeDescriptor { + + @Override + public void init(Drawable node) {} + + @Override + public String getId(Drawable node) { + return Integer.toString(System.identityHashCode(node)); + } + + @Override + public String getName(Drawable node) { + return node.getClass().getSimpleName(); + } + + @Override + public int getChildCount(Drawable node) { + return 0; + } + + @Override + public @Nullable Object getChildAt(Drawable node, int index) { + return null; + } + + @Override + public List> getData(Drawable node) { + final SonarObject.Builder props = new SonarObject.Builder(); + final Rect bounds = node.getBounds(); + + props.put("left", InspectorValue.mutable(bounds.left)); + props.put("top", InspectorValue.mutable(bounds.top)); + props.put("right", InspectorValue.mutable(bounds.right)); + props.put("bottom", InspectorValue.mutable(bounds.bottom)); + + if (hasAlphaSupport()) { + props.put("alpha", InspectorValue.mutable(node.getAlpha())); + } + + return Arrays.asList(new Named<>("Drawable", props.build())); + } + + @Override + public void setValue(Drawable node, String[] path, SonarDynamic value) { + final Rect bounds = node.getBounds(); + + switch (path[0]) { + case "Drawable": + switch (path[1]) { + case "left": + bounds.left = value.asInt(); + node.setBounds(bounds); + break; + case "top": + bounds.top = value.asInt(); + node.setBounds(bounds); + break; + case "right": + bounds.right = value.asInt(); + node.setBounds(bounds); + break; + case "bottom": + bounds.bottom = value.asInt(); + node.setBounds(bounds); + break; + case "alpha": + node.setAlpha(value.asInt()); + break; + } + break; + } + } + + private static boolean hasAlphaSupport() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + } + + @Override + public List> getAttributes(Drawable node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setHighlighted(Drawable node, boolean selected) { + // Ensure we handle wrapping drawable + Drawable.Callback callbacks = node.getCallback(); + while (callbacks instanceof Drawable) { + callbacks = ((Drawable) callbacks).getCallback(); + } + + if (!(callbacks instanceof View)) { + return; + } + + final View callbackView = (View) callbacks; + if (selected) { + final Rect zero = new Rect(); + final Rect bounds = node.getBounds(); + HighlightedOverlay.setHighlighted(callbackView, zero, zero, bounds); + } else { + HighlightedOverlay.removeHighlight(callbackView); + } + } + + @Override + public void hitTest(Drawable node, Touch touch) { + touch.finish(); + } + + @Override + public @Nullable String getDecoration(Drawable obj) { + return null; + } + + @Override + public boolean matches(String query, Drawable node) throws Exception { + NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/descriptors/FragmentDescriptor.java b/android/plugins/inspector/descriptors/FragmentDescriptor.java new file mode 100644 index 000000000..5f05bdb26 --- /dev/null +++ b/android/plugins/inspector/descriptors/FragmentDescriptor.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.View; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import com.facebook.stetho.common.android.ResourcesUtil; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class FragmentDescriptor extends NodeDescriptor { + + @Override + public void init(Fragment node) {} + + @Override + public String getId(Fragment node) { + return Integer.toString(System.identityHashCode(node)); + } + + @Override + public String getName(Fragment node) { + return node.getClass().getSimpleName(); + } + + @Override + public int getChildCount(Fragment node) { + return node.getView() == null ? 0 : 1; + } + + @Override + public Object getChildAt(Fragment node, int index) { + return node.getView(); + } + + @Override + public List> getData(Fragment node) { + final Bundle args = node.getArguments(); + if (args == null || args.isEmpty()) { + return Collections.EMPTY_LIST; + } + + final SonarObject.Builder bundle = new SonarObject.Builder(); + + for (String key : args.keySet()) { + bundle.put(key, args.get(key)); + } + + return Arrays.asList(new Named<>("Arguments", bundle.build())); + } + + @Override + public void setValue(Fragment node, String[] path, SonarDynamic value) {} + + @Override + public List> getAttributes(Fragment node) { + final String resourceId = getResourceId(node); + + if (resourceId == null) { + return Collections.EMPTY_LIST; + } + + return Arrays.asList(new Named<>("id", resourceId)); + } + + @Nullable + private static String getResourceId(Fragment node) { + final int id = node.getId(); + + if (id == View.NO_ID || node.getHost() == null) { + return null; + } + + return ResourcesUtil.getIdStringQuietly(node.getContext(), node.getResources(), id); + } + + @Override + public void setHighlighted(Fragment node, boolean selected) throws Exception { + if (node.getView() == null) { + return; + } + + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.setHighlighted(node.getView(), selected); + } + + @Override + public void hitTest(Fragment node, Touch touch) { + touch.continueWithOffset(0, 0, 0); + } + + @Override + public @Nullable String getDecoration(Fragment obj) { + return null; + } + + @Override + public boolean matches(String query, Fragment node) throws Exception { + final String resourceId = getResourceId(node); + + if (resourceId != null) { + if (resourceId.toLowerCase().contains(query)) { + return true; + } + } + + final NodeDescriptor objectDescriptor = descriptorForClass(Object.class); + return objectDescriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/descriptors/ObjectDescriptor.java b/android/plugins/inspector/descriptors/ObjectDescriptor.java new file mode 100644 index 000000000..8a8d42fec --- /dev/null +++ b/android/plugins/inspector/descriptors/ObjectDescriptor.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class ObjectDescriptor extends NodeDescriptor { + + @Override + public void init(Object node) {} + + @Override + public String getId(Object node) { + return Integer.toString(System.identityHashCode(node)); + } + + @Override + public String getName(Object node) { + return node.getClass().getName(); + } + + @Override + public int getChildCount(Object node) { + return 0; + } + + @Override + public @Nullable Object getChildAt(Object node, int index) { + return null; + } + + @Override + public List> getData(Object node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setValue(Object node, String[] path, SonarDynamic value) {} + + @Override + public List> getAttributes(Object node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setHighlighted(Object node, boolean selected) {} + + @Override + public void hitTest(Object node, Touch touch) { + touch.finish(); + } + + @Override + public @Nullable String getDecoration(Object obj) { + return null; + } + + @Override + public boolean matches(String query, Object node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(node.getClass()); + final List> attributes = descriptor.getAttributes(node); + for (Named namedString : attributes) { + if (namedString.getName().equals("id")) { + if (namedString.getValue().toLowerCase().contains(query)) { + return true; + } + } + } + + return descriptor.getName(node).toLowerCase().contains(query); + } +} diff --git a/android/plugins/inspector/descriptors/SupportDialogFragmentDescriptor.java b/android/plugins/inspector/descriptors/SupportDialogFragmentDescriptor.java new file mode 100644 index 000000000..71ccb8496 --- /dev/null +++ b/android/plugins/inspector/descriptors/SupportDialogFragmentDescriptor.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import android.app.Dialog; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import java.util.List; +import javax.annotation.Nullable; + +public class SupportDialogFragmentDescriptor extends NodeDescriptor { + + @Override + public void init(DialogFragment node) {} + + @Override + public String getId(DialogFragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + return descriptor.getId(node); + } + + @Override + public String getName(DialogFragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + return descriptor.getName(node); + } + + @Override + public int getChildCount(DialogFragment node) { + return node.getDialog() == null ? 0 : 1; + } + + @Override + public Object getChildAt(DialogFragment node, int index) { + return node.getDialog(); + } + + @Override + public List> getData(DialogFragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + return descriptor.getData(node); + } + + @Override + public void setValue(DialogFragment node, String[] path, SonarDynamic value) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + descriptor.setValue(node, path, value); + } + + @Override + public List> getAttributes(DialogFragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Fragment.class); + return descriptor.getAttributes(node); + } + + @Override + public void setHighlighted(DialogFragment node, boolean selected) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Dialog.class); + if (node.getDialog() != null) { + descriptor.setHighlighted(node.getDialog(), selected); + } + } + + @Override + public void hitTest(DialogFragment node, Touch touch) { + touch.continueWithOffset(0, 0, 0); + } + + @Override + public @Nullable String getDecoration(DialogFragment obj) { + return null; + } + + @Override + public boolean matches(String query, DialogFragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/descriptors/SupportFragmentDescriptor.java b/android/plugins/inspector/descriptors/SupportFragmentDescriptor.java new file mode 100644 index 000000000..7af9e65ee --- /dev/null +++ b/android/plugins/inspector/descriptors/SupportFragmentDescriptor.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.View; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import com.facebook.stetho.common.android.ResourcesUtil; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class SupportFragmentDescriptor extends NodeDescriptor { + + @Override + public void init(Fragment node) {} + + @Override + public String getId(Fragment node) { + return Integer.toString(System.identityHashCode(node)); + } + + @Override + public String getName(Fragment node) { + return node.getClass().getSimpleName(); + } + + @Override + public int getChildCount(Fragment node) { + return node.getView() == null ? 0 : 1; + } + + @Override + public @Nullable Object getChildAt(Fragment node, int index) { + return node.getView(); + } + + @Override + public List> getData(Fragment node) { + final Bundle args = node.getArguments(); + if (args == null || args.isEmpty()) { + return Collections.EMPTY_LIST; + } + + final SonarObject.Builder bundle = new SonarObject.Builder(); + + for (String key : args.keySet()) { + bundle.put(key, args.get(key)); + } + + return Arrays.asList(new Named<>("Arguments", bundle.build())); + } + + @Override + public void setValue(Fragment node, String[] path, SonarDynamic value) {} + + @Override + public List> getAttributes(Fragment node) { + final int id = node.getId(); + if (id == View.NO_ID || node.getHost() == null) { + return Collections.EMPTY_LIST; + } + + return Arrays.asList( + new Named<>( + "id", ResourcesUtil.getIdStringQuietly(node.getContext(), node.getResources(), id))); + } + + @Override + public void setHighlighted(Fragment node, boolean selected) throws Exception { + if (node.getView() == null) { + return; + } + + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.setHighlighted(node.getView(), selected); + } + + @Override + public void hitTest(Fragment node, Touch touch) { + touch.continueWithOffset(0, 0, 0); + } + + @Override + public @Nullable String getDecoration(Fragment obj) { + return null; + } + + @Override + public boolean matches(String query, Fragment node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/descriptors/TextViewDescriptor.java b/android/plugins/inspector/descriptors/TextViewDescriptor.java new file mode 100644 index 000000000..4426259b5 --- /dev/null +++ b/android/plugins/inspector/descriptors/TextViewDescriptor.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Color; +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Number; +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Text; + +import android.view.View; +import android.widget.TextView; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.InspectorValue; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +public class TextViewDescriptor extends NodeDescriptor { + + @Override + public void init(TextView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.init(node); + } + + @Override + public String getId(TextView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + return descriptor.getId(node); + } + + @Override + public String getName(TextView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + return descriptor.getName(node); + } + + @Override + public int getChildCount(TextView node) { + return 0; + } + + @Override + public @Nullable Object getChildAt(TextView node, int index) { + return null; + } + + @Override + public List> getData(TextView node) throws Exception { + final List> props = new ArrayList<>(); + final NodeDescriptor descriptor = descriptorForClass(View.class); + + props.add( + 0, + new Named<>( + "TextView", + new SonarObject.Builder() + .put("text", InspectorValue.mutable(Text, node.getText().toString())) + .put( + "textColor", + InspectorValue.mutable(Color, node.getTextColors().getDefaultColor())) + .put("textSize", InspectorValue.mutable(Number, node.getTextSize())) + .build())); + + props.addAll(descriptor.getData(node)); + + return props; + } + + @Override + public void setValue(TextView node, String[] path, SonarDynamic value) throws Exception { + switch (path[0]) { + case "TextView": + switch (path[1]) { + case "text": + node.setText(value.asString()); + break; + case "textColor": + node.setTextColor(value.asInt()); + break; + case "textSize": + node.setTextSize(value.asInt()); + break; + } + break; + default: + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.setValue(node, path, value); + break; + } + invalidate(node); + } + + @Override + public List> getAttributes(TextView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + return descriptor.getAttributes(node); + } + + @Override + public void setHighlighted(TextView node, boolean selected) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.setHighlighted(node, selected); + } + + @Override + public void hitTest(TextView node, Touch touch) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.hitTest(node, touch); + } + + @Override + public @Nullable String getDecoration(TextView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + return descriptor.getDecoration(node); + } + + @Override + public boolean matches(String query, TextView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/descriptors/ViewDescriptor.java b/android/plugins/inspector/descriptors/ViewDescriptor.java new file mode 100644 index 000000000..b6883ace2 --- /dev/null +++ b/android/plugins/inspector/descriptors/ViewDescriptor.java @@ -0,0 +1,708 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Color; +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Enum; + +import android.annotation.TargetApi; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.view.MarginLayoutParamsCompat; +import android.support.v4.view.ViewCompat; +import android.util.SparseArray; +import android.view.Gravity; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewParent; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.HighlightedOverlay; +import com.facebook.sonar.plugins.inspector.InspectorValue; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import com.facebook.sonar.plugins.inspector.descriptors.utils.AccessibilityUtil; +import com.facebook.sonar.plugins.inspector.descriptors.utils.EnumMapping; +import com.facebook.stetho.common.android.ResourcesUtil; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nullable; + +public class ViewDescriptor extends NodeDescriptor { + + private static Field sKeyedTagsField; + private static Field sListenerInfoField; + private static Field sOnClickListenerField; + + static { + try { + sKeyedTagsField = View.class.getDeclaredField("mKeyedTags"); + sKeyedTagsField.setAccessible(true); + + sListenerInfoField = View.class.getDeclaredField("mListenerInfo"); + sListenerInfoField.setAccessible(true); + + final String viewInfoClassName = View.class.getName() + "$ListenerInfo"; + sOnClickListenerField = Class.forName(viewInfoClassName).getDeclaredField("mOnClickListener"); + sOnClickListenerField.setAccessible(true); + } catch (Exception ignored) { + } + } + + @Override + public void init(final View node) {} + + @Override + public String getId(View node) { + return Integer.toString(System.identityHashCode(node)); + } + + @Override + public String getName(View node) { + return node.getClass().getSimpleName(); + } + + @Override + public int getChildCount(View node) { + return 0; + } + + @Override + public @Nullable Object getChildAt(View node, int index) { + return null; + } + + @Override + public List> getData(View node) { + final SonarObject.Builder viewProps = + new SonarObject.Builder() + .put("height", InspectorValue.mutable(node.getHeight())) + .put("width", InspectorValue.mutable(node.getWidth())) + .put("alpha", InspectorValue.mutable(node.getAlpha())) + .put("visibility", sVisibilityMapping.get(node.getVisibility())) + .put("background", fromDrawable(node.getBackground())) + .put("tag", InspectorValue.mutable(node.getTag())) + .put("keyedTags", getTags(node)) + .put("layoutParams", getLayoutParams(node)) + .put( + "state", + new SonarObject.Builder() + .put("enabled", InspectorValue.mutable(node.isEnabled())) + .put("activated", InspectorValue.mutable(node.isActivated())) + .put("focused", node.isFocused()) + .put("selected", InspectorValue.mutable(node.isSelected()))) + .put( + "bounds", + new SonarObject.Builder() + .put("left", InspectorValue.mutable(node.getLeft())) + .put("right", InspectorValue.mutable(node.getRight())) + .put("top", InspectorValue.mutable(node.getTop())) + .put("bottom", InspectorValue.mutable(node.getBottom()))) + .put( + "padding", + new SonarObject.Builder() + .put("left", InspectorValue.mutable(node.getPaddingLeft())) + .put("top", InspectorValue.mutable(node.getPaddingTop())) + .put("right", InspectorValue.mutable(node.getPaddingRight())) + .put("bottom", InspectorValue.mutable(node.getPaddingBottom()))) + .put( + "rotation", + new SonarObject.Builder() + .put("x", InspectorValue.mutable(node.getRotationX())) + .put("y", InspectorValue.mutable(node.getRotationY())) + .put("z", InspectorValue.mutable(node.getRotation()))) + .put( + "scale", + new SonarObject.Builder() + .put("x", InspectorValue.mutable(node.getScaleX())) + .put("y", InspectorValue.mutable(node.getScaleY()))) + .put( + "pivot", + new SonarObject.Builder() + .put("x", InspectorValue.mutable(node.getPivotX())) + .put("y", InspectorValue.mutable(node.getPivotY()))); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + viewProps + .put("layoutDirection", sLayoutDirectionMapping.get(node.getLayoutDirection())) + .put("textDirection", sTextDirectionMapping.get(node.getTextDirection())) + .put("textAlignment", sTextAlignmentMapping.get(node.getTextAlignment())); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + viewProps.put("elevation", InspectorValue.mutable(node.getElevation())); + } + + SonarObject.Builder translation = + new SonarObject.Builder() + .put("x", InspectorValue.mutable(node.getTranslationX())) + .put("y", InspectorValue.mutable(node.getTranslationY())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + translation.put("z", InspectorValue.mutable(node.getTranslationZ())); + } + viewProps.put("translation", translation); + + SonarObject.Builder position = + new SonarObject.Builder() + .put("x", InspectorValue.mutable(node.getX())) + .put("y", InspectorValue.mutable(node.getY())); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + position.put("z", InspectorValue.mutable(node.getZ())); + } + viewProps.put("position", position); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + viewProps.put("foreground", fromDrawable(node.getForeground())); + } + + return Arrays.asList( + new Named<>("View", viewProps.build()), + new Named<>("Accessibility", getAccessibilityData(node))); + } + + private static SonarObject getAccessibilityData(View view) { + final SonarObject.Builder accessibilityProps = new SonarObject.Builder(); + + // This needs to be an empty string to be mutable. See t20470623. + CharSequence contentDescription = + view.getContentDescription() != null ? view.getContentDescription() : ""; + accessibilityProps.put("content-description", InspectorValue.mutable(contentDescription)); + accessibilityProps.put("focusable", InspectorValue.mutable(view.isFocusable())); + accessibilityProps.put("node-info", AccessibilityUtil.getAccessibilityNodeInfoProperties(view)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + accessibilityProps.put( + "important-for-accessibility", + AccessibilityUtil.sImportantForAccessibilityMapping.get( + view.getImportantForAccessibility())); + } + + AccessibilityUtil.addTalkbackProperties(accessibilityProps, view); + + return accessibilityProps.build(); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + @Override + public void setValue(View node, String[] path, SonarDynamic value) { + if (path[0].equals("Accessibility")) { + setAccessibilityValue(node, path, value); + } + + if (!path[0].equals("View")) { + return; + } + + switch (path[1]) { + case "elevation": + node.setElevation(value.asFloat()); + break; + case "alpha": + node.setAlpha(value.asFloat()); + break; + case "visibility": + node.setVisibility(sVisibilityMapping.get(value.asString())); + break; + case "layoutParams": + setLayoutParams(node, Arrays.copyOfRange(path, 1, path.length), value); + break; + case "layoutDirection": + node.setLayoutDirection(sLayoutDirectionMapping.get(value.asString())); + break; + case "textDirection": + node.setTextDirection(sTextDirectionMapping.get(value.asString())); + break; + case "textAlignment": + node.setTextAlignment(sTextAlignmentMapping.get(value.asString())); + break; + case "background": + node.setBackground(new ColorDrawable(value.asInt())); + break; + case "foreground": + node.setForeground(new ColorDrawable(value.asInt())); + break; + case "state": + switch (path[2]) { + case "enabled": + node.setEnabled(value.asBoolean()); + break; + case "activated": + node.setActivated(value.asBoolean()); + break; + case "selected": + node.setSelected(value.asBoolean()); + break; + } + break; + case "bounds": + switch (path[2]) { + case "left": + node.setLeft(value.asInt()); + break; + case "top": + node.setTop(value.asInt()); + break; + case "right": + node.setRight(value.asInt()); + break; + case "bottom": + node.setBottom(value.asInt()); + break; + } + break; + case "padding": + switch (path[2]) { + case "left": + node.setPadding( + value.asInt(), + node.getPaddingTop(), + node.getPaddingRight(), + node.getPaddingBottom()); + break; + case "top": + node.setPadding( + node.getPaddingLeft(), + value.asInt(), + node.getPaddingRight(), + node.getPaddingBottom()); + break; + case "right": + node.setPadding( + node.getPaddingLeft(), + node.getPaddingTop(), + value.asInt(), + node.getPaddingBottom()); + break; + case "bottom": + node.setPadding( + node.getPaddingLeft(), node.getPaddingTop(), node.getPaddingRight(), value.asInt()); + break; + } + break; + case "rotation": + switch (path[2]) { + case "x": + node.setRotationX(value.asFloat()); + break; + case "y": + node.setRotationY(value.asFloat()); + break; + case "z": + node.setRotation(value.asFloat()); + break; + } + break; + case "translation": + switch (path[2]) { + case "x": + node.setTranslationX(value.asFloat()); + break; + case "y": + node.setTranslationY(value.asFloat()); + break; + case "z": + node.setTranslationZ(value.asFloat()); + break; + } + break; + case "position": + switch (path[2]) { + case "x": + node.setX(value.asFloat()); + break; + case "y": + node.setY(value.asFloat()); + break; + case "z": + node.setZ(value.asFloat()); + break; + } + break; + case "scale": + switch (path[2]) { + case "x": + node.setScaleX(value.asFloat()); + break; + case "y": + node.setScaleY(value.asFloat()); + break; + } + break; + case "pivot": + switch (path[2]) { + case "x": + node.setPivotY(value.asFloat()); + break; + case "y": + node.setPivotX(value.asFloat()); + break; + } + break; + case "width": + LayoutParams lpw = node.getLayoutParams(); + lpw.width = value.asInt(); + node.setLayoutParams(lpw); + break; + case "height": + LayoutParams lph = node.getLayoutParams(); + lph.height = value.asInt(); + node.setLayoutParams(lph); + break; + } + + invalidate(node); + } + + private void setAccessibilityValue(View node, String[] path, SonarDynamic value) { + switch (path[1]) { + case "focusable": + node.setFocusable(value.asBoolean()); + break; + case "important-for-accessibility": + node.setImportantForAccessibility( + AccessibilityUtil.sImportantForAccessibilityMapping.get(value.asString())); + break; + case "content-description": + node.setContentDescription(value.asString()); + break; + } + invalidate(node); + } + + @Override + public List> getAttributes(View node) throws Exception { + final List> attributes = new ArrayList<>(); + + final String resourceId = getResourceId(node); + if (resourceId != null) { + attributes.add(new Named<>("id", resourceId)); + } + + if (sListenerInfoField != null && sOnClickListenerField != null) { + final Object listenerInfo = sListenerInfoField.get(node); + if (listenerInfo != null) { + final OnClickListener clickListener = + (OnClickListener) sOnClickListenerField.get(listenerInfo); + if (clickListener != null) { + attributes.add(new Named<>("onClick", clickListener.getClass().getName())); + } + } + } + + return attributes; + } + + @Nullable + private static String getResourceId(View node) { + final int id = node.getId(); + + if (id == View.NO_ID) { + return null; + } + + return ResourcesUtil.getIdStringQuietly(node.getContext(), node.getResources(), id); + } + + @Override + public void setHighlighted(View node, boolean selected) { + // We need to figure out whether the given View has a parent View since margins are not + // included within a View's bounds. So, in order to display the margin values for a particular + // view, we need to apply an overlay on its parent rather than itself. + final View targetView; + final ViewParent parent = node.getParent(); + if (parent instanceof View) { + targetView = (View) parent; + } else { + targetView = node; + } + + if (!selected) { + HighlightedOverlay.removeHighlight(targetView); + return; + } + + final Rect padding = + new Rect( + ViewCompat.getPaddingStart(node), + node.getPaddingTop(), + ViewCompat.getPaddingEnd(node), + node.getPaddingBottom()); + + final Rect margin; + final ViewGroup.LayoutParams params = node.getLayoutParams(); + if (params instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) params; + margin = + new Rect( + MarginLayoutParamsCompat.getMarginStart(marginParams), + marginParams.topMargin, + MarginLayoutParamsCompat.getMarginEnd(marginParams), + marginParams.bottomMargin); + } else { + margin = new Rect(); + } + + final int left = node.getLeft(); + final int top = node.getTop(); + final Rect contentBounds = new Rect(left, top, left + node.getWidth(), top + node.getHeight()); + if (targetView == node) { + // If the View doesn't have a parent View that we're applying the overlay to, then + // we need to ensure that it is aligned to 0, 0, rather than its relative location to its + // parent + contentBounds.offset(-left, -top); + } + + HighlightedOverlay.setHighlighted(targetView, margin, padding, contentBounds); + } + + @Override + public void hitTest(View node, Touch touch) { + touch.finish(); + } + + @Override + public @Nullable String getDecoration(View obj) { + return null; + } + + @Override + public boolean matches(String query, View node) throws Exception { + final String resourceId = getResourceId(node); + + if (resourceId != null && resourceId.toLowerCase().contains(query)) { + return true; + } + + final NodeDescriptor objectDescriptor = descriptorForClass(Object.class); + return objectDescriptor.matches(query, node); + } + + private SonarObject getTags(final View node) { + final SonarObject.Builder tags = new SonarObject.Builder(); + if (sKeyedTagsField == null) { + return tags.build(); + } + + new ErrorReportingRunnable() { + @Override + protected void runOrThrow() throws Exception { + final SparseArray keyedTags = (SparseArray) sKeyedTagsField.get(node); + if (keyedTags != null) { + for (int i = 0, count = keyedTags.size(); i < count; i++) { + final String id = + ResourcesUtil.getIdStringQuietly( + node.getContext(), node.getResources(), keyedTags.keyAt(i)); + tags.put(id, keyedTags.valueAt(i)); + } + } + } + }.run(); + + return tags.build(); + } + + private static InspectorValue fromDrawable(Drawable d) { + if (d instanceof ColorDrawable) { + return InspectorValue.mutable(Color, ((ColorDrawable) d).getColor()); + } + return InspectorValue.mutable(Color, 0); + } + + private static SonarObject getLayoutParams(View node) { + final LayoutParams layoutParams = node.getLayoutParams(); + final SonarObject.Builder params = new SonarObject.Builder(); + + params.put("width", fromSize(layoutParams.width)); + params.put("height", fromSize(layoutParams.height)); + + if (layoutParams instanceof MarginLayoutParams) { + final MarginLayoutParams marginLayoutParams = (MarginLayoutParams) layoutParams; + params.put( + "margin", + new SonarObject.Builder() + .put("left", InspectorValue.mutable(marginLayoutParams.leftMargin)) + .put("top", InspectorValue.mutable(marginLayoutParams.topMargin)) + .put("right", InspectorValue.mutable(marginLayoutParams.rightMargin)) + .put("bottom", InspectorValue.mutable(marginLayoutParams.bottomMargin))); + } + + if (layoutParams instanceof FrameLayout.LayoutParams) { + final FrameLayout.LayoutParams frameLayoutParams = (FrameLayout.LayoutParams) layoutParams; + params.put("gravity", sGravityMapping.get(frameLayoutParams.gravity)); + } + + if (layoutParams instanceof LinearLayout.LayoutParams) { + final LinearLayout.LayoutParams linearLayoutParams = (LinearLayout.LayoutParams) layoutParams; + params + .put("weight", InspectorValue.mutable(linearLayoutParams.weight)) + .put("gravity", sGravityMapping.get(linearLayoutParams.gravity)); + } + + return params.build(); + } + + private void setLayoutParams(View node, String[] path, SonarDynamic value) { + final LayoutParams params = node.getLayoutParams(); + + switch (path[0]) { + case "width": + params.width = toSize(value.asString()); + break; + case "height": + params.height = toSize(value.asString()); + break; + case "weight": + final LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) params; + linearParams.weight = value.asFloat(); + break; + } + + if (params instanceof MarginLayoutParams) { + final MarginLayoutParams marginParams = (MarginLayoutParams) params; + + switch (path[0]) { + case "margin": + switch (path[1]) { + case "left": + marginParams.leftMargin = value.asInt(); + break; + case "top": + marginParams.topMargin = value.asInt(); + break; + case "right": + marginParams.rightMargin = value.asInt(); + break; + case "bottom": + marginParams.bottomMargin = value.asInt(); + break; + } + break; + } + } + + if (params instanceof FrameLayout.LayoutParams) { + final FrameLayout.LayoutParams frameLayoutParams = (FrameLayout.LayoutParams) params; + + switch (path[0]) { + case "gravity": + frameLayoutParams.gravity = sGravityMapping.get(value.asString()); + break; + } + } + + if (params instanceof LinearLayout.LayoutParams) { + final LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) params; + + switch (path[0]) { + case "weight": + linearParams.weight = value.asFloat(); + break; + case "gravity": + linearParams.gravity = sGravityMapping.get(value.asString()); + break; + } + } + + node.setLayoutParams(params); + } + + private static InspectorValue fromSize(int size) { + switch (size) { + case LayoutParams.WRAP_CONTENT: + return InspectorValue.mutable(Enum, "WRAP_CONTENT"); + case LayoutParams.MATCH_PARENT: + return InspectorValue.mutable(Enum, "MATCH_PARENT"); + default: + return InspectorValue.mutable(Enum, Integer.toString(size)); + } + } + + private static int toSize(String size) { + switch (size) { + case "WRAP_CONTENT": + return LayoutParams.WRAP_CONTENT; + case "MATCH_PARENT": + return LayoutParams.MATCH_PARENT; + default: + return Integer.parseInt(size); + } + } + + private static final EnumMapping sVisibilityMapping = + new EnumMapping("VISIBLE") { + { + put("VISIBLE", View.VISIBLE); + put("INVISIBLE", View.INVISIBLE); + put("GONE", View.GONE); + } + }; + + private static final EnumMapping sLayoutDirectionMapping = + new EnumMapping("LAYOUT_DIRECTION_INHERIT") { + { + put("LAYOUT_DIRECTION_INHERIT", View.LAYOUT_DIRECTION_INHERIT); + put("LAYOUT_DIRECTION_LOCALE", View.LAYOUT_DIRECTION_LOCALE); + put("LAYOUT_DIRECTION_LTR", View.LAYOUT_DIRECTION_LTR); + put("LAYOUT_DIRECTION_RTL", View.LAYOUT_DIRECTION_RTL); + } + }; + + private static final EnumMapping sTextDirectionMapping = + new EnumMapping("TEXT_DIRECTION_INHERIT") { + { + put("TEXT_DIRECTION_INHERIT", View.TEXT_DIRECTION_INHERIT); + put("TEXT_DIRECTION_FIRST_STRONG", View.TEXT_DIRECTION_FIRST_STRONG); + put("TEXT_DIRECTION_ANY_RTL", View.TEXT_DIRECTION_ANY_RTL); + put("TEXT_DIRECTION_LTR", View.TEXT_DIRECTION_LTR); + put("TEXT_DIRECTION_RTL", View.TEXT_DIRECTION_RTL); + put("TEXT_DIRECTION_LOCALE", View.TEXT_DIRECTION_LOCALE); + put("TEXT_DIRECTION_FIRST_STRONG_LTR", View.TEXT_DIRECTION_FIRST_STRONG_LTR); + put("TEXT_DIRECTION_FIRST_STRONG_RTL", View.TEXT_DIRECTION_FIRST_STRONG_RTL); + } + }; + + private static final EnumMapping sTextAlignmentMapping = + new EnumMapping("TEXT_ALIGNMENT_INHERIT") { + { + put("TEXT_ALIGNMENT_INHERIT", View.TEXT_ALIGNMENT_INHERIT); + put("TEXT_ALIGNMENT_GRAVITY", View.TEXT_ALIGNMENT_GRAVITY); + put("TEXT_ALIGNMENT_TEXT_START", View.TEXT_ALIGNMENT_TEXT_START); + put("TEXT_ALIGNMENT_TEXT_END", View.TEXT_ALIGNMENT_TEXT_END); + put("TEXT_ALIGNMENT_CENTER", View.TEXT_ALIGNMENT_CENTER); + put("TEXT_ALIGNMENT_VIEW_START", View.TEXT_ALIGNMENT_VIEW_START); + put("TEXT_ALIGNMENT_VIEW_END", View.TEXT_ALIGNMENT_VIEW_END); + } + }; + + private static final EnumMapping sGravityMapping = + new EnumMapping("NO_GRAVITY") { + { + put("NO_GRAVITY", Gravity.NO_GRAVITY); + put("LEFT", Gravity.LEFT); + put("TOP", Gravity.TOP); + put("RIGHT", Gravity.RIGHT); + put("BOTTOM", Gravity.BOTTOM); + put("CENTER", Gravity.CENTER); + put("CENTER_VERTICAL", Gravity.CENTER_VERTICAL); + put("FILL_VERTICAL", Gravity.FILL_VERTICAL); + put("CENTER_HORIZONTAL", Gravity.CENTER_HORIZONTAL); + put("FILL_HORIZONTAL", Gravity.FILL_HORIZONTAL); + } + }; +} diff --git a/android/plugins/inspector/descriptors/ViewGroupDescriptor.java b/android/plugins/inspector/descriptors/ViewGroupDescriptor.java new file mode 100644 index 000000000..14a096397 --- /dev/null +++ b/android/plugins/inspector/descriptors/ViewGroupDescriptor.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import static android.support.v4.view.ViewGroupCompat.LAYOUT_MODE_CLIP_BOUNDS; +import static android.support.v4.view.ViewGroupCompat.LAYOUT_MODE_OPTICAL_BOUNDS; +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Boolean; +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Enum; + +import android.os.Build; +import android.view.View; +import android.view.ViewGroup; +import com.facebook.sonar.R; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.HiddenNode; +import com.facebook.sonar.plugins.inspector.InspectorValue; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import com.facebook.stetho.common.android.FragmentCompatUtil; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +public class ViewGroupDescriptor extends NodeDescriptor { + + private class NodeKey { + private int[] mKey; + + boolean set(ViewGroup node) { + final int childCount = node.getChildCount(); + final int[] key = new int[childCount]; + + for (int i = 0; i < childCount; i++) { + final View child = node.getChildAt(i); + key[i] = System.identityHashCode(child); + } + + boolean changed = false; + if (mKey == null) { + changed = true; + } else if (mKey.length != key.length) { + changed = true; + } else { + for (int i = 0; i < childCount; i++) { + if (mKey[i] != key[i]) { + changed = true; + break; + } + } + } + + mKey = key; + return changed; + } + } + + @Override + public void init(final ViewGroup node) { + final NodeKey key = new NodeKey(); + + final Runnable maybeInvalidate = + new ErrorReportingRunnable() { + @Override + public void runOrThrow() throws Exception { + if (connected()) { + if (key.set(node)) { + invalidate(node); + } + + final boolean hasAttachedToWindow = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + if (!hasAttachedToWindow || node.isAttachedToWindow()) { + node.postDelayed(this, 1000); + } + } + } + }; + + node.postDelayed(maybeInvalidate, 1000); + } + + @Override + public String getId(ViewGroup node) throws Exception { + NodeDescriptor descriptor = descriptorForClass(View.class); + return descriptor.getId(node); + } + + @Override + public String getName(ViewGroup node) throws Exception { + NodeDescriptor descriptor = descriptorForClass(View.class); + return descriptor.getName(node); + } + + @Override + public int getChildCount(ViewGroup node) { + int childCount = 0; + for (int i = 0, count = node.getChildCount(); i < count; i++) { + if (!(node.getChildAt(i) instanceof HiddenNode)) { + childCount++; + } + } + return childCount; + } + + @Override + public @Nullable Object getChildAt(ViewGroup node, int index) { + for (int i = 0, count = node.getChildCount(); i < count; i++) { + final View child = node.getChildAt(i); + if (child instanceof HiddenNode) { + continue; + } + + if (i >= index) { + final Object fragment = getAttachedFragmentForView(child); + if (fragment != null && !FragmentCompatUtil.isDialogFragment(fragment)) { + return fragment; + } + + return child; + } + } + return null; + } + + @Override + public List> getData(ViewGroup node) throws Exception { + final List> props = new ArrayList<>(); + final NodeDescriptor descriptor = descriptorForClass(View.class); + + final SonarObject.Builder vgProps = new SonarObject.Builder(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + vgProps + .put( + "layoutMode", + InspectorValue.mutable( + Enum, + node.getLayoutMode() == LAYOUT_MODE_CLIP_BOUNDS + ? "LAYOUT_MODE_CLIP_BOUNDS" + : "LAYOUT_MODE_OPTICAL_BOUNDS")) + .put("clipChildren", InspectorValue.mutable(Boolean, node.getClipChildren())); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + vgProps.put("clipToPadding", InspectorValue.mutable(Boolean, node.getClipToPadding())); + } + + props.add(0, new Named<>("ViewGroup", vgProps.build())); + + props.addAll(descriptor.getData(node)); + + return props; + } + + @Override + public void setValue(ViewGroup node, String[] path, SonarDynamic value) throws Exception { + switch (path[0]) { + case "ViewGroup": + switch (path[1]) { + case "layoutMode": + switch (value.asString()) { + case "LAYOUT_MODE_CLIP_BOUNDS": + node.setLayoutMode(LAYOUT_MODE_CLIP_BOUNDS); + break; + case "LAYOUT_MODE_OPTICAL_BOUNDS": + node.setLayoutMode(LAYOUT_MODE_OPTICAL_BOUNDS); + break; + default: + node.setLayoutMode(-1); + break; + } + break; + case "clipChildren": + node.setClipChildren(value.asBoolean()); + break; + case "clipToPadding": + node.setClipToPadding(value.asBoolean()); + break; + } + break; + default: + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.setValue(node, path, value); + break; + } + invalidate(node); + } + + @Override + public List> getAttributes(ViewGroup node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + return descriptor.getAttributes(node); + } + + @Override + public void setHighlighted(ViewGroup node, boolean selected) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.setHighlighted(node, selected); + } + + @Override + public void hitTest(ViewGroup node, Touch touch) { + for (int i = node.getChildCount() - 1; i >= 0; i--) { + final View child = node.getChildAt(i); + if (child instanceof HiddenNode + || child.getVisibility() != View.VISIBLE + || shouldSkip(child)) { + continue; + } + + final int scrollX = node.getScrollX(); + final int scrollY = node.getScrollY(); + + final int left = (child.getLeft() + (int) child.getTranslationX()) - scrollX; + final int top = (child.getTop() + (int) child.getTranslationY()) - scrollY; + final int right = (child.getRight() + (int) child.getTranslationX()) - scrollX; + final int bottom = (child.getBottom() + (int) child.getTranslationY()) - scrollY; + + final boolean hit = touch.containedIn(left, top, right, bottom); + + if (hit) { + touch.continueWithOffset(i, left, top); + return; + } + } + + touch.finish(); + } + + private static boolean shouldSkip(View view) { + Object tag = view.getTag(R.id.sonar_skip_view_traversal); + if (!(tag instanceof Boolean)) { + return false; + } + + return (Boolean) tag; + } + + @Override + public @Nullable String getDecoration(ViewGroup obj) { + return null; + } + + @Override + public boolean matches(String query, ViewGroup node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } + + private static Object getAttachedFragmentForView(View v) { + try { + final Object fragment = FragmentCompatUtil.findFragmentForView(v); + boolean added = false; + if (fragment instanceof android.app.Fragment) { + added = ((android.app.Fragment) fragment).isAdded(); + } else if (fragment instanceof android.support.v4.app.Fragment) { + added = ((android.support.v4.app.Fragment) fragment).isAdded(); + } + + return added ? fragment : null; + } catch (RuntimeException e) { + return null; + } + } +} diff --git a/android/plugins/inspector/descriptors/WindowDescriptor.java b/android/plugins/inspector/descriptors/WindowDescriptor.java new file mode 100644 index 000000000..6b070283f --- /dev/null +++ b/android/plugins/inspector/descriptors/WindowDescriptor.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors; + +import android.view.View; +import android.view.Window; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nullable; + +public class WindowDescriptor extends NodeDescriptor { + + @Override + public void init(Window node) {} + + @Override + public String getId(Window node) { + return Integer.toString(System.identityHashCode(node)); + } + + @Override + public String getName(Window node) { + return node.getClass().getSimpleName(); + } + + @Override + public int getChildCount(Window node) { + return 1; + } + + @Override + public Object getChildAt(Window node, int index) { + return node.getDecorView(); + } + + @Override + public List> getData(Window node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setValue(Window node, String[] path, SonarDynamic value) {} + + @Override + public List> getAttributes(Window node) { + return Collections.EMPTY_LIST; + } + + @Override + public void setHighlighted(Window node, boolean selected) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.setHighlighted(node.getDecorView(), selected); + } + + @Override + public void hitTest(Window node, Touch touch) { + touch.continueWithOffset(0, 0, 0); + } + + @Override + public @Nullable String getDecoration(Window obj) { + return null; + } + + @Override + public boolean matches(String query, Window node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/descriptors/utils/AccessibilityEvaluationUtil.java b/android/plugins/inspector/descriptors/utils/AccessibilityEvaluationUtil.java new file mode 100644 index 000000000..ddfa683b1 --- /dev/null +++ b/android/plugins/inspector/descriptors/utils/AccessibilityEvaluationUtil.java @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.inspector.descriptors.utils; + +import android.graphics.Rect; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import com.facebook.sonar.plugins.inspector.descriptors.utils.AccessibilityRoleUtil.AccessibilityRole; +import java.util.List; +import javax.annotation.Nullable; + +/** + * This class provides utility methods for determining certain accessibility properties of {@link + * View}s and {@link AccessibilityNodeInfoCompat}s. It is porting some of the checks from {@link + * com.googlecode.eyesfree.utils.AccessibilityNodeInfoUtils}, but has stripped many features which + * are unnecessary here. + */ +public class AccessibilityEvaluationUtil { + + private AccessibilityEvaluationUtil() {} + + /** + * Returns whether the specified node has text or a content description. + * + * @param node The node to check. + * @return {@code true} if the node has text. + */ + public static boolean hasText(@Nullable AccessibilityNodeInfoCompat node) { + return node != null + && node.getCollectionInfo() == null + && (!TextUtils.isEmpty(node.getText()) || !TextUtils.isEmpty(node.getContentDescription())); + } + + /** + * Returns whether the supplied {@link View} and {@link AccessibilityNodeInfoCompat} would produce + * spoken feedback if it were accessibility focused. NOTE: not all speaking nodes are focusable. + * + * @param view The {@link View} to evaluate + * @param node The {@link AccessibilityNodeInfoCompat} to evaluate + * @return {@code true} if it meets the criterion for producing spoken feedback + */ + public static boolean isSpeakingNode( + @Nullable AccessibilityNodeInfoCompat node, @Nullable View view) { + if (node == null || view == null) { + return false; + } + + final int important = ViewCompat.getImportantForAccessibility(view); + if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + || (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO && node.getChildCount() <= 0)) { + return false; + } + + return node.isCheckable() || hasText(node) || hasNonActionableSpeakingDescendants(node, view); + } + + /** + * Determines if the supplied {@link View} and {@link AccessibilityNodeInfoCompat} has any + * children which are not independently accessibility focusable and also have a spoken + * description. + * + *

NOTE: Accessibility services will include these children's descriptions in the closest + * focusable ancestor. + * + * @param view The {@link View} to evaluate + * @param node The {@link AccessibilityNodeInfoCompat} to evaluate + * @return {@code true} if it has any non-actionable speaking descendants within its subtree + */ + public static boolean hasNonActionableSpeakingDescendants( + @Nullable AccessibilityNodeInfoCompat node, @Nullable View view) { + + if (node == null || view == null || !(view instanceof ViewGroup)) { + return false; + } + + final ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) { + final View childView = viewGroup.getChildAt(i); + + if (childView == null) { + continue; + } + + final AccessibilityNodeInfoCompat childNode = AccessibilityNodeInfoCompat.obtain(); + try { + ViewCompat.onInitializeAccessibilityNodeInfo(childView, childNode); + + if (!node.isVisibleToUser()) { + continue; + } + + if (isAccessibilityFocusable(childNode, childView)) { + continue; + } + + if (isSpeakingNode(childNode, childView)) { + return true; + } + } finally { + if (childNode != null) { + childNode.recycle(); + } + } + } + + return false; + } + + /** + * Determines if the provided {@link View} and {@link AccessibilityNodeInfoCompat} meet the + * criteria for gaining accessibility focus. + * + *

Note: this is evaluating general focusability by accessibility services, and does not mean + * this view will be guaranteed to be focused by specific services such as Talkback. For Talkback + * focusability, see {@link #isTalkbackFocusable(View)} + * + * @param view The {@link View} to evaluate + * @param node The {@link AccessibilityNodeInfoCompat} to evaluate + * @return {@code true} if it is possible to gain accessibility focus + */ + public static boolean isAccessibilityFocusable( + @Nullable AccessibilityNodeInfoCompat node, @Nullable View view) { + if (node == null || view == null) { + return false; + } + + // Never focus invisible nodes. + if (!node.isVisibleToUser()) { + return false; + } + + // Always focus "actionable" nodes. + if (isActionableForAccessibility(node)) { + return true; + } + + // only focus top-level list items with non-actionable speaking children. + return isTopLevelScrollItem(node, view) && isSpeakingNode(node, view); + } + + /** + * Determines whether the provided {@link View} and {@link AccessibilityNodeInfoCompat} is a + * top-level item in a scrollable container. + * + * @param view The {@link View} to evaluate + * @param node The {@link AccessibilityNodeInfoCompat} to evaluate + * @return {@code true} if it is a top-level item in a scrollable container. + */ + public static boolean isTopLevelScrollItem( + @Nullable AccessibilityNodeInfoCompat node, @Nullable View view) { + if (node == null || view == null) { + return false; + } + + final View parent = (View) ViewCompat.getParentForAccessibility(view); + if (parent == null) { + return false; + } + + if (node.isScrollable()) { + return true; + } + + final List actionList = node.getActionList(); + if (actionList.contains(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD) + || actionList.contains(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD)) { + return true; + } + + // Top-level items in a scrolling pager are actually two levels down since the first + // level items in pagers are the pages themselves. + View grandparent = (View) ViewCompat.getParentForAccessibility(parent); + if (grandparent != null + && AccessibilityRoleUtil.getRole(grandparent) == AccessibilityRole.PAGER) { + return true; + } + + AccessibilityRole parentRole = AccessibilityRoleUtil.getRole(parent); + return parentRole == AccessibilityRole.LIST + || parentRole == AccessibilityRole.GRID + || parentRole == AccessibilityRole.SCROLL_VIEW + || parentRole == AccessibilityRole.HORIZONTAL_SCROLL_VIEW; + } + + /** + * Returns whether a node is actionable. That is, the node supports one of {@link + * AccessibilityNodeInfoCompat#isClickable()}, {@link AccessibilityNodeInfoCompat#isFocusable()}, + * or {@link AccessibilityNodeInfoCompat#isLongClickable()}. + * + * @param node The {@link AccessibilityNodeInfoCompat} to evaluate + * @return {@code true} if node is actionable. + */ + public static boolean isActionableForAccessibility(@Nullable AccessibilityNodeInfoCompat node) { + if (node == null) { + return false; + } + + if (node.isClickable() || node.isLongClickable() || node.isFocusable()) { + return true; + } + + final List actionList = node.getActionList(); + return actionList.contains(AccessibilityNodeInfoCompat.ACTION_CLICK) + || actionList.contains(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK) + || actionList.contains(AccessibilityNodeInfoCompat.ACTION_FOCUS); + } + + /** + * Determines if any of the provided {@link View}'s and {@link AccessibilityNodeInfoCompat}'s + * ancestors can receive accessibility focus + * + * @param view The {@link View} to evaluate + * @param node The {@link AccessibilityNodeInfoCompat} to evaluate + * @return {@code true} if an ancestor of may receive accessibility focus + */ + public static boolean hasFocusableAncestor( + @Nullable AccessibilityNodeInfoCompat node, @Nullable View view) { + if (node == null || view == null) { + return false; + } + + final ViewParent parentView = ViewCompat.getParentForAccessibility(view); + if (!(parentView instanceof View)) { + return false; + } + + final AccessibilityNodeInfoCompat parentNode = AccessibilityNodeInfoCompat.obtain(); + try { + ViewCompat.onInitializeAccessibilityNodeInfo((View) parentView, parentNode); + if (parentNode == null) { + return false; + } + + if (hasEqualBoundsToViewRoot(parentNode, (View) parentView) + && parentNode.getChildCount() > 0) { + return false; + } + + if (isAccessibilityFocusable(parentNode, (View) parentView)) { + return true; + } + + if (hasFocusableAncestor(parentNode, (View) parentView)) { + return true; + } + } finally { + parentNode.recycle(); + } + return false; + } + + /** + * Returns whether a one given view is a descendant of another. + * + * @param view The {@link View} to evaluate + * @param potentialAncestor The potential ancestor {@link View} + * @return {@code true} if view is a descendant of potentialAncestor + */ + private static boolean viewIsDescendant(View view, View potentialAncestor) { + ViewParent parent = view.getParent(); + while (parent != null) { + if (parent == potentialAncestor) { + return true; + } + parent = parent.getParent(); + } + + return false; + } + + /** + * Returns whether a View has the same size and position as its View Root. + * + * @param view The {@link View} to evaluate + * @return {@code true} if view has equal bounds + */ + public static boolean hasEqualBoundsToViewRoot(AccessibilityNodeInfoCompat node, View view) { + AndroidRootResolver rootResolver = new AndroidRootResolver(); + List roots = rootResolver.listActiveRoots(); + if (roots != null) { + for (AndroidRootResolver.Root root : roots) { + if (view == root.view) { + return true; + } + + if (viewIsDescendant(view, root.view)) { + Rect nodeBounds = new Rect(); + node.getBoundsInScreen(nodeBounds); + + Rect viewRootBounds = new Rect(); + viewRootBounds.set( + root.param.x, + root.param.y, + root.param.width + root.param.x, + root.param.height + root.param.y); + + return nodeBounds.equals(viewRootBounds); + } + } + } + return false; + } + + /** + * Returns whether a given {@link View} will be focusable by Google's TalkBack screen reader. + * + * @param view The {@link View} to evaluate. + * @return {@code boolean} if the view will be ignored by TalkBack. + */ + public static boolean isTalkbackFocusable(View view) { + if (view == null) { + return false; + } + + final int important = ViewCompat.getImportantForAccessibility(view); + if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO + || important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { + return false; + } + + // Go all the way up the tree to make sure no parent has hidden its descendants + ViewParent parent = view.getParent(); + while (parent instanceof View) { + if (ViewCompat.getImportantForAccessibility((View) parent) + == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { + return false; + } + parent = parent.getParent(); + } + + final AccessibilityNodeInfoCompat node = ViewAccessibilityHelper.createNodeInfoFromView(view); + if (node == null) { + return false; + } + + // Non-leaf nodes identical in size to their View Root should not be focusable. + if (hasEqualBoundsToViewRoot(node, view) && node.getChildCount() > 0) { + return false; + } + + try { + if (!node.isVisibleToUser()) { + return false; + } + + if (isAccessibilityFocusable(node, view)) { + if (node.getChildCount() <= 0) { + // Leaves that are accessibility focusable are never ignored, even if they don't have a + // speakable description + return true; + } else if (isSpeakingNode(node, view)) { + // Node is focusable and has something to speak + return true; + } + + // Node is focusable and has nothing to speak + return false; + } + + // if view is not accessibility focusable, it needs to have text and no focusable ancestors. + if (!hasText(node)) { + return false; + } + + if (!hasFocusableAncestor(node, view)) { + return true; + } + + return false; + } finally { + node.recycle(); + } + } +} diff --git a/android/plugins/inspector/descriptors/utils/AccessibilityRoleUtil.java b/android/plugins/inspector/descriptors/utils/AccessibilityRoleUtil.java new file mode 100644 index 000000000..367b7732f --- /dev/null +++ b/android/plugins/inspector/descriptors/utils/AccessibilityRoleUtil.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors.utils; + +import android.support.v4.view.ViewCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; +import javax.annotation.Nullable; + +/** + * Utility class that handles the addition of a "role" for accessibility to either a View or + * AccessibilityNodeInfo. + */ +public class AccessibilityRoleUtil { + + /** + * These roles are defined by Google's TalkBack screen reader, and this list should be kept up to + * date with their implementation. Details can be seen in their source code here: + * + *

https://github.com/google/talkback/blob/master/utils/src/main/java/Role.java + */ + public enum AccessibilityRole { + NONE(null), + BUTTON("android.widget.Button"), + CHECK_BOX("android.widget.CompoundButton"), + DROP_DOWN_LIST("android.widget.Spinner"), + EDIT_TEXT("android.widget.EditText"), + GRID("android.widget.GridView"), + IMAGE("android.widget.ImageView"), + IMAGE_BUTTON("android.widget.ImageView"), + LIST("android.widget.AbsListView"), + PAGER("android.support.v4.view.ViewPager"), + RADIO_BUTTON("android.widget.RadioButton"), + SEEK_CONTROL("android.widget.SeekBar"), + SWITCH("android.widget.Switch"), + TAB_BAR("android.widget.TabWidget"), + TOGGLE_BUTTON("android.widget.ToggleButton"), + VIEW_GROUP("android.view.ViewGroup"), + WEB_VIEW("android.webkit.WebView"), + CHECKED_TEXT_VIEW("android.widget.CheckedTextView"), + PROGRESS_BAR("android.widget.ProgressBar"), + ACTION_BAR_TAB("android.app.ActionBar$Tab"), + DRAWER_LAYOUT("android.support.v4.widget.DrawerLayout"), + SLIDING_DRAWER("android.widget.SlidingDrawer"), + ICON_MENU("com.android.internal.view.menu.IconMenuView"), + TOAST("android.widget.Toast$TN"), + DATE_PICKER_DIALOG("android.app.DatePickerDialog"), + TIME_PICKER_DIALOG("android.app.TimePickerDialog"), + DATE_PICKER("android.widget.DatePicker"), + TIME_PICKER("android.widget.TimePicker"), + NUMBER_PICKER("android.widget.NumberPicker"), + SCROLL_VIEW("android.widget.ScrollView"), + HORIZONTAL_SCROLL_VIEW("android.widget.HorizontalScrollView"), + KEYBOARD_KEY("android.inputmethodservice.Keyboard$Key"); + + @Nullable private final String mValue; + + AccessibilityRole(String type) { + mValue = type; + } + + @Nullable + public String getValue() { + return mValue; + } + + public static AccessibilityRole fromValue(String value) { + for (AccessibilityRole role : AccessibilityRole.values()) { + if (role.getValue() != null && role.getValue().equals(value)) { + return role; + } + } + return AccessibilityRole.NONE; + } + } + + private AccessibilityRoleUtil() { + // No instances + } + + public static AccessibilityRole getRole(View view) { + AccessibilityNodeInfoCompat nodeInfo = AccessibilityNodeInfoCompat.obtain(); + ViewCompat.onInitializeAccessibilityNodeInfo(view, nodeInfo); + AccessibilityRole role = getRole(nodeInfo); + nodeInfo.recycle(); + return role; + } + + public static AccessibilityRole getRole(AccessibilityNodeInfo nodeInfo) { + return getRole(new AccessibilityNodeInfoCompat(nodeInfo)); + } + + public static AccessibilityRole getRole(AccessibilityNodeInfoCompat nodeInfo) { + AccessibilityRole role = AccessibilityRole.fromValue((String) nodeInfo.getClassName()); + if (role.equals(AccessibilityRole.IMAGE_BUTTON) || role.equals(AccessibilityRole.IMAGE)) { + return nodeInfo.isClickable() ? AccessibilityRole.IMAGE_BUTTON : AccessibilityRole.IMAGE; + } + + if (role.equals(AccessibilityRole.NONE)) { + AccessibilityNodeInfoCompat.CollectionInfoCompat collection = nodeInfo.getCollectionInfo(); + if (collection != null) { + // RecyclerView will be classified as a list or grid. + if (collection.getRowCount() > 1 && collection.getColumnCount() > 1) { + return AccessibilityRole.GRID; + } else { + return AccessibilityRole.LIST; + } + } + } + + return role; + } +} diff --git a/android/plugins/inspector/descriptors/utils/AccessibilityUtil.java b/android/plugins/inspector/descriptors/utils/AccessibilityUtil.java new file mode 100644 index 000000000..3d556894d --- /dev/null +++ b/android/plugins/inspector/descriptors/utils/AccessibilityUtil.java @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors.utils; + +import static android.content.Context.ACCESSIBILITY_SERVICE; + +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityManager; +import android.widget.EditText; +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarObject; +import javax.annotation.Nullable; + +/** + * This class provides utility methods for determining certain accessibility properties of {@link + * View}s and {@link AccessibilityNodeInfoCompat}s. It is porting some of the checks from {@link + * com.googlecode.eyesfree.utils.AccessibilityNodeInfoUtils}, but has stripped many features which + * are unnecessary here. + */ +public final class AccessibilityUtil { + private AccessibilityUtil() {} + + public static final EnumMapping sAccessibilityActionMapping = + new EnumMapping("UNKNOWN") { + { + put("FOCUS", AccessibilityNodeInfoCompat.ACTION_FOCUS); + put("CLEAR_FOCUS", AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS); + put("SELECT", AccessibilityNodeInfoCompat.ACTION_SELECT); + put("CLEAR_SELECTION", AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION); + put("CLICK", AccessibilityNodeInfoCompat.ACTION_CLICK); + put("LONG_CLICK", AccessibilityNodeInfoCompat.ACTION_LONG_CLICK); + put("ACCESSIBILITY_FOCUS", AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); + put( + "CLEAR_ACCESSIBILITY_FOCUS", + AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS); + put( + "NEXT_AT_MOVEMENT_GRANULARITY", + AccessibilityNodeInfoCompat.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); + put( + "PREVIOUS_AT_MOVEMENT_GRANULARITY", + AccessibilityNodeInfoCompat.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); + put("NEXT_HTML_ELEMENT", AccessibilityNodeInfoCompat.ACTION_NEXT_HTML_ELEMENT); + put("PREVIOUS_HTML_ELEMENT", AccessibilityNodeInfoCompat.ACTION_PREVIOUS_HTML_ELEMENT); + put("SCROLL_FORWARD", AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); + put("SCROLL_BACKWARD", AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); + put("CUT", AccessibilityNodeInfoCompat.ACTION_CUT); + put("COPY", AccessibilityNodeInfoCompat.ACTION_COPY); + put("PASTE", AccessibilityNodeInfoCompat.ACTION_PASTE); + put("SET_SELECTION", AccessibilityNodeInfoCompat.ACTION_SET_SELECTION); + put("SET_SELECTION", AccessibilityNodeInfoCompat.ACTION_SET_SELECTION); + put("EXPAND", AccessibilityNodeInfoCompat.ACTION_EXPAND); + put("COLLAPSE", AccessibilityNodeInfoCompat.ACTION_COLLAPSE); + put("DISMISS", AccessibilityNodeInfoCompat.ACTION_DISMISS); + put("SET_TEXT", AccessibilityNodeInfoCompat.ACTION_SET_TEXT); + } + }; + + public static final EnumMapping sImportantForAccessibilityMapping = + new EnumMapping("AUTO") { + { + put("AUTO", View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + put("NO", View.IMPORTANT_FOR_ACCESSIBILITY_NO); + put("YES", View.IMPORTANT_FOR_ACCESSIBILITY_YES); + put("NO_HIDE_DESCENDANTS", View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + } + }; + + /** + * Given a {@link Context}, determine if any accessibility service is running. + * + * @param context The {@link Context} used to get the {@link AccessibilityManager}. + * @return {@code true} if an accessibility service is currently running. + */ + public static boolean isAccessibilityEnabled(Context context) { + return ((AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE)).isEnabled(); + } + + /** + * Returns a sentence describing why a given {@link View} will be ignored by Google's TalkBack + * screen reader. + * + * @param view The {@link View} to evaluate. + * @return {@code String} describing why a {@link View} is ignored. + */ + public static String getTalkbackIgnoredReasons(View view) { + final int important = ViewCompat.getImportantForAccessibility(view); + + if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO) { + return "View has importantForAccessibility set to 'NO'."; + } + + if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { + return "View has importantForAccessibility set to 'NO_HIDE_DESCENDANTS'."; + } + + ViewParent parent = view.getParent(); + while (parent instanceof View) { + if (ViewCompat.getImportantForAccessibility((View) parent) + == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { + return "An ancestor View has importantForAccessibility set to 'NO_HIDE_DESCENDANTS'."; + } + parent = parent.getParent(); + } + + final AccessibilityNodeInfoCompat node = ViewAccessibilityHelper.createNodeInfoFromView(view); + if (node == null) { + return "AccessibilityNodeInfo cannot be found."; + } + + try { + if (AccessibilityEvaluationUtil.hasEqualBoundsToViewRoot(node, view)) { + return "View has the same dimensions as the View Root."; + } + + if (!node.isVisibleToUser()) { + return "View is not visible."; + } + + if (AccessibilityEvaluationUtil.isAccessibilityFocusable(node, view)) { + return "View is actionable, but has no description."; + } + + if (AccessibilityEvaluationUtil.hasText(node)) { + return "View is not actionable, and an ancestor View has co-opted its description."; + } + + return "View is not actionable and has no description."; + } finally { + node.recycle(); + } + } + + /** + * Returns a sentence describing why a given {@link View} will be focusable by Google's TalkBack + * screen reader. + * + * @param view The {@link View} to evaluate. + * @return {@code String} describing why a {@link View} is focusable. + */ + @Nullable + public static String getTalkbackFocusableReasons(View view) { + final AccessibilityNodeInfoCompat node = ViewAccessibilityHelper.createNodeInfoFromView(view); + if (node == null) { + return null; + } + try { + final boolean hasText = AccessibilityEvaluationUtil.hasText(node); + final boolean isCheckable = node.isCheckable(); + final boolean hasNonActionableSpeakingDescendants = + AccessibilityEvaluationUtil.hasNonActionableSpeakingDescendants(node, view); + + if (AccessibilityEvaluationUtil.isActionableForAccessibility(node)) { + if (node.getChildCount() <= 0) { + return "View is actionable and has no children."; + } else if (hasText) { + return "View is actionable and has a description."; + } else if (isCheckable) { + return "View is actionable and checkable."; + } else if (hasNonActionableSpeakingDescendants) { + return "View is actionable and has non-actionable descendants with descriptions."; + } + } + + if (AccessibilityEvaluationUtil.isTopLevelScrollItem(node, view)) { + if (hasText) { + return "View is a direct child of a scrollable container and has a description."; + } else if (isCheckable) { + return "View is a direct child of a scrollable container and is checkable."; + } else if (hasNonActionableSpeakingDescendants) { + return "View is a direct child of a scrollable container and has non-actionable " + + "descendants with descriptions."; + } + } + + if (hasText) { + return "View has a description and is not actionable, but has no actionable ancestor."; + } + + return null; + } finally { + node.recycle(); + } + } + + /** + * Creates the text that Gogole's TalkBack screen reader will read aloud for a given {@link View}. + * This may be any combination of the {@link View}'s {@code text}, {@code contentDescription}, and + * the {@code text} and {@code contentDescription} of any ancestor {@link View}. + * + *

Note: This string does not include any additional semantic information that Talkback will + * read, such as "Button", or "disabled". + * + * @param view The {@link View} to evaluate. + * @return {@code String} describing why a {@link View} is focusable. + */ + @Nullable + public static CharSequence getTalkbackDescription(View view) { + final AccessibilityNodeInfoCompat node = ViewAccessibilityHelper.createNodeInfoFromView(view); + if (node == null) { + return null; + } + try { + final CharSequence contentDescription = node.getContentDescription(); + final CharSequence nodeText = node.getText(); + + final boolean hasNodeText = !TextUtils.isEmpty(nodeText); + final boolean isEditText = view instanceof EditText; + + // EditText's prioritize their own text content over a contentDescription + if (!TextUtils.isEmpty(contentDescription) && (!isEditText || !hasNodeText)) { + return contentDescription; + } + + if (hasNodeText) { + return nodeText; + } + + // If there are child views and no contentDescription the text of all non-focusable children, + // comma separated, becomes the description. + if (view instanceof ViewGroup) { + final StringBuilder concatChildDescription = new StringBuilder(); + final String separator = ", "; + final ViewGroup viewGroup = (ViewGroup) view; + + for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) { + final View child = viewGroup.getChildAt(i); + + final AccessibilityNodeInfoCompat childNodeInfo = AccessibilityNodeInfoCompat.obtain(); + ViewCompat.onInitializeAccessibilityNodeInfo(child, childNodeInfo); + + CharSequence childNodeDescription = null; + if (AccessibilityEvaluationUtil.isSpeakingNode(childNodeInfo, child) + && !AccessibilityEvaluationUtil.isAccessibilityFocusable(childNodeInfo, child)) { + childNodeDescription = getTalkbackDescription(child); + } + + if (!TextUtils.isEmpty(childNodeDescription)) { + if (concatChildDescription.length() > 0) { + concatChildDescription.append(separator); + } + concatChildDescription.append(childNodeDescription); + } + childNodeInfo.recycle(); + } + + return concatChildDescription.length() > 0 ? concatChildDescription.toString() : null; + } + + return null; + } finally { + node.recycle(); + } + } + + /** + * Creates a {@link SonarObject} of useful properties of AccessibilityNodeInfo, to be shown in the + * Sonar Layout Inspector. All properties are immutable since they are all derived from various + * {@link View} properties. + * + * @param view The {@link View} to derive the AccessibilityNodeInfo properties from. + * @return {@link SonarObject} containing the properties. + */ + @Nullable + public static SonarObject getAccessibilityNodeInfoProperties(View view) { + final AccessibilityNodeInfoCompat nodeInfo = + ViewAccessibilityHelper.createNodeInfoFromView(view); + if (nodeInfo == null) { + return null; + } + + final SonarObject.Builder nodeInfoProps = new SonarObject.Builder(); + final Rect bounds = new Rect(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + final SonarArray.Builder actionsArrayBuilder = new SonarArray.Builder(); + for (AccessibilityNodeInfoCompat.AccessibilityActionCompat action : + nodeInfo.getActionList()) { + final String actionLabel = (String) action.getLabel(); + if (actionLabel != null) { + actionsArrayBuilder.put(actionLabel); + } else { + actionsArrayBuilder.put( + AccessibilityUtil.sAccessibilityActionMapping.get(action.getId(), false)); + } + } + nodeInfoProps.put("actions", actionsArrayBuilder.build()); + } + + nodeInfoProps + .put("clickable", nodeInfo.isClickable()) + .put("content-description", nodeInfo.getContentDescription()) + .put("text", nodeInfo.getText()) + .put("focused", nodeInfo.isAccessibilityFocused()) + .put("long-clickable", nodeInfo.isLongClickable()) + .put("focusable", nodeInfo.isFocusable()); + + nodeInfo.getBoundsInParent(bounds); + nodeInfoProps.put( + "parent-bounds", + new SonarObject.Builder() + .put("width", bounds.width()) + .put("height", bounds.height()) + .put("top", bounds.top) + .put("left", bounds.left) + .put("bottom", bounds.bottom) + .put("right", bounds.right)); + + nodeInfo.getBoundsInScreen(bounds); + nodeInfoProps.put( + "screen-bounds", + new SonarObject.Builder() + .put("width", bounds.width()) + .put("height", bounds.height()) + .put("top", bounds.top) + .put("left", bounds.left) + .put("bottom", bounds.bottom) + .put("right", bounds.right)); + + nodeInfo.recycle(); + + return nodeInfoProps.build(); + } + + /** + * Modifies a {@link SonarObject.Builder} to add Talkback-specific Accessibiltiy properties to be + * shown in the Sonar Layout Inspector. + * + * @param props The {@link SonarObject.Builder} to add the properties to. + * @param view The {@link View} to derive the properties from. + */ + public static void addTalkbackProperties(SonarObject.Builder props, View view) { + if (!AccessibilityEvaluationUtil.isTalkbackFocusable(view)) { + props + .put("talkback-ignored", true) + .put("talkback-ignored-reasons", getTalkbackIgnoredReasons(view)); + } else { + props + .put("talkback-focusable", true) + .put("talkback-focusable-reasons", getTalkbackFocusableReasons(view)) + .put("talkback-description", getTalkbackDescription(view)); + } + } +} diff --git a/android/plugins/inspector/descriptors/utils/AndroidRootResolver.java b/android/plugins/inspector/descriptors/utils/AndroidRootResolver.java new file mode 100644 index 000000000..c444f897e --- /dev/null +++ b/android/plugins/inspector/descriptors/utils/AndroidRootResolver.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors.utils; + +import static android.view.WindowManager.LayoutParams; + +import android.os.Build; +import android.view.View; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nullable; + +public final class AndroidRootResolver { + + private static final String WINDOW_MANAGER_IMPL_CLAZZ = "android.view.WindowManagerImpl"; + private static final String WINDOW_MANAGER_GLOBAL_CLAZZ = "android.view.WindowManagerGlobal"; + private static final String VIEWS_FIELD = "mViews"; + private static final String WINDOW_PARAMS_FIELD = "mParams"; + private static final String GET_DEFAULT_IMPL = "getDefault"; + private static final String GET_GLOBAL_INSTANCE = "getInstance"; + + private boolean initialized; + private Object windowManagerObj; + private Field viewsField; + private Field paramsField; + + public static class Root { + public final View view; + public final LayoutParams param; + + private Root(View view, LayoutParams param) { + this.view = view; + this.param = param; + } + } + + public @Nullable List listActiveRoots() { + if (!initialized) { + initialize(); + } + + if (null == windowManagerObj) { + return null; + } + + if (null == viewsField) { + return null; + } + if (null == paramsField) { + return null; + } + + List views = null; + List params = null; + + try { + if (Build.VERSION.SDK_INT < 19) { + views = Arrays.asList((View[]) viewsField.get(windowManagerObj)); + params = Arrays.asList((LayoutParams[]) paramsField.get(windowManagerObj)); + } else { + views = (List) viewsField.get(windowManagerObj); + params = (List) paramsField.get(windowManagerObj); + } + } catch (RuntimeException | IllegalAccessException re) { + return null; + } + + List roots = new ArrayList<>(); + for (int i = 0, stop = views.size(); i < stop; i++) { + roots.add(new Root(views.get(i), params.get(i))); + } + return roots; + } + + private void initialize() { + initialized = true; + String accessClass = + Build.VERSION.SDK_INT > 16 ? WINDOW_MANAGER_GLOBAL_CLAZZ : WINDOW_MANAGER_IMPL_CLAZZ; + String instanceMethod = Build.VERSION.SDK_INT > 16 ? GET_GLOBAL_INSTANCE : GET_DEFAULT_IMPL; + + try { + Class clazz = Class.forName(accessClass); + Method getMethod = clazz.getMethod(instanceMethod); + windowManagerObj = getMethod.invoke(null); + viewsField = clazz.getDeclaredField(VIEWS_FIELD); + viewsField.setAccessible(true); + paramsField = clazz.getDeclaredField(WINDOW_PARAMS_FIELD); + paramsField.setAccessible(true); + } catch (InvocationTargetException | IllegalAccessException | RuntimeException | NoSuchMethodException | NoSuchFieldException | ClassNotFoundException ignored) { + } + } +} diff --git a/android/plugins/inspector/descriptors/utils/EnumMapping.java b/android/plugins/inspector/descriptors/utils/EnumMapping.java new file mode 100644 index 000000000..08719a6f4 --- /dev/null +++ b/android/plugins/inspector/descriptors/utils/EnumMapping.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors.utils; + +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Enum; + +import android.support.v4.util.SimpleArrayMap; +import com.facebook.sonar.plugins.inspector.InspectorValue; + +public class EnumMapping { + private final SimpleArrayMap mMapping = new SimpleArrayMap<>(); + private final String mDefaultKey; + + public EnumMapping(String defaultKey) { + mDefaultKey = defaultKey; + } + + public void put(String s, int i) { + mMapping.put(s, i); + } + + public InspectorValue get(final int i) { + return get(i, true); + } + + public InspectorValue get(final int i, final boolean mutable) { + for (int ii = 0, count = mMapping.size(); ii < count; ii++) { + if (mMapping.valueAt(ii) == i) { + return mutable + ? InspectorValue.mutable(Enum, mMapping.keyAt(ii)) + : InspectorValue.immutable(Enum, mMapping.keyAt(ii)); + } + } + return mutable + ? InspectorValue.mutable(Enum, mDefaultKey) + : InspectorValue.immutable(Enum, mDefaultKey); + } + + public int get(String s) { + if (mMapping.containsKey(s)) { + return mMapping.get(s); + } + return mMapping.get(mDefaultKey); + } +} diff --git a/android/plugins/inspector/descriptors/utils/ViewAccessibilityHelper.java b/android/plugins/inspector/descriptors/utils/ViewAccessibilityHelper.java new file mode 100644 index 000000000..904219916 --- /dev/null +++ b/android/plugins/inspector/descriptors/utils/ViewAccessibilityHelper.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.inspector.descriptors.utils; + +import android.support.v4.view.ViewCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.view.View; +import javax.annotation.Nullable; + +/** Class that helps with accessibility by providing useful methods. */ +public final class ViewAccessibilityHelper { + + /** + * Creates and returns an {@link AccessibilityNodeInfoCompat} from the the provided {@link View}. + * Note: This does not handle recycling of the {@link AccessibilityNodeInfoCompat}. + * + * @param view The {@link View} to create the {@link AccessibilityNodeInfoCompat} from. + * @return {@link AccessibilityNodeInfoCompat} + */ + @Nullable + public static AccessibilityNodeInfoCompat createNodeInfoFromView(View view) { + if (view == null) { + return null; + } + + final AccessibilityNodeInfoCompat nodeInfo = AccessibilityNodeInfoCompat.obtain(); + + // For some unknown reason, Android seems to occasionally throw a NPE from + // onInitializeAccessibilityNodeInfo. + try { + ViewCompat.onInitializeAccessibilityNodeInfo(view, nodeInfo); + } catch (NullPointerException e) { + if (nodeInfo != null) { + nodeInfo.recycle(); + } + return null; + } + + return nodeInfo; + } + +} diff --git a/android/plugins/inspector/litho-sonar/DebugComponentDescriptor.java b/android/plugins/inspector/litho-sonar/DebugComponentDescriptor.java new file mode 100644 index 000000000..39e2cdaa6 --- /dev/null +++ b/android/plugins/inspector/litho-sonar/DebugComponentDescriptor.java @@ -0,0 +1,696 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.litho.sonar; + +import static com.facebook.litho.annotations.ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_NO; +import static com.facebook.litho.annotations.ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Color; +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Enum; +import static com.facebook.sonar.plugins.inspector.InspectorValue.Type.Number; + +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.support.v4.util.Pair; +import android.view.View; +import com.facebook.litho.Component; +import com.facebook.litho.ComponentContext; +import com.facebook.litho.ComponentLifecycle; +import com.facebook.litho.DebugComponent; +import com.facebook.litho.DebugLayoutNode; +import com.facebook.litho.EventHandler; +import com.facebook.litho.LithoView; +import com.facebook.litho.annotations.Prop; +import com.facebook.litho.annotations.State; +import com.facebook.litho.reference.Reference; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.HighlightedOverlay; +import com.facebook.sonar.plugins.inspector.InspectorValue; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import com.facebook.sonar.plugins.inspector.descriptors.ObjectDescriptor; +import com.facebook.sonar.plugins.inspector.descriptors.utils.AccessibilityUtil; +import com.facebook.yoga.YogaAlign; +import com.facebook.yoga.YogaDirection; +import com.facebook.yoga.YogaEdge; +import com.facebook.yoga.YogaFlexDirection; +import com.facebook.yoga.YogaJustify; +import com.facebook.yoga.YogaPositionType; +import com.facebook.yoga.YogaValue; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + +public class DebugComponentDescriptor extends NodeDescriptor { + + private Map>> mOverrides = new HashMap<>(); + private DebugComponent.Overrider mOverrider = + new DebugComponent.Overrider() { + @Override + public void applyComponentOverrides(String key, Component component) { + final List> overrides = mOverrides.get(key); + if (overrides == null) { + return; + } + + for (Pair override : overrides) { + if (override.first[0].equals("Props")) { + applyReflectiveOverride(component, override.first[1], override.second); + } + } + } + + @Override + public void applyStateOverrides( + String key, ComponentLifecycle.StateContainer stateContainer) { + final List> overrides = mOverrides.get(key); + if (overrides == null) { + return; + } + + for (Pair override : overrides) { + if (override.first[0].equals("State")) { + applyReflectiveOverride(stateContainer, override.first[1], override.second); + } + } + } + + @Override + public void applyLayoutOverrides(String key, DebugLayoutNode node) { + final List> overrides = mOverrides.get(key); + if (overrides == null) { + return; + } + + for (Pair override : overrides) { + if (override.first[0].equals("Layout")) { + try { + applyLayoutOverride( + node, + Arrays.copyOfRange(override.first, 1, override.first.length), + override.second); + } catch (Exception ignored) { + } + } else if (override.first[0].equals("Accessibility")) { + applyAccessibilityOverride(node, override.first[1], override.second); + } + } + } + }; + + @Override + public void init(DebugComponent node) { + // We rely on the LithoView being invalidated when a component hierarchy changes. + } + + @Override + public String getId(DebugComponent node) { + return node.getGlobalKey(); + } + + @Override + public String getName(DebugComponent node) throws Exception { + NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass()); + if (componentDescriptor.getClass() != ObjectDescriptor.class) { + return componentDescriptor.getName(node.getComponent()); + } + return node.getComponent().getSimpleName(); + } + + @Override + public int getChildCount(DebugComponent node) { + if (node.getMountedView() != null || node.getMountedDrawable() != null) { + return 1; + } else { + return node.getChildComponents().size(); + } + } + + @Override + public Object getChildAt(DebugComponent node, int index) { + final View mountedView = node.getMountedView(); + final Drawable mountedDrawable = node.getMountedDrawable(); + + if (mountedView != null) { + return mountedView; + } else if (mountedDrawable != null) { + return mountedDrawable; + } else { + return node.getChildComponents().get(index); + } + } + + @Override + public List> getData(DebugComponent node) throws Exception { + NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass()); + if (componentDescriptor.getClass() != ObjectDescriptor.class) { + return componentDescriptor.getData(node.getComponent()); + } + + final List> data = new ArrayList<>(); + + final SonarObject layoutData = getLayoutData(node); + if (layoutData != null) { + data.add(new Named<>("Layout", layoutData)); + } + + final SonarObject propData = getPropData(node); + if (propData != null) { + data.add(new Named<>("Props", propData)); + } + + final SonarObject stateData = getStateData(node); + if (stateData != null) { + data.add(new Named<>("State", stateData)); + } + + final SonarObject accessibilityData = getAccessibilityData(node); + if (accessibilityData != null) { + data.add(new Named<>("Accessibility", accessibilityData)); + } + + return data; + } + + @Nullable + private static SonarObject getLayoutData(DebugComponent node) { + final DebugLayoutNode layout = node.getLayoutNode(); + if (layout == null) { + return null; + } + + final SonarObject.Builder data = new SonarObject.Builder(); + data.put("background", fromReference(node.getContext(), layout.getBackground())); + data.put("foreground", fromDrawable(layout.getForeground())); + + data.put("direction", InspectorValue.mutable(Enum, layout.getLayoutDirection().toString())); + data.put("flex-direction", InspectorValue.mutable(Enum, layout.getFlexDirection().toString())); + data.put( + "justify-content", InspectorValue.mutable(Enum, layout.getJustifyContent().toString())); + data.put("align-items", InspectorValue.mutable(Enum, layout.getAlignItems().toString())); + data.put("align-self", InspectorValue.mutable(Enum, layout.getAlignSelf().toString())); + data.put("align-content", InspectorValue.mutable(Enum, layout.getAlignContent().toString())); + data.put("position-type", InspectorValue.mutable(Enum, layout.getPositionType().toString())); + + data.put("flex-grow", fromFloat(layout.getFlexGrow())); + data.put("flex-shrink", fromFloat(layout.getFlexShrink())); + data.put("flex-basis", fromYogaValue(layout.getFlexBasis())); + + data.put("width", fromYogaValue(layout.getWidth())); + data.put("min-width", fromYogaValue(layout.getMinWidth())); + data.put("max-width", fromYogaValue(layout.getMaxWidth())); + + data.put("height", fromYogaValue(layout.getHeight())); + data.put("min-height", fromYogaValue(layout.getMinHeight())); + data.put("max-height", fromYogaValue(layout.getMaxHeight())); + + data.put("aspect-ratio", fromFloat(layout.getAspectRatio())); + + data.put( + "margin", + new SonarObject.Builder() + .put("left", fromYogaValue(layout.getMargin(YogaEdge.LEFT))) + .put("top", fromYogaValue(layout.getMargin(YogaEdge.TOP))) + .put("right", fromYogaValue(layout.getMargin(YogaEdge.RIGHT))) + .put("bottom", fromYogaValue(layout.getMargin(YogaEdge.BOTTOM))) + .put("start", fromYogaValue(layout.getMargin(YogaEdge.START))) + .put("end", fromYogaValue(layout.getMargin(YogaEdge.END))) + .put("horizontal", fromYogaValue(layout.getMargin(YogaEdge.HORIZONTAL))) + .put("vertical", fromYogaValue(layout.getMargin(YogaEdge.VERTICAL))) + .put("all", fromYogaValue(layout.getMargin(YogaEdge.ALL)))); + + data.put( + "padding", + new SonarObject.Builder() + .put("left", fromYogaValue(layout.getPadding(YogaEdge.LEFT))) + .put("top", fromYogaValue(layout.getPadding(YogaEdge.TOP))) + .put("right", fromYogaValue(layout.getPadding(YogaEdge.RIGHT))) + .put("bottom", fromYogaValue(layout.getPadding(YogaEdge.BOTTOM))) + .put("start", fromYogaValue(layout.getPadding(YogaEdge.START))) + .put("end", fromYogaValue(layout.getPadding(YogaEdge.END))) + .put("horizontal", fromYogaValue(layout.getPadding(YogaEdge.HORIZONTAL))) + .put("vertical", fromYogaValue(layout.getPadding(YogaEdge.VERTICAL))) + .put("all", fromYogaValue(layout.getPadding(YogaEdge.ALL)))); + + data.put( + "border", + new SonarObject.Builder() + .put("left", fromFloat(layout.getBorderWidth(YogaEdge.LEFT))) + .put("top", fromFloat(layout.getBorderWidth(YogaEdge.TOP))) + .put("right", fromFloat(layout.getBorderWidth(YogaEdge.RIGHT))) + .put("bottom", fromFloat(layout.getBorderWidth(YogaEdge.BOTTOM))) + .put("start", fromFloat(layout.getBorderWidth(YogaEdge.START))) + .put("end", fromFloat(layout.getBorderWidth(YogaEdge.END))) + .put("horizontal", fromFloat(layout.getBorderWidth(YogaEdge.HORIZONTAL))) + .put("vertical", fromFloat(layout.getBorderWidth(YogaEdge.VERTICAL))) + .put("all", fromFloat(layout.getBorderWidth(YogaEdge.ALL)))); + + data.put( + "position", + new SonarObject.Builder() + .put("left", fromYogaValue(layout.getPosition(YogaEdge.LEFT))) + .put("top", fromYogaValue(layout.getPosition(YogaEdge.TOP))) + .put("right", fromYogaValue(layout.getPosition(YogaEdge.RIGHT))) + .put("bottom", fromYogaValue(layout.getPosition(YogaEdge.BOTTOM))) + .put("start", fromYogaValue(layout.getPosition(YogaEdge.START))) + .put("end", fromYogaValue(layout.getPosition(YogaEdge.END))) + .put("horizontal", fromYogaValue(layout.getPosition(YogaEdge.HORIZONTAL))) + .put("vertical", fromYogaValue(layout.getPosition(YogaEdge.VERTICAL))) + .put("all", fromYogaValue(layout.getPosition(YogaEdge.ALL)))); + + return data.build(); + } + + @Nullable + private static SonarObject getPropData(DebugComponent node) { + if (node.isInternalComponent()) { + return null; + } + + final Component component = node.getComponent(); + final SonarObject.Builder props = new SonarObject.Builder(); + + boolean hasProps = false; + for (Field f : component.getClass().getDeclaredFields()) { + try { + f.setAccessible(true); + + final Prop annotation = f.getAnnotation(Prop.class); + if (annotation != null) { + switch (annotation.resType()) { + case COLOR: + props.put(f.getName(), fromColor((Integer) f.get(component))); + break; + case DRAWABLE: + props.put(f.getName(), fromDrawable((Drawable) f.get(component))); + break; + default: + if (f.get(component) != null + && PropWithDescription.class.isAssignableFrom(f.get(component).getClass())) { + final Object description = + ((PropWithDescription) f.get(component)) + .getSonarLayoutInspectorPropDescription(); + // Treat the description as immutable for now, because it's a "translation" of the + // actual prop, + // mutating them is not going to change the original prop. + if (description instanceof Map) { + final Map descriptionMap = (Map) description; + for (Map.Entry entry : descriptionMap.entrySet()) { + props.put( + entry.getKey().toString(), InspectorValue.immutable(entry.getValue())); + } + } else { + props.put(f.getName(), InspectorValue.immutable(description)); + } + } else { + if (isTypeMutable(f.getType())) { + props.put(f.getName(), InspectorValue.mutable(f.get(component))); + } else { + props.put(f.getName(), InspectorValue.immutable(f.get(component))); + } + } + break; + } + hasProps = true; + } + } catch (Exception ignored) { + } + } + + return hasProps ? props.build() : null; + } + + @Nullable + private static SonarObject getStateData(DebugComponent node) { + if (node.isInternalComponent()) { + return null; + } + + final ComponentLifecycle.StateContainer stateContainer = node.getStateContainer(); + if (stateContainer == null) { + return null; + } + + final SonarObject.Builder state = new SonarObject.Builder(); + + boolean hasState = false; + for (Field f : stateContainer.getClass().getDeclaredFields()) { + try { + f.setAccessible(true); + + final State annotation = f.getAnnotation(State.class); + if (annotation != null) { + if (isTypeMutable(f.getType())) { + state.put(f.getName(), InspectorValue.mutable(f.get(stateContainer))); + } else { + state.put(f.getName(), InspectorValue.immutable(f.get(stateContainer))); + } + hasState = true; + } + } catch (Exception ignored) { + } + } + + return hasState ? state.build() : null; + } + + private static boolean isTypeMutable(Class type) { + if (type == int.class || type == Integer.class) { + return true; + } else if (type == long.class || type == Long.class) { + return true; + } else if (type == float.class || type == Float.class) { + return true; + } else if (type == double.class || type == Double.class) { + return true; + } else if (type == boolean.class || type == Boolean.class) { + return true; + } else if (type.isAssignableFrom(String.class)) { + return true; + } + return false; + } + + @Nullable + private static SonarObject getAccessibilityData(DebugComponent node) { + final DebugLayoutNode layout = node.getLayoutNode(); + if (layout == null) { + return null; + } + + final View hostView = node.getComponentHost(); + final SonarObject.Builder accessibilityProps = new SonarObject.Builder(); + + // This needs to be an empty string to be mutable. See t20470623. + final CharSequence contentDescription = + layout.getContentDescription() != null ? layout.getContentDescription() : ""; + accessibilityProps.put("content-description", InspectorValue.mutable(contentDescription)); + accessibilityProps.put("focusable", InspectorValue.mutable(layout.getFocusable())); + accessibilityProps.put( + "important-for-accessibility", + AccessibilityUtil.sImportantForAccessibilityMapping.get( + layout.getImportantForAccessibility())); + + // No host view exists, so this component is inherently not accessible. Add the reason why this + // is the case and then return. + if (hostView == node.getLithoView() || hostView == null) { + final int importantForAccessibility = layout.getImportantForAccessibility(); + final boolean isAccessibilityEnabled = + AccessibilityUtil.isAccessibilityEnabled(node.getContext()); + String ignoredReason; + + if (!isAccessibilityEnabled) { + ignoredReason = "No accessibility service is running."; + } else if (importantForAccessibility == IMPORTANT_FOR_ACCESSIBILITY_NO) { + ignoredReason = "Component has importantForAccessibility set to NO."; + } else if (importantForAccessibility == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) { + ignoredReason = "Component has importantForAccessibility set to NO_HIDE_DESCENDANTS."; + } else { + ignoredReason = "Component does not have content, or accessibility handlers."; + } + + accessibilityProps.put("talkback-ignored", true); + accessibilityProps.put("talkback-ignored-reasons", ignoredReason); + + return accessibilityProps.build(); + } + + accessibilityProps.put( + "node-info", AccessibilityUtil.getAccessibilityNodeInfoProperties(hostView)); + AccessibilityUtil.addTalkbackProperties(accessibilityProps, hostView); + + return accessibilityProps.build(); + } + + @Override + public void setValue(DebugComponent node, String[] path, SonarDynamic value) { + List> overrides = mOverrides.get(node.getGlobalKey()); + if (overrides == null) { + overrides = new ArrayList<>(); + mOverrides.put(node.getGlobalKey(), overrides); + } + overrides.add(new Pair<>(path, value)); + + node.setOverrider(mOverrider); + node.rerender(); + } + + @Override + public List> getAttributes(DebugComponent node) { + final List> attributes = new ArrayList<>(); + final String key = node.getKey(); + final String testKey = node.getTestKey(); + + if (key != null && key.trim().length() > 0) { + attributes.add(new Named<>("key", key)); + } + + if (testKey != null && testKey.trim().length() > 0) { + attributes.add(new Named<>("testKey", testKey)); + } + + return attributes; + } + + @Override + public void setHighlighted(DebugComponent node, boolean selected) { + final LithoView lithoView = node.getLithoView(); + if (lithoView == null) { + return; + } + + if (!selected) { + HighlightedOverlay.removeHighlight(lithoView); + return; + } + + final DebugLayoutNode layout = node.getLayoutNode(); + final boolean hasNode = layout != null; + final Rect margin; + if (!node.isRoot()) { + margin = + new Rect( + hasNode ? (int) layout.getResultMargin(YogaEdge.START) : 0, + hasNode ? (int) layout.getResultMargin(YogaEdge.TOP) : 0, + hasNode ? (int) layout.getResultMargin(YogaEdge.END) : 0, + hasNode ? (int) layout.getResultMargin(YogaEdge.BOTTOM) : 0); + } else { + // Margin not applied if you're at the root + margin = new Rect(); + } + + final Rect padding = + new Rect( + hasNode ? (int) layout.getResultPadding(YogaEdge.START) : 0, + hasNode ? (int) layout.getResultPadding(YogaEdge.TOP) : 0, + hasNode ? (int) layout.getResultPadding(YogaEdge.END) : 0, + hasNode ? (int) layout.getResultPadding(YogaEdge.BOTTOM) : 0); + + final Rect contentBounds = node.getBoundsInLithoView(); + HighlightedOverlay.setHighlighted(lithoView, margin, padding, contentBounds); + } + + @Override + public void hitTest(DebugComponent node, Touch touch) { + for (int i = getChildCount(node) - 1; i >= 0; i--) { + final Object child = getChildAt(node, i); + if (child instanceof DebugComponent) { + final DebugComponent componentChild = (DebugComponent) child; + final Rect bounds = componentChild.getBounds(); + + if (touch.containedIn(bounds.left, bounds.top, bounds.right, bounds.bottom)) { + touch.continueWithOffset(i, bounds.left, bounds.top); + return; + } + } else if (child instanceof View || child instanceof Drawable) { + // Components can only mount one view or drawable and its bounds are the same as the + // hosting component. + touch.continueWithOffset(i, 0, 0); + return; + } + } + + touch.finish(); + } + + @Override + public String getDecoration(DebugComponent node) throws Exception { + if (node.getComponent() != null) { + NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass()); + if (componentDescriptor.getClass() != ObjectDescriptor.class) { + return componentDescriptor.getDecoration(node.getComponent()); + } + } + return "litho"; + } + + @Override + public boolean matches(String query, DebugComponent node) throws Exception { + NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } + + private static void applyAccessibilityOverride( + DebugLayoutNode node, String key, SonarDynamic value) { + switch (key) { + case "focusable": + node.setFocusable(value.asBoolean()); + break; + case "important-for-accessibility": + node.setImportantForAccessibility( + AccessibilityUtil.sImportantForAccessibilityMapping.get(value.asString())); + break; + case "content-description": + node.setContentDescription(value.asString()); + break; + } + } + + private static void applyLayoutOverride(DebugLayoutNode node, String[] path, SonarDynamic value) { + switch (path[0]) { + case "background": + node.setBackgroundColor(value.asInt()); + break; + case "foreground": + node.setForegroundColor(value.asInt()); + break; + case "direction": + node.setLayoutDirection(YogaDirection.valueOf(value.asString().toUpperCase())); + break; + case "flex-direction": + node.setFlexDirection(YogaFlexDirection.valueOf(value.asString().toUpperCase())); + break; + case "justify-content": + node.setJustifyContent(YogaJustify.valueOf(value.asString().toUpperCase())); + break; + case "align-items": + node.setAlignItems(YogaAlign.valueOf(value.asString().toUpperCase())); + break; + case "align-self": + node.setAlignSelf(YogaAlign.valueOf(value.asString().toUpperCase())); + break; + case "align-content": + node.setAlignContent(YogaAlign.valueOf(value.asString().toUpperCase())); + break; + case "position-type": + node.setPositionType(YogaPositionType.valueOf(value.asString().toUpperCase())); + break; + case "flex-grow": + node.setFlexGrow(value.asFloat()); + break; + case "flex-shrink": + node.setFlexShrink(value.asFloat()); + break; + case "flex-basis": + node.setFlexBasis(YogaValue.parse(value.asString())); + break; + case "width": + node.setWidth(YogaValue.parse(value.asString())); + break; + case "min-width": + node.setMinWidth(YogaValue.parse(value.asString())); + break; + case "max-width": + node.setMaxWidth(YogaValue.parse(value.asString())); + break; + case "height": + node.setHeight(YogaValue.parse(value.asString())); + break; + case "min-height": + node.setMinHeight(YogaValue.parse(value.asString())); + break; + case "max-height": + node.setMaxHeight(YogaValue.parse(value.asString())); + break; + case "aspect-ratio": + node.setAspectRatio(value.asFloat()); + break; + case "margin": + node.setMargin(edgeFromString(path[1]), YogaValue.parse(value.asString())); + break; + case "padding": + node.setPadding(edgeFromString(path[1]), YogaValue.parse(value.asString())); + break; + case "border": + node.setBorderWidth(edgeFromString(path[1]), value.asFloat()); + break; + case "position": + node.setPosition(edgeFromString(path[1]), YogaValue.parse(value.asString())); + break; + } + } + + private static YogaEdge edgeFromString(String s) { + return YogaEdge.valueOf(s.toUpperCase()); + } + + private static void applyReflectiveOverride(Object o, String key, SonarDynamic dynamic) { + try { + final Field field = o.getClass().getDeclaredField(key); + field.setAccessible(true); + + final Class type = field.getType(); + + Object value = null; + if (type == int.class || type == Integer.class) { + value = dynamic.asInt(); + } else if (type == long.class || type == Long.class) { + value = dynamic.asLong(); + } else if (type == float.class || type == Float.class) { + value = dynamic.asFloat(); + } else if (type == double.class || type == Double.class) { + value = dynamic.asDouble(); + } else if (type == boolean.class || type == Boolean.class) { + value = dynamic.asBoolean(); + } else if (type.isAssignableFrom(String.class)) { + value = dynamic.asString(); + } + + if (value != null) { + field.set(o, value); + } + } catch (Exception ignored) { + } + } + + private static InspectorValue fromDrawable(Drawable d) { + if (d instanceof ColorDrawable) { + return InspectorValue.mutable(Color, ((ColorDrawable) d).getColor()); + } + return InspectorValue.mutable(Color, 0); + } + + private static InspectorValue fromReference( + ComponentContext c, Reference r) { + if (r == null) { + return fromDrawable(null); + } + + final T d = Reference.acquire(c, r); + final InspectorValue v = fromDrawable(d); + Reference.release(c, d, r); + return v; + } + + private static InspectorValue fromFloat(float f) { + if (Float.isNaN(f)) { + return InspectorValue.mutable(Enum, "undefined"); + } + return InspectorValue.mutable(Number, f); + } + + private static InspectorValue fromYogaValue(YogaValue v) { + // TODO add support for Type.Dimension or similar + return InspectorValue.mutable(Enum, v.toString()); + } + + private static InspectorValue fromColor(int color) { + return InspectorValue.mutable(Color, color); + } +} diff --git a/android/plugins/inspector/litho-sonar/LithoSonarDescriptors.java b/android/plugins/inspector/litho-sonar/LithoSonarDescriptors.java new file mode 100644 index 000000000..773a3d89c --- /dev/null +++ b/android/plugins/inspector/litho-sonar/LithoSonarDescriptors.java @@ -0,0 +1,15 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.litho.sonar; + +import com.facebook.litho.DebugComponent; +import com.facebook.litho.LithoView; +import com.facebook.sonar.plugins.inspector.DescriptorMapping; + +public final class LithoSonarDescriptors { + + public static void add(DescriptorMapping descriptorMapping) { + descriptorMapping.register(LithoView.class, new LithoViewDescriptor()); + descriptorMapping.register(DebugComponent.class, new DebugComponentDescriptor()); + } +} diff --git a/android/plugins/inspector/litho-sonar/LithoViewDescriptor.java b/android/plugins/inspector/litho-sonar/LithoViewDescriptor.java new file mode 100644 index 000000000..4cb96cf3b --- /dev/null +++ b/android/plugins/inspector/litho-sonar/LithoViewDescriptor.java @@ -0,0 +1,111 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.litho.sonar; + +import android.graphics.Rect; +import android.view.ViewGroup; +import com.facebook.litho.DebugComponent; +import com.facebook.litho.LithoView; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.Named; +import com.facebook.sonar.plugins.inspector.NodeDescriptor; +import com.facebook.sonar.plugins.inspector.Touch; +import java.util.ArrayList; +import java.util.List; + +public class LithoViewDescriptor extends NodeDescriptor { + + @Override + public void init(LithoView node) throws Exception { + node.setOnDirtyMountListener( + new LithoView.OnDirtyMountListener() { + @Override + public void onDirtyMount(LithoView view) { + invalidate(view); + } + }); + } + + @Override + public String getId(LithoView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class); + return descriptor.getId(node); + } + + @Override + public String getName(LithoView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class); + return descriptor.getName(node); + } + + @Override + public int getChildCount(LithoView node) { + return DebugComponent.getRootInstance(node) == null ? 0 : 1; + } + + @Override + public Object getChildAt(LithoView node, int index) { + return DebugComponent.getRootInstance(node); + } + + @Override + public List> getData(LithoView node) throws Exception { + final List> props = new ArrayList<>(); + final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class); + final Rect mountedBounds = node.getPreviousMountBounds(); + + props.add( + 0, + new Named<>( + "LithoView", + new SonarObject.Builder() + .put( + "mountbounds", + new SonarObject.Builder() + .put("left", mountedBounds.left) + .put("top", mountedBounds.top) + .put("right", mountedBounds.right) + .put("bottom", mountedBounds.bottom)) + .build())); + + props.addAll(descriptor.getData(node)); + + return props; + } + + @Override + public void setValue(LithoView node, String[] path, SonarDynamic value) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class); + descriptor.setValue(node, path, value); + } + + @Override + public List> getAttributes(LithoView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class); + return descriptor.getAttributes(node); + } + + @Override + public void setHighlighted(LithoView node, boolean selected) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class); + descriptor.setHighlighted(node, selected); + } + + @Override + public void hitTest(LithoView node, Touch touch) { + touch.continueWithOffset(0, 0, 0); + } + + @Override + public String getDecoration(LithoView node) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class); + return descriptor.getDecoration(node); + } + + @Override + public boolean matches(String query, LithoView node) throws Exception { + NodeDescriptor descriptor = descriptorForClass(Object.class); + return descriptor.matches(query, node); + } +} diff --git a/android/plugins/inspector/litho-sonar/PropWithDescription.java b/android/plugins/inspector/litho-sonar/PropWithDescription.java new file mode 100644 index 000000000..a56714366 --- /dev/null +++ b/android/plugins/inspector/litho-sonar/PropWithDescription.java @@ -0,0 +1,8 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.litho.sonar; + +public interface PropWithDescription { + + Object getSonarLayoutInspectorPropDescription(); +} diff --git a/android/plugins/network/NetworkReporter.java b/android/plugins/network/NetworkReporter.java new file mode 100644 index 000000000..e74e3032e --- /dev/null +++ b/android/plugins/network/NetworkReporter.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.network; + +import java.util.ArrayList; +import java.util.List; + +public interface NetworkReporter { + void reportRequest(RequestInfo requestInfo); + + void reportResponse(ResponseInfo responseInfo); + + public class Header { + public final String name; + public final String value; + + public Header(final String name, final String value) { + this.name = name; + this.value = value; + } + + @Override + public String toString() { + return "Header{" + name + ": " + value + "}"; + } + } + + public class RequestInfo { + public String requestId; + public long timeStamp; + public List

headers = new ArrayList<>(); + public String method; + public String uri; + public byte[] body; + + public Header getFirstHeader(final String name) { + for (Header header : headers) { + if (name.equalsIgnoreCase(header.name)) { + return header; + } + } + return null; + } + } + + public class ResponseInfo { + public String requestId; + public long timeStamp; + public int statusCode; + public String statusReason; + public List
headers = new ArrayList<>(); + public byte[] body; + + public Header getFirstHeader(final String name) { + for (Header header : headers) { + if (name.equalsIgnoreCase(header.name)) { + return header; + } + } + return null; + } + } +} diff --git a/android/plugins/network/NetworkResponseFormatter.java b/android/plugins/network/NetworkResponseFormatter.java new file mode 100644 index 000000000..728a2a149 --- /dev/null +++ b/android/plugins/network/NetworkResponseFormatter.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.network; + +import com.facebook.sonar.plugins.network.NetworkReporter.ResponseInfo; + +public interface NetworkResponseFormatter { + + interface OnCompletionListener { + void onCompletion(String json); + } + + boolean shouldFormat(ResponseInfo response); + + void format(ResponseInfo response, OnCompletionListener onCompletionListener); +} diff --git a/android/plugins/network/NetworkSonarPlugin.java b/android/plugins/network/NetworkSonarPlugin.java new file mode 100644 index 000000000..1a2ebf760 --- /dev/null +++ b/android/plugins/network/NetworkSonarPlugin.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.network; + +import android.util.Base64; +import com.facebook.sonar.core.ErrorReportingRunnable; +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.common.BufferingSonarPlugin; +import java.util.List; + +public class NetworkSonarPlugin extends BufferingSonarPlugin implements NetworkReporter { + public static final String ID = "Network"; + + private final List mFormatters; + + public NetworkSonarPlugin() { + this(null); + } + + public NetworkSonarPlugin(List formatters) { + this.mFormatters = formatters; + } + + @Override + public String getId() { + return ID; + } + + @Override + public void reportRequest(RequestInfo requestInfo) { + final SonarObject request = + new SonarObject.Builder() + .put("id", requestInfo.requestId) + .put("timestamp", requestInfo.timeStamp) + .put("method", requestInfo.method) + .put("url", requestInfo.uri) + .put("headers", toSonarObject(requestInfo.headers)) + .put("data", toBase64(requestInfo.body)) + .build(); + + send("newRequest", request); + } + + @Override + public void reportResponse(final ResponseInfo responseInfo) { + final Runnable job = + new ErrorReportingRunnable(getConnection()) { + @Override + protected void runOrThrow() throws Exception { + if (shouldStripResponseBody(responseInfo)) { + responseInfo.body = null; + } + + final SonarObject response = + new SonarObject.Builder() + .put("id", responseInfo.requestId) + .put("timestamp", responseInfo.timeStamp) + .put("status", responseInfo.statusCode) + .put("reason", responseInfo.statusReason) + .put("headers", toSonarObject(responseInfo.headers)) + .put("data", toBase64(responseInfo.body)) + .build(); + + send("newResponse", response); + } + }; + + if (mFormatters != null) { + for (NetworkResponseFormatter formatter : mFormatters) { + if (formatter.shouldFormat(responseInfo)) { + formatter.format( + responseInfo, + new NetworkResponseFormatter.OnCompletionListener() { + @Override + public void onCompletion(final String json) { + responseInfo.body = json.getBytes(); + job.run(); + } + }); + return; + } + } + } + + job.run(); + } + + private String toBase64(byte[] bytes) { + if (bytes == null) { + return null; + } + return new String(Base64.encode(bytes, Base64.DEFAULT)); + } + + private SonarArray toSonarObject(List
headers) { + final SonarArray.Builder list = new SonarArray.Builder(); + + for (Header header : headers) { + list.put(new SonarObject.Builder().put("key", header.name).put("value", header.value)); + } + + return list.build(); + } + + private static boolean shouldStripResponseBody(ResponseInfo responseInfo) { + final Header contentType = responseInfo.getFirstHeader("content-type"); + if (contentType == null) { + return false; + } + + return contentType.value.contains("image/") + || contentType.value.contains("video/") + || contentType.value.contains("application/zip"); + } +} diff --git a/android/plugins/network/SonarOkhttpInterceptor.java b/android/plugins/network/SonarOkhttpInterceptor.java new file mode 100644 index 000000000..1ca385555 --- /dev/null +++ b/android/plugins/network/SonarOkhttpInterceptor.java @@ -0,0 +1,106 @@ +// Copyright 2004-present Facebook. All Rights Reserved. +package com.facebook.sonar.plugins.network; + +import android.util.Log; +import com.facebook.sonar.plugins.network.NetworkReporter.RequestInfo; +import com.facebook.sonar.plugins.network.NetworkReporter.ResponseInfo; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; +import javax.annotation.Nullable; +import okhttp3.Headers; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; + +public class SonarOkhttpInterceptor implements Interceptor { + + public @Nullable NetworkSonarPlugin plugin; + + public SonarOkhttpInterceptor() { + this.plugin = null; + } + + public SonarOkhttpInterceptor(NetworkSonarPlugin plugin) { + this.plugin = plugin; + } + + @Override + public Response intercept(Interceptor.Chain chain) throws IOException { + Request request = chain.request(); + int randInt = randInt(1, Integer.MAX_VALUE); + plugin.reportRequest(convertRequest(request, randInt)); + Response response = chain.proceed(request); + ResponseBody body = response.body(); + ResponseInfo responseInfo = convertResponse(response, body, randInt); + plugin.reportResponse(responseInfo); + // Creating new response as can't used response.body() more than once + return response + .newBuilder() + .body(ResponseBody.create(body.contentType(), responseInfo.body)) + .build(); + } + + private static byte[] bodyToByteArray(final Request request) { + + try { + final Request copy = request.newBuilder().build(); + final Buffer buffer = new Buffer(); + copy.body().writeTo(buffer); + return buffer.readByteArray(); + } catch (final IOException e) { + return e.getMessage().getBytes(); + } + } + + private RequestInfo convertRequest(Request request, int identifier) { + List headers = convertHeader(request.headers()); + RequestInfo info = new RequestInfo(); + info.requestId = String.valueOf(identifier); + info.timeStamp = System.currentTimeMillis(); + info.headers = headers; + info.method = request.method(); + info.uri = request.url().toString(); + if (request.body() != null) { + info.body = bodyToByteArray(request); + } + + return info; + } + + private ResponseInfo convertResponse(Response response, ResponseBody body, int identifier) { + + List headers = convertHeader(response.headers()); + ResponseInfo info = new ResponseInfo(); + info.requestId = String.valueOf(identifier); + info.timeStamp = response.receivedResponseAtMillis(); + info.statusCode = response.code(); + info.headers = headers; + try { + info.body = body.bytes(); + } catch (IOException e) { + Log.e("Sonar", e.toString()); + } + return info; + } + + private List convertHeader(Headers headers) { + List list = new ArrayList<>(); + + Set keys = headers.names(); + for (String key : keys) { + list.add(new NetworkReporter.Header(key, headers.get(key))); + } + return list; + } + + private int randInt(int min, int max) { + Random rand = new Random(); + int randomNum = rand.nextInt((max - min) + 1) + min; + return randomNum; + } +} diff --git a/android/plugins/sharedpreferences/SharedPreferencesSonarPlugin.java b/android/plugins/sharedpreferences/SharedPreferencesSonarPlugin.java new file mode 100644 index 000000000..fef931a48 --- /dev/null +++ b/android/plugins/sharedpreferences/SharedPreferencesSonarPlugin.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +package com.facebook.sonar.plugins.sharedpreferences; + +import static android.content.Context.MODE_PRIVATE; + +import android.content.Context; +import android.content.SharedPreferences; +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarPlugin; +import com.facebook.sonar.core.SonarReceiver; +import com.facebook.sonar.core.SonarResponder; +import java.util.Map; + +public class SharedPreferencesSonarPlugin implements SonarPlugin { + + private SonarConnection mConnection; + private final SharedPreferences mSharedPreferences; + + public SharedPreferencesSonarPlugin(Context context) { + mSharedPreferences = context.getSharedPreferences(context.getPackageName(), MODE_PRIVATE); + + mSharedPreferences.registerOnSharedPreferenceChangeListener( + new SharedPreferences.OnSharedPreferenceChangeListener() { + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (mConnection != null) { + mConnection.send( + "sharedPreferencesChange", + new SonarObject.Builder() + .put("name", key) + .put("deleted", !mSharedPreferences.contains(key)) + .put("time", System.currentTimeMillis()) + .put("value", mSharedPreferences.getAll().get(key)) + .build()); + } + } + }); + } + + @Override + public String getId() { + return "Preferences"; + } + + private SonarObject getSharedPreferencesObject() { + final SonarObject.Builder builder = new SonarObject.Builder(); + final Map map = mSharedPreferences.getAll(); + + for (Map.Entry entry : map.entrySet()) { + final Object val = entry.getValue(); + builder.put(entry.getKey(), val); + } + + return builder.build(); + } + + @Override + public void onConnect(SonarConnection connection) { + mConnection = connection; + + connection.receive( + "getSharedPreferences", + new SonarReceiver() { + @Override + public void onReceive(SonarObject params, SonarResponder responder) { + responder.success(getSharedPreferencesObject()); + } + }); + + connection.receive( + "setSharedPreference", + new SonarReceiver() { + @Override + public void onReceive(SonarObject params, SonarResponder responder) + throws IllegalArgumentException { + + String preferenceName = params.getString("preferenceName"); + Object originalValue = mSharedPreferences.getAll().get(preferenceName); + SharedPreferences.Editor editor = mSharedPreferences.edit(); + + if (originalValue instanceof Boolean) { + editor.putBoolean(preferenceName, params.getBoolean("preferenceValue")); + } else if (originalValue instanceof Long) { + editor.putLong(preferenceName, params.getLong("preferenceValue")); + } else if (originalValue instanceof Integer) { + editor.putInt(preferenceName, params.getInt("preferenceValue")); + } else if (originalValue instanceof Float) { + editor.putFloat(preferenceName, params.getFloat("preferenceValue")); + } else if (originalValue instanceof String) { + editor.putString(preferenceName, params.getString("preferenceValue")); + } else { + throw new IllegalArgumentException("Type not supported: " + preferenceName); + } + + editor.apply(); + + responder.success(getSharedPreferencesObject()); + } + }); + } + + @Override + public void onDisconnect() { + mConnection = null; + } +} diff --git a/android/sample/AndroidManifest.xml b/android/sample/AndroidManifest.xml new file mode 100644 index 000000000..5d52b59f9 --- /dev/null +++ b/android/sample/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/sample/build.gradle b/android/sample/build.gradle new file mode 100644 index 000000000..5d0794a74 --- /dev/null +++ b/android/sample/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'com.android.application' + +android { + + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion + defaultConfig { + minSdkVersion rootProject.minSdkVersion + testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' + applicationId "com.facebook.sonar.sample" + } + + lintOptions { + abortOnError false + } + sourceSets { + main { + manifest.srcFile './AndroidManifest.xml' + java { + srcDir 'src' + } + res { + srcDir 'res' + } + } + } + packagingOptions { + pickFirst 'lib/armeabi-v7a/libfb.so' + pickFirst 'lib/x86/libfb.so' + pickFirst 'lib/x86_64/libfb.so' + pickFirst 'lib/arm64-v8a/libfb.so' + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:26.1.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + implementation 'com.android.support:design:26.1.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + // ... + // Litho + implementation 'com.facebook.litho:litho-core:0.15.0' + implementation 'com.facebook.litho:litho-widget:0.15.0' + compileOnly 'com.facebook.litho:litho-annotations:0.15.0' + + annotationProcessor 'com.facebook.litho:litho-processor:0.15.0' + + // SoLoader + implementation 'com.facebook.soloader:soloader:0.4.1' + + // For integration with Fresco + implementation 'com.facebook.litho:litho-fresco:0.15.0' + + // For testing + testImplementation 'com.facebook.litho:litho-testing:0.15.0' + + implementation 'com.squareup.okhttp3:okhttp:3.10.0' + implementation project(':android') + //implementation project(':sonar') +} diff --git a/android/sample/debug.keystore b/android/sample/debug.keystore new file mode 100644 index 000000000..65cb6bb32 Binary files /dev/null and b/android/sample/debug.keystore differ diff --git a/android/sample/debug.keystore.properties b/android/sample/debug.keystore.properties new file mode 100644 index 000000000..3c06c8e4a --- /dev/null +++ b/android/sample/debug.keystore.properties @@ -0,0 +1,3 @@ +key.alias=androiddebugkey +key.store.password=android +key.alias.password=android diff --git a/android/sample/res/drawable-hdpi/ic_launcher.png b/android/sample/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..cc0c1f1de Binary files /dev/null and b/android/sample/res/drawable-hdpi/ic_launcher.png differ diff --git a/android/sample/res/drawable-mdpi/ic_launcher.png b/android/sample/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..840d8da8a Binary files /dev/null and b/android/sample/res/drawable-mdpi/ic_launcher.png differ diff --git a/android/sample/res/drawable-xhdpi/ic_launcher.png b/android/sample/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..cf52c668b Binary files /dev/null and b/android/sample/res/drawable-xhdpi/ic_launcher.png differ diff --git a/android/sample/res/drawable-xxhdpi/ic_launcher.png b/android/sample/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..ed9c79e25 Binary files /dev/null and b/android/sample/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/android/sample/res/drawable-xxxhdpi/ic_launcher.png b/android/sample/res/drawable-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4485f7cc1 Binary files /dev/null and b/android/sample/res/drawable-xxxhdpi/ic_launcher.png differ diff --git a/android/sample/res/values/strings.xml b/android/sample/res/values/strings.xml new file mode 100644 index 000000000..16078fc00 --- /dev/null +++ b/android/sample/res/values/strings.xml @@ -0,0 +1,5 @@ + + + + Sonar + diff --git a/android/sample/res/values/styles.xml b/android/sample/res/values/styles.xml new file mode 100644 index 000000000..961808643 --- /dev/null +++ b/android/sample/res/values/styles.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/android/sample/src/sonar/com/facebook/sonar/sample/MainActivity.java b/android/sample/src/sonar/com/facebook/sonar/sample/MainActivity.java new file mode 100644 index 000000000..656a670cd --- /dev/null +++ b/android/sample/src/sonar/com/facebook/sonar/sample/MainActivity.java @@ -0,0 +1,22 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.sonar.sample; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import com.facebook.litho.ComponentContext; +import com.facebook.litho.LithoView; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final ComponentContext c = new ComponentContext(this); + setContentView( + LithoView.create( + c, + RootComponent.create(c).build())); + } +} diff --git a/android/sample/src/sonar/com/facebook/sonar/sample/RootComponentSpec.java b/android/sample/src/sonar/com/facebook/sonar/sample/RootComponentSpec.java new file mode 100644 index 000000000..aec4b7828 --- /dev/null +++ b/android/sample/src/sonar/com/facebook/sonar/sample/RootComponentSpec.java @@ -0,0 +1,105 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.sonar.sample; + +import com.facebook.litho.Column; +import com.facebook.litho.Component; +import com.facebook.litho.ComponentContext; +import com.facebook.litho.annotations.LayoutSpec; +import com.facebook.litho.annotations.OnCreateLayout; +import com.facebook.litho.widget.Text; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.RequestBody; +import okhttp3.FormBody; +import okhttp3.FormBody.Builder; +import okhttp3.OkHttpClient; +import okhttp3.Call; +import okhttp3.Callback; +import com.facebook.litho.ClickEvent; +import android.util.Log; +import java.io.IOException; +import com.facebook.litho.annotations.OnEvent; + +@LayoutSpec +public class RootComponentSpec { + + @OnCreateLayout + static Component onCreateLayout(ComponentContext c) { + return Column.create(c) + .child( + Text.create(c) + .text("Tap to hit get request") + .key("1") + .textSizeSp(20) + .clickHandler(RootComponent.hitGetRequest(c))) + .child( + Text.create(c) + .text("Tap to hit post request") + .key("2") + .textSizeSp(20) + .clickHandler(RootComponent.hitPostRequest(c))) + .child( + Text.create(c) + .text("I m just a text") + .key("3") + .textSizeSp(20)) + .build(); + } + +@OnEvent(ClickEvent.class) + static void hitGetRequest(ComponentContext c) { + + Request request = new Request.Builder() + .url("https://api.github.com/repos/facebook/yoga") + .get() + .build(); + SonarSampleApplication.okhttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + e.printStackTrace(); + Log.d("Sonar", e.getMessage()); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful()) { + Log.d("Sonar", response.body().string()); + } else { + Log.d("Sonar", "not successful"); + } + } + }); + } + + @OnEvent(ClickEvent.class) + static void hitPostRequest(ComponentContext c) { + + RequestBody formBody = new FormBody.Builder() + .add("app", "Sonar") + .add("remarks", "Its awesome") + .build(); + + Request request = new Request.Builder() + .url("https://demo9512366.mockable.io/SonarPost") + .post(formBody) + .build(); + + SonarSampleApplication.okhttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + e.printStackTrace(); + Log.d("Sonar", e.getMessage()); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful()) { + Log.d("Sonar", response.body().string()); + } else { + Log.d("Sonar", "not successful"); + } + } + }); + } +} diff --git a/android/sample/src/sonar/com/facebook/sonar/sample/SonarSampleApplication.java b/android/sample/src/sonar/com/facebook/sonar/sample/SonarSampleApplication.java new file mode 100644 index 000000000..6acb6a5b3 --- /dev/null +++ b/android/sample/src/sonar/com/facebook/sonar/sample/SonarSampleApplication.java @@ -0,0 +1,50 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.sonar.sample; + +import android.app.Application; +import android.net.Network; +import android.support.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import com.facebook.litho.sonar.LithoSonarDescriptors; +import com.facebook.soloader.SoLoader; +import com.facebook.sonar.android.utils.SonarUtils; +import com.facebook.sonar.android.AndroidSonarClient; +import com.facebook.sonar.core.SonarClient; +import com.facebook.sonar.plugins.inspector.DescriptorMapping; +import com.facebook.sonar.plugins.inspector.InspectorSonarPlugin; +import com.facebook.sonar.plugins.network.NetworkSonarPlugin; +import com.facebook.sonar.plugins.network.SonarOkhttpInterceptor; +import com.facebook.sonar.plugins.network.NetworkResponseFormatter; +import okhttp3.OkHttpClient; +import java.util.concurrent.TimeUnit; + +public class SonarSampleApplication extends Application { + + static public OkHttpClient okhttpClient; + + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, false); + + final SonarClient client = AndroidSonarClient.getInstance(this); + final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults(); + + NetworkSonarPlugin networkPlugin = new NetworkSonarPlugin(); + SonarOkhttpInterceptor interceptor = new SonarOkhttpInterceptor(networkPlugin); + + okhttpClient = new OkHttpClient.Builder() + .addNetworkInterceptor(interceptor) + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.MINUTES) + .build(); + + LithoSonarDescriptors.add(descriptorMapping); + client.addPlugin(new InspectorSonarPlugin(this, descriptorMapping)); + client.addPlugin(networkPlugin); + client.start(); +} +} diff --git a/android/testing/SonarConnectionMock.java b/android/testing/SonarConnectionMock.java new file mode 100644 index 000000000..c0979f512 --- /dev/null +++ b/android/testing/SonarConnectionMock.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.testing; + +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarReceiver; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SonarConnectionMock implements SonarConnection { + public final Map receivers = new HashMap<>(); + public final Map> sent = new HashMap<>(); + + @Override + public void send(String method, SonarObject params) { + final List paramList; + if (sent.containsKey(method)) { + paramList = sent.get(method); + } else { + paramList = new ArrayList<>(); + sent.put(method, paramList); + } + + paramList.add(params); + } + + @Override + public void send(String method, SonarArray params) { + final List paramList; + if (sent.containsKey(method)) { + paramList = sent.get(method); + } else { + paramList = new ArrayList<>(); + sent.put(method, paramList); + } + + paramList.add(params); + } + + @Override + public void reportError(Throwable throwable) {} + + @Override + public void receive(String method, SonarReceiver receiver) { + receivers.put(method, receiver); + } +} diff --git a/android/testing/SonarResponderMock.java b/android/testing/SonarResponderMock.java new file mode 100644 index 000000000..76971b3ef --- /dev/null +++ b/android/testing/SonarResponderMock.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.testing; + +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.core.SonarResponder; +import java.util.LinkedList; +import java.util.List; + +public class SonarResponderMock implements SonarResponder { + public final List successes = new LinkedList<>(); + public final List errors = new LinkedList<>(); + + @Override + public void success(SonarObject response) { + successes.add(response); + } + + @Override + public void success(SonarArray response) { + successes.add(response); + } + + @Override + public void success() { + successes.add(new SonarObject.Builder().build()); + } + + @Override + public void error(SonarObject response) { + errors.add(response); + } +} diff --git a/android/tests/plugins/console/ConsoleSonarPluginTest.java b/android/tests/plugins/console/ConsoleSonarPluginTest.java new file mode 100644 index 000000000..023029aee --- /dev/null +++ b/android/tests/plugins/console/ConsoleSonarPluginTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.console; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.testing.SonarConnectionMock; +import com.facebook.sonar.testing.SonarResponderMock; +import com.facebook.testing.robolectric.v3.WithTestDefaultsRunner; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(WithTestDefaultsRunner.class) +public class ConsoleSonarPluginTest { + + SonarConnectionMock connection; + SonarResponderMock responder; + + @Before + public void setup() throws Exception { + JavascriptEnvironment jsEnvironment = new JavascriptEnvironment(); + final ConsoleSonarPlugin plugin = new ConsoleSonarPlugin(jsEnvironment); + connection = new SonarConnectionMock(); + responder = new SonarResponderMock(); + plugin.onConnect(connection); + } + + @Test + public void simpleExpressionShouldEvaluateCorrectly() throws Exception { + + receiveScript("2 + 2"); + assertThat( + responder.successes, + hasItem(new SonarObject.Builder().put("value", 4).put("type", "json").build())); + } + + private void receiveScript(String a) throws Exception { + SonarObject getValue = new SonarObject.Builder().put("command", a).build(); + connection.receivers.get("executeCommand").onReceive(getValue, responder); + } +} diff --git a/android/tests/plugins/console/JavascriptSessionTest.java b/android/tests/plugins/console/JavascriptSessionTest.java new file mode 100644 index 000000000..0f02200e1 --- /dev/null +++ b/android/tests/plugins/console/JavascriptSessionTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.console; + +import static org.junit.Assert.assertEquals; + +import com.facebook.testing.robolectric.v3.WithTestDefaultsRunner; +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.HashMap; +import org.json.JSONObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mozilla.javascript.ContextFactory; + +@RunWith(WithTestDefaultsRunner.class) +public class JavascriptSessionTest { + + ContextFactory mContextFactory = new ContextFactory(); + + @Test + public void testSimpleExpressionsEvaluate() throws Exception { + JavascriptSession session = + new JavascriptSession(mContextFactory, Collections.emptyMap()); + JSONObject json = session.evaluateCommand("2+2-1"); + assertEquals(3, json.getInt("value")); + } + + @Test + public void testStatePersistsBetweenCommands() throws Exception { + JavascriptSession session = + new JavascriptSession(mContextFactory, Collections.emptyMap()); + session.evaluateCommand("var x = 10;"); + JSONObject json = session.evaluateCommand("x"); + assertEquals(10, json.getInt("value")); + } + + @Test + public void testVariablesGetBoundCorrectly() throws Exception { + JavascriptSession session = + new JavascriptSession( + mContextFactory, + ImmutableMap.of( + "a", 2, + "b", 2)); + JSONObject json = session.evaluateCommand("a+b"); + assertEquals("json", json.getString("type")); + assertEquals(4, json.getInt("value")); + } + + @Test + public void testNumberEvaluation() throws Exception { + assertEquals(4, evaluateWithNoGlobals("4").getInt("value")); + } + + @Test + public void testStringEvaluation() throws Exception { + assertEquals("hello", evaluateWithNoGlobals("\"hello\"").getString("value")); + } + + @Test + public void testJavaObjectEvaluation() throws Exception { + JavascriptSession session = + new JavascriptSession( + mContextFactory, + ImmutableMap.of("object", new HashMap())); + JSONObject json = session.evaluateCommand("object"); + assertEquals("javaObject", json.getString("type")); + assertEquals("{}", json.getJSONObject("value").getString("toString")); + } + + @Test + public void testJavaMethodEvaluation() throws Exception { + JavascriptSession session = + new JavascriptSession( + mContextFactory, + ImmutableMap.of("object", new HashMap())); + JSONObject json = session.evaluateCommand("object.get"); + assertEquals("method", json.getString("type")); + } + + @Test + public void testJsFunctionEvaluation() throws Exception { + JSONObject json = evaluateWithNoGlobals("function() {}"); + assertEquals("function", json.getString("type")); + assertEquals("function(){}", removeWhitespace(json.getString("value"))); + } + + @Test + public void testNullEvaluation() throws Exception { + assertEquals("null", evaluateWithNoGlobals("null").getString("type")); + assertEquals("null", evaluateWithNoGlobals("undefined").getString("type")); + } + + private static String removeWhitespace(String input) { + return input.replaceAll("\\s", ""); + } + + private JSONObject evaluateWithNoGlobals(String input) throws Exception { + JavascriptSession session = + new JavascriptSession(mContextFactory, new HashMap()); + return session.evaluateCommand(input); + } +} diff --git a/android/tests/plugins/inspector/ApplicationWrapperTest.java b/android/tests/plugins/inspector/ApplicationWrapperTest.java new file mode 100644 index 000000000..d85412b25 --- /dev/null +++ b/android/tests/plugins/inspector/ApplicationWrapperTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.inspector; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import android.app.Activity; +import android.app.Application; +import android.app.Application.ActivityLifecycleCallbacks; +import android.os.Bundle; +import com.facebook.testing.robolectric.v3.WithTestDefaultsRunner; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +@RunWith(WithTestDefaultsRunner.class) +public class ApplicationWrapperTest { + + private ApplicationWrapper mWrapper; + private ActivityLifecycleCallbacks mCallbacks; + + @Before + public void setup() { + final Application app = Mockito.mock(Application.class); + Mockito.doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + mCallbacks = (ActivityLifecycleCallbacks) invocation.getArguments()[0]; + return null; + } + }) + .when(app) + .registerActivityLifecycleCallbacks(Mockito.any(ActivityLifecycleCallbacks.class)); + + mWrapper = new ApplicationWrapper(app); + } + + @Test + public void testActivityCreated() { + final Activity activity1 = Mockito.mock(Activity.class); + mCallbacks.onActivityCreated(activity1, Mockito.mock(Bundle.class)); + + final Activity activity2 = Mockito.mock(Activity.class); + mCallbacks.onActivityCreated(activity2, Mockito.mock(Bundle.class)); + + assertThat(mWrapper.getActivityStack().size(), equalTo(2)); + assertThat(mWrapper.getActivityStack().get(0), equalTo(activity1)); + assertThat(mWrapper.getActivityStack().get(1), equalTo(activity2)); + } + + @Test + public void testActivityPaused() { + final Activity activity1 = Mockito.mock(Activity.class); + mCallbacks.onActivityCreated(activity1, Mockito.mock(Bundle.class)); + + final Activity activity2 = Mockito.mock(Activity.class); + mCallbacks.onActivityCreated(activity2, Mockito.mock(Bundle.class)); + + mCallbacks.onActivityPaused(activity2); + + assertThat(mWrapper.getActivityStack().size(), equalTo(2)); + assertThat(mWrapper.getActivityStack().get(0), equalTo(activity1)); + assertThat(mWrapper.getActivityStack().get(1), equalTo(activity2)); + } + + @Test + public void testFinishingActivityPaused() { + final Activity activity1 = Mockito.mock(Activity.class); + mCallbacks.onActivityCreated(activity1, Mockito.mock(Bundle.class)); + + final Activity activity2 = Mockito.mock(Activity.class); + mCallbacks.onActivityCreated(activity2, Mockito.mock(Bundle.class)); + + Mockito.when(activity2.isFinishing()).thenReturn(true); + mCallbacks.onActivityPaused(activity2); + + assertThat(mWrapper.getActivityStack().size(), equalTo(1)); + assertThat(mWrapper.getActivityStack().get(0), equalTo(activity1)); + } +} diff --git a/android/tests/plugins/inspector/DescriptorMappingTest.java b/android/tests/plugins/inspector/DescriptorMappingTest.java new file mode 100644 index 000000000..19ed24765 --- /dev/null +++ b/android/tests/plugins/inspector/DescriptorMappingTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.inspector; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.testing.SonarConnectionMock; +import com.facebook.testing.robolectric.v3.WithTestDefaultsRunner; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(WithTestDefaultsRunner.class) +public class DescriptorMappingTest { + + private class TestClass {} + + private class TestSubClass extends TestClass {} + + private class TestDescriptor extends NodeDescriptor { + @Override + public void init(T node) {} + + @Override + public String getId(T node) { + return null; + } + + @Override + public String getName(T node) { + return null; + } + + @Override + public int getChildCount(T node) { + return 0; + } + + @Override + public T getChildAt(T node, int index) { + return null; + } + + @Override + public List> getData(T node) { + return null; + } + + @Override + public void setValue(T node, String[] path, SonarDynamic value) throws Exception {} + + @Override + public List> getAttributes(T node) { + return null; + } + + @Override + public void setHighlighted(T node, boolean selected) {} + + @Override + public void hitTest(T node, Touch touch) {} + + @Override + public String getDecoration(T obj) { + return null; + } + + @Override + public boolean matches(String query, T obj) { + return false; + } + } + + @Test + public void testDescriptorForRegisteredClass() { + final DescriptorMapping descriptorMapping = new DescriptorMapping(); + final NodeDescriptor descriptor1 = new TestDescriptor<>(); + final NodeDescriptor descriptor2 = new TestDescriptor<>(); + + descriptorMapping.register(TestClass.class, descriptor1); + descriptorMapping.register(TestSubClass.class, descriptor2); + + assertThat(descriptorMapping.descriptorForClass(TestSubClass.class), equalTo(descriptor2)); + } + + @Test + public void testDescriptorForRegisteredSuperClass() { + final DescriptorMapping descriptorMapping = new DescriptorMapping(); + final NodeDescriptor descriptor = new TestDescriptor<>(); + + descriptorMapping.register(TestClass.class, descriptor); + + assertThat(descriptorMapping.descriptorForClass(TestSubClass.class), equalTo(descriptor)); + } + + @Test + public void testOnConnect() { + final DescriptorMapping descriptorMapping = new DescriptorMapping(); + final NodeDescriptor descriptor = new TestDescriptor<>(); + descriptorMapping.register(TestClass.class, descriptor); + + final SonarConnection connection = new SonarConnectionMock(); + descriptorMapping.onConnect(connection); + + assertThat(descriptor.connected(), equalTo(true)); + } + + @Test + public void testOnDisconnect() { + final DescriptorMapping descriptorMapping = new DescriptorMapping(); + final NodeDescriptor descriptor = new TestDescriptor<>(); + descriptorMapping.register(TestClass.class, descriptor); + + final SonarConnection connection = new SonarConnectionMock(); + descriptorMapping.onConnect(connection); + descriptorMapping.onDisconnect(); + + assertThat(descriptor.connected(), equalTo(false)); + } +} diff --git a/android/tests/plugins/inspector/InspectorSonarPluginTest.java b/android/tests/plugins/inspector/InspectorSonarPluginTest.java new file mode 100644 index 000000000..d3e1edde0 --- /dev/null +++ b/android/tests/plugins/inspector/InspectorSonarPluginTest.java @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.inspector; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; + +import android.app.Application; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import com.facebook.sonar.core.SonarArray; +import com.facebook.sonar.core.SonarConnection; +import com.facebook.sonar.core.SonarDynamic; +import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.console.iface.NullScriptingEnvironment; +import com.facebook.sonar.plugins.console.iface.ScriptingEnvironment; +import com.facebook.sonar.plugins.inspector.InspectorSonarPlugin.TouchOverlayView; +import com.facebook.sonar.plugins.inspector.descriptors.ApplicationDescriptor; +import com.facebook.sonar.testing.SonarConnectionMock; +import com.facebook.sonar.testing.SonarResponderMock; +import com.facebook.testing.robolectric.v3.WithTestDefaultsRunner; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.robolectric.RuntimeEnvironment; + +@RunWith(WithTestDefaultsRunner.class) +public class InspectorSonarPluginTest { + + private MockApplicationDescriptor mApplicationDescriptor; + private DescriptorMapping mDescriptorMapping; + private ApplicationWrapper mApp; + private ScriptingEnvironment mScriptingEnvironment; + + @Before + public void setup() { + final Application app = Mockito.spy(RuntimeEnvironment.application); + Mockito.when(app.getApplicationContext()).thenReturn(app); + Mockito.when(app.getPackageName()).thenReturn("com.facebook.sonar"); + + mDescriptorMapping = new DescriptorMapping(); + mApplicationDescriptor = new MockApplicationDescriptor(); + mDescriptorMapping.register(ApplicationWrapper.class, mApplicationDescriptor); + mDescriptorMapping.register(TestNode.class, new TestNodeDescriptor()); + mScriptingEnvironment = new NullScriptingEnvironment(); + mApp = Mockito.spy(new ApplicationWrapper(app)); + } + + @Test + public void testOnConnect() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarConnection connection = new SonarConnectionMock(); + + plugin.onConnect(connection); + assertThat(mApplicationDescriptor.connected(), equalTo(true)); + } + + @Test + public void testOnDisconnect() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarConnection connection = new SonarConnectionMock(); + + plugin.onConnect(connection); + plugin.onDisconnect(); + assertThat(mApplicationDescriptor.connected(), equalTo(false)); + } + + @Test + public void testGetRoot() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarResponderMock responder = new SonarResponderMock(); + final SonarConnectionMock connection = new SonarConnectionMock(); + plugin.onConnect(connection); + + final TestNode root = new TestNode(); + root.id = "test"; + mApplicationDescriptor.root = root; + plugin.mGetRoot.onReceive(null, responder); + + assertThat( + responder.successes, + hasItem( + new SonarObject.Builder() + .put("id", "com.facebook.sonar") + .put("name", "com.facebook.sonar") + .put("data", new SonarObject.Builder()) + .put("children", new SonarArray.Builder().put("test")) + .put("attributes", new SonarArray.Builder()) + .put("decoration", (String) null) + .build())); + } + + @Test + public void testGetNodes() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarResponderMock responder = new SonarResponderMock(); + final SonarConnectionMock connection = new SonarConnectionMock(); + plugin.onConnect(connection); + + final TestNode root = new TestNode(); + root.id = "test"; + root.name = "test"; + mApplicationDescriptor.root = root; + + plugin.mGetRoot.onReceive(null, responder); + plugin.mGetNodes.onReceive( + new SonarObject.Builder().put("ids", new SonarArray.Builder().put("test")).build(), + responder); + + assertThat( + responder.successes, + hasItem( + new SonarObject.Builder() + .put( + "elements", + new SonarArray.Builder() + .put( + new SonarObject.Builder() + .put("id", "test") + .put("name", "test") + .put("data", new SonarObject.Builder()) + .put("children", new SonarArray.Builder()) + .put("attributes", new SonarArray.Builder()) + .put("decoration", (String) null))) + .build())); + } + + @Test + public void testGetNodesThatDontExist() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarResponderMock responder = new SonarResponderMock(); + final SonarConnectionMock connection = new SonarConnectionMock(); + plugin.onConnect(connection); + + final TestNode root = new TestNode(); + root.id = "test"; + mApplicationDescriptor.root = root; + + plugin.mGetRoot.onReceive(null, responder); + plugin.mGetNodes.onReceive( + new SonarObject.Builder().put("ids", new SonarArray.Builder().put("notest")).build(), + responder); + + assertThat( + responder.errors, + hasItem( + new SonarObject.Builder() + .put("message", "No node with given id") + .put("id", "notest") + .build())); + } + + @Test + public void testSetData() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarConnectionMock connection = new SonarConnectionMock(); + final SonarResponderMock responder = new SonarResponderMock(); + plugin.onConnect(connection); + + final TestNode root = new TestNode(); + root.id = "test"; + root.data = new SonarObject.Builder().put("prop", "value").build(); + + mApplicationDescriptor.root = root; + + plugin.mGetRoot.onReceive(null, responder); + plugin.mSetData.onReceive( + new SonarObject.Builder() + .put("id", "test") + .put("path", new SonarArray.Builder().put("data")) + .put("value", new SonarObject.Builder().put("prop", "updated_value")) + .build(), + responder); + + assertThat(root.data.getString("prop"), equalTo("updated_value")); + assertThat( + connection.sent.get("invalidate"), + hasItem( + new SonarObject.Builder() + .put( + "nodes", + new SonarArray.Builder() + .put(new SonarObject.Builder().put("id", "test").build()) + .build()) + .build())); + } + + @Test + public void testSetHighlighted() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarConnectionMock connection = new SonarConnectionMock(); + final SonarResponderMock responder = new SonarResponderMock(); + plugin.onConnect(connection); + + final TestNode root = new TestNode(); + root.id = "test"; + mApplicationDescriptor.root = root; + + plugin.mGetRoot.onReceive(null, responder); + plugin.mSetHighlighted.onReceive( + new SonarObject.Builder().put("id", "com.facebook.sonar").build(), responder); + + assertThat(mApplicationDescriptor.highlighted, equalTo(true)); + + plugin.mSetHighlighted.onReceive( + new SonarObject.Builder().put("id", "test").build(), responder); + + assertThat(mApplicationDescriptor.highlighted, equalTo(false)); + assertThat(root.highlighted, equalTo(true)); + + plugin.onDisconnect(); + + assertThat(root.highlighted, equalTo(false)); + } + + @Test + public void testHitTest() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarConnectionMock connection = new SonarConnectionMock(); + plugin.onConnect(connection); + + final TestNode one = new TestNode(); + one.id = "1"; + one.bounds.set(5, 5, 20, 20); + + final TestNode two = new TestNode(); + two.id = "2"; + two.bounds.set(20, 20, 100, 100); + + final TestNode three = new TestNode(); + three.id = "3"; + three.bounds.set(0, 0, 20, 20); + + final TestNode root = new TestNode(); + root.id = "test"; + root.children.add(one); + root.children.add(two); + root.children.add(three); + mApplicationDescriptor.root = root; + + plugin.hitTest(10, 10); + + assertThat( + connection.sent.get("select"), + hasItem( + new SonarObject.Builder() + .put( + "path", new SonarArray.Builder().put("com.facebook.sonar").put("test").put("3")) + .build())); + } + + @Test + public void testSetSearchActive() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarConnectionMock connection = new SonarConnectionMock(); + final SonarResponderMock responder = new SonarResponderMock(); + plugin.onConnect(connection); + + final ViewGroup decorView = Mockito.spy(new FrameLayout(mApp.getApplication())); + Mockito.when(mApp.getViewRoots()).thenReturn(Arrays.asList(decorView)); + + plugin.mSetSearchActive.onReceive( + new SonarObject.Builder().put("active", true).build(), responder); + + Mockito.verify(decorView, Mockito.times(1)).addView(Mockito.any(TouchOverlayView.class)); + + plugin.mSetSearchActive.onReceive( + new SonarObject.Builder().put("active", false).build(), responder); + + Mockito.verify(decorView, Mockito.times(1)).removeView(Mockito.any(TouchOverlayView.class)); + } + + @Test(expected = AssertionError.class) + public void testNullChildThrows() throws Exception { + final InspectorSonarPlugin plugin = + new InspectorSonarPlugin(mApp, mDescriptorMapping, mScriptingEnvironment); + final SonarResponderMock responder = new SonarResponderMock(); + final SonarConnectionMock connection = new SonarConnectionMock(); + plugin.onConnect(connection); + + final TestNode root = new TestNode(); + root.id = "test"; + root.name = "test"; + root.children = new ArrayList<>(); + root.children.add(null); + mApplicationDescriptor.root = root; + + plugin.mGetRoot.onReceive(null, responder); + plugin.mGetNodes.onReceive( + new SonarObject.Builder().put("ids", new SonarArray.Builder().put("test")).build(), + responder); + } + + private class TestNode { + String id; + String name; + List children = new ArrayList<>(); + SonarObject data; + List> atttributes = new ArrayList<>(); + String decoration; + boolean highlighted; + Rect bounds = new Rect(); + } + + private class TestNodeDescriptor extends NodeDescriptor { + + @Override + public void init(TestNode node) {} + + @Override + public String getId(TestNode node) { + return node.id; + } + + @Override + public String getName(TestNode node) { + return node.name; + } + + @Override + public int getChildCount(TestNode node) { + return node.children.size(); + } + + @Override + public Object getChildAt(TestNode node, int index) { + return node.children.get(index); + } + + @Override + public List> getData(TestNode node) { + return Collections.singletonList(new Named<>("data", node.data)); + } + + @Override + public void setValue(TestNode node, String[] path, SonarDynamic value) throws Exception { + if (path[0].equals("data")) { + node.data = value.asObject(); + } + invalidate(node); + } + + @Override + public List> getAttributes(TestNode node) { + return node.atttributes; + } + + @Override + public void setHighlighted(TestNode node, boolean selected) { + node.highlighted = selected; + } + + @Override + public void hitTest(TestNode node, Touch touch) { + for (int i = node.children.size() - 1; i >= 0; i--) { + final TestNode child = node.children.get(i); + final Rect bounds = child.bounds; + if (touch.containedIn(bounds.left, bounds.top, bounds.right, bounds.bottom)) { + touch.continueWithOffset(i, bounds.left, bounds.top); + return; + } + } + + touch.finish(); + } + + @Override + public String getDecoration(TestNode node) { + return node.decoration; + } + + @Override + public boolean matches(String query, TestNode node) { + return getName(node).contains(query); + } + } + + private class MockApplicationDescriptor extends ApplicationDescriptor { + TestNode root; + boolean highlighted; + + @Override + public int getChildCount(ApplicationWrapper node) { + return 1; + } + + @Override + public Object getChildAt(ApplicationWrapper node, int index) { + return root; + } + + @Override + public void setHighlighted(ApplicationWrapper node, boolean selected) { + highlighted = selected; + } + + @Override + public void hitTest(ApplicationWrapper node, Touch touch) { + touch.continueWithOffset(0, 0, 0); + } + } +} diff --git a/android/tests/plugins/inspector/descriptors/ViewGroupDescriptorTest.java b/android/tests/plugins/inspector/descriptors/ViewGroupDescriptorTest.java new file mode 100644 index 000000000..c176374f9 --- /dev/null +++ b/android/tests/plugins/inspector/descriptors/ViewGroupDescriptorTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.sonar.plugins.inspector.descriptors; + +import static android.view.View.MeasureSpec.EXACTLY; +import static android.view.View.MeasureSpec.makeMeasureSpec; +import static org.mockito.Matchers.any; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import com.facebook.sonar.plugins.inspector.Touch; +import com.facebook.testing.robolectric.v3.WithTestDefaultsRunner; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.robolectric.RuntimeEnvironment; + +@RunWith(WithTestDefaultsRunner.class) +public class ViewGroupDescriptorTest { + + @Test + public void testHitTestVisibleChild() { + final ViewGroupDescriptor descriptor = new ViewGroupDescriptor(); + + final ViewGroup root = new FrameLayout(RuntimeEnvironment.application); + final View child = new View(RuntimeEnvironment.application); + root.addView(child); + + root.measure(makeMeasureSpec(100, EXACTLY), makeMeasureSpec(100, EXACTLY)); + root.layout(0, 0, 100, 100); + + final Touch touch = Mockito.mock(Touch.class); + Mockito.when(touch.containedIn(any(int.class), any(int.class), any(int.class), any(int.class))) + .thenReturn(true); + descriptor.hitTest(root, touch); + Mockito.verify(touch, Mockito.times(1)).continueWithOffset(0, 0, 0); + } + + @Test + public void testHitTestInvisibleChild() { + final ViewGroupDescriptor descriptor = new ViewGroupDescriptor(); + + final ViewGroup root = new FrameLayout(RuntimeEnvironment.application); + final View child = new View(RuntimeEnvironment.application); + child.setVisibility(View.GONE); + root.addView(child); + + root.measure(makeMeasureSpec(100, EXACTLY), makeMeasureSpec(100, EXACTLY)); + root.layout(0, 0, 100, 100); + + final Touch touch = Mockito.mock(Touch.class); + Mockito.when(touch.containedIn(any(int.class), any(int.class), any(int.class), any(int.class))) + .thenReturn(true); + descriptor.hitTest(root, touch); + Mockito.verify(touch, Mockito.times(1)).finish(); + } +} diff --git a/android/third-party/DoubleConversion/ApplicationManifest.xml b/android/third-party/DoubleConversion/ApplicationManifest.xml new file mode 100644 index 000000000..60e2a288b --- /dev/null +++ b/android/third-party/DoubleConversion/ApplicationManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/third-party/DoubleConversion/CMakeLists.txt b/android/third-party/DoubleConversion/CMakeLists.txt new file mode 100644 index 000000000..3e9993f7e --- /dev/null +++ b/android/third-party/DoubleConversion/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required (VERSION 3.6.0) + +PROJECT(doubleconversion CXX) +enable_language(CXX) +set(PACKAGE_NAME doubleconversion) +set(doubleconversion_DIR double-conversion-3.0.0/double-conversion) +include_directories(${doubleconversion_DIR}) +file(GLOB SRCFILES ${doubleconversion_DIR}/*.cc) +message(STATUS "SRC FILES :- " ${SRCFILES}) +add_library(${PACKAGE_NAME} SHARED ${SRCFILES}) +install(TARGETS ${PACKAGE_NAME} DESTINATION ./build/) +target_link_libraries(${PACKAGE_NAME}) diff --git a/android/third-party/DoubleConversion/build.gradle b/android/third-party/DoubleConversion/build.gradle new file mode 100644 index 000000000..9dda53405 --- /dev/null +++ b/android/third-party/DoubleConversion/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + buildConfigField "boolean", "IS_INTERNAL_BUILD", 'true' + ndk { + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + } + + externalNativeBuild { + cmake { + arguments '-DANDROID_TOOLCHAIN=clang' + } + } + } + lintOptions { + abortOnError false + } + sourceSets { + main { + manifest.srcFile './ApplicationManifest.xml' + } + } + externalNativeBuild { + cmake { + path './CMakeLists.txt' + } + } +} diff --git a/android/third-party/Folly/ApplicationManifest.xml b/android/third-party/Folly/ApplicationManifest.xml new file mode 100644 index 000000000..2b0862478 --- /dev/null +++ b/android/third-party/Folly/ApplicationManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/third-party/Folly/CMakeLists.txt b/android/third-party/Folly/CMakeLists.txt new file mode 100644 index 000000000..ee9c054ea --- /dev/null +++ b/android/third-party/Folly/CMakeLists.txt @@ -0,0 +1,81 @@ +cmake_minimum_required (VERSION 3.6.0) + +PROJECT(folly CXX) +enable_language(CXX) +set(PACKAGE_NAME folly) + +set(FOLLY_DIR ${PROJECT_SOURCE_DIR}/folly) + + +list(APPEND dir_list ./) +list(APPEND dir_list ${FOLLY_DIR}/lang) +list(APPEND dir_list ${FOLLY_DIR}/hash/) +list(APPEND dir_list ${FOLLY_DIR}/detail) +list(APPEND dir_list ${FOLLY_DIR}/memory/detail) + +set(BOOST_DIR ../boost/boost_1_63_0/) +set(GLOG_DIR ../glog/) +set(DOUBLECONVERSION_DIR ../double-conversion/double-conversion-3.0.0/) + +list(APPEND dir_list ${BOOST_DIR}) +list(APPEND dir_list ${BOOST_DIR}/../) + +include_directories(${dir_list}) + +add_compile_options( + -DFOLLY_NO_CONFIG=1 + -DFOLLY_HAVE_MEMRCHR + -DFOLLY_MOBILE=1 + -DFOLLY_USE_LIBCPP=1 + -DFOLLY_HAVE_LIBJEMALLOC=0 + -DFOLLY_HAVE_PREADV=0 + -frtti + -fexceptions + -std=c++14 + -Wno-error + -Wno-unused-local-typedefs + -Wno-unused-variable + -Wno-sign-compare + -Wno-comment + -Wno-return-type + -Wno-tautological-constant-compare + ) + +list(APPEND SRC_FILES ${FOLLY_DIR}/Executor.cpp + ${FOLLY_DIR}/lang/ColdClass.cpp + ${FOLLY_DIR}/lang/Assume.cpp + ${FOLLY_DIR}/json.cpp + ${FOLLY_DIR}/Unicode.cpp + ${FOLLY_DIR}/Conv.cpp + ${FOLLY_DIR}/Demangle.cpp + ${FOLLY_DIR}/memory/detail/MallocImpl.cpp + ${FOLLY_DIR}/String.cpp + ${FOLLY_DIR}/dynamic.cpp + ${FOLLY_DIR}/ScopeGuard.cpp + ${FOLLY_DIR}/json_pointer.cpp + ${FOLLY_DIR}/FormatArg.cpp + ${FOLLY_DIR}/Format.cpp + ) + +add_library(${PACKAGE_NAME} SHARED ${SRC_FILES}) + +set(build_DIR ${CMAKE_SOURCE_DIR}/build) + +set(libglog_build_DIR ${build_DIR}/libglog/${ANDROID_ABI}) +set(doubleconversion_build_DIR ${build_DIR}/doubleconversion/${ANDROID_ABI}) + +file(MAKE_DIRECTORY ${build_DIR}) + +add_subdirectory(${GLOG_DIR} ${libglog_build_DIR}) +add_subdirectory(${DOUBLECONVERSION_DIR} ${doubleconversion_build_DIR}) + +target_include_directories(${PACKAGE_NAME} PRIVATE + ${BOOST_DIR} + ${BOOST_DIR}/../ + ${GLOG_DIR}/../ + ${GLOG_DIR}/glog-0.3.5/src/ + ${DOUBLECONVERSION_DIR}) + + +install(TARGETS ${PACKAGE_NAME} DESTINATION ./build/) +target_link_libraries(${PACKAGE_NAME} glog double-conversion) diff --git a/android/third-party/Folly/build.gradle b/android/third-party/Folly/build.gradle new file mode 100644 index 000000000..5593f5fac --- /dev/null +++ b/android/third-party/Folly/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + buildConfigField "boolean", "IS_INTERNAL_BUILD", 'true' + ndk { + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + } + + externalNativeBuild { + cmake { + arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_shared' + } + } + } + lintOptions { + abortOnError false + } + sourceSets { + main { + manifest.srcFile './ApplicationManifest.xml' + } + } + externalNativeBuild { + cmake { + path './CMakeLists.txt' + } + } + + dependencies { + implementation project(':glog') + implementation project(':doubleconversion') + } +} diff --git a/android/third-party/glog/ApplicationManifest.xml b/android/third-party/glog/ApplicationManifest.xml new file mode 100644 index 000000000..381f56f15 --- /dev/null +++ b/android/third-party/glog/ApplicationManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/third-party/glog/CMakeLists.txt b/android/third-party/glog/CMakeLists.txt new file mode 100644 index 000000000..346f24852 --- /dev/null +++ b/android/third-party/glog/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required (VERSION 3.6.0) + +PROJECT(glog CXX) +enable_language(CXX) +set(PACKAGE_NAME glog) + +set(glog_DIR glog-0.3.5) + +list(APPEND dir_list ./../) +list(APPEND dir_list ./) +list(APPEND dir_list ${glog_DIR}/) +list(APPEND dir_list ${glog_DIR}/src) +list(APPEND dir_list ${glog_DIR}/glog) +list(APPEND dir_list ${glog_DIR}/base) + +message(STATUS "dir_list = " ${dir_list}) +include_directories(${dir_list}) + + add_compile_options( + -std=c++11 + -Wno-macro-redefined + -Wno-macro-redefined + -Wall + -Wwrite-strings + -Woverloaded-virtual + -Wno-sign-compare + -DNDEBUG + -g + -O2 + -D_START_GOOGLE_NAMESPACE_="namespace google {" + -D_END_GOOGLE_NAMESPACE_="}" + ) + +list(APPEND src_files ${glog_DIR}/src/demangle.cc + ${glog_DIR}/src/logging.cc + ${glog_DIR}/src/raw_logging.cc + ${glog_DIR}/src/signalhandler.cc + ${glog_DIR}/src/symbolize.cc + ${glog_DIR}/src/utilities.cc + ${glog_DIR}/src/vlog_is_on.cc + ) + +add_library(${PACKAGE_NAME} SHARED ${src_files}) +install(TARGETS ${PACKAGE_NAME} DESTINATION ./build/) +target_link_libraries(${PACKAGE_NAME}) diff --git a/android/third-party/glog/build.gradle b/android/third-party/glog/build.gradle new file mode 100644 index 000000000..9dda53405 --- /dev/null +++ b/android/third-party/glog/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + buildConfigField "boolean", "IS_INTERNAL_BUILD", 'true' + ndk { + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + } + + externalNativeBuild { + cmake { + arguments '-DANDROID_TOOLCHAIN=clang' + } + } + } + lintOptions { + abortOnError false + } + sourceSets { + main { + manifest.srcFile './ApplicationManifest.xml' + } + } + externalNativeBuild { + cmake { + path './CMakeLists.txt' + } + } +} diff --git a/android/third-party/glog/config.h b/android/third-party/glog/config.h new file mode 100644 index 000000000..1e20b0da7 --- /dev/null +++ b/android/third-party/glog/config.h @@ -0,0 +1,179 @@ +/* src/config.h. Generated from config.h.in by configure. */ +/* src/config.h.in. Generated from configure.ac by autoheader. */ + +/* define if glog doesn't use RTTI */ +#define DISABLE_RTTI 1 + +/* Namespace for Google classes */ +#define GOOGLE_NAMESPACE google + +/* Define if you have the `dladdr' function */ +#define HAVE_DLADDR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_EXECINFO_H */ + +/* Define if you have the `fcntl' function */ +#define HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GLOB_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +/* #undef HAVE_LIBPTHREAD */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LIBUNWIND_H */ + +/* define if you have google gflags library */ +/* #undef HAVE_LIB_GFLAGS */ + +/* define if you have google gmock library */ +/* #undef HAVE_LIB_GMOCK */ + +/* define if you have google gtest library */ +/* #undef HAVE_LIB_GTEST */ + +/* define if you have libunwind */ +/* #undef HAVE_LIB_UNWIND */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* define if the compiler implements namespaces */ +#define HAVE_NAMESPACES 1 + +/* Define if you have POSIX threads libraries and header files. */ +#define HAVE_PTHREAD 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PWD_H 1 + +/* define if the compiler implements pthread_rwlock_* */ +#define HAVE_RWLOCK 1 + +/* Define if you have the `sigaltstack' function */ +#define HAVE_SIGALTSTACK 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYSCALL_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYSLOG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SYSCALL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_UCONTEXT_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UTSNAME_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_UCONTEXT_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNWIND_H 1 + +/* define if the compiler supports using expression for operator */ +#define HAVE_USING_OPERATOR 1 + +/* define if your compiler has __attribute__ */ +#define HAVE___ATTRIBUTE__ 1 + +/* define if your compiler has __builtin_expect */ +#define HAVE___BUILTIN_EXPECT 1 + +/* define if your compiler has __sync_val_compare_and_swap */ +#define HAVE___SYNC_VAL_COMPARE_AND_SWAP 1 + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#define LT_OBJDIR ".libs/" + +/* Name of package */ +#define PACKAGE "glog" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "opensource@google.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "glog" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "glog 0.3.3" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "glog" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.3.3" + +/* How to access the PC from a struct ucontext */ +/* #undef PC_FROM_UCONTEXT */ + +/* Define to necessary symbol if this constant uses a non-standard name on + your system. */ +/* #undef PTHREAD_CREATE_JOINABLE */ + +/* The size of `void *', as computed by sizeof. */ +#define SIZEOF_VOID_P 4 + +/* Define to 1 if you have the ANSI C header files. */ +/* #undef STDC_HEADERS */ + +/* the namespace where STL code like vector<> is defined */ +#define STL_NAMESPACE std + +/* location of source code */ +#define TEST_SRC_DIR "." + +/* Version number of package */ +#define VERSION "0.3.3" + +/* Stops putting the code inside the Google namespace */ +#define _END_GOOGLE_NAMESPACE_ } + +/* Puts following code inside the Google namespace */ +#define _START_GOOGLE_NAMESPACE_ namespace google { + + +/* TODO(vjn/dreiss): revisit these when use the android-21 (or newer) NDK platform. */ +#undef HAVE_SYSCALL_H +#undef HAVE_SYS_SYSCALL_H +#undef OS_LINUX +#undef OS_MACOSX diff --git a/android/third-party/glog/logging.cc b/android/third-party/glog/logging.cc new file mode 100644 index 000000000..6529caa76 --- /dev/null +++ b/android/third-party/glog/logging.cc @@ -0,0 +1,2088 @@ +// Copyright (c) 1999, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// 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 +// OWNER 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. + +#define _GNU_SOURCE 1 // needed for O_NOFOLLOW and pread()/pwrite() + +#include "utilities.h" + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +# include // For _exit. +#endif +#include +#include +#include +#ifdef HAVE_SYS_UTSNAME_H +# include // For uname. +#endif +#include +#include +#include +#include +#include +#ifdef HAVE_PWD_H +# include +#endif +#ifdef HAVE_SYSLOG_H +# include +#endif +#include +#include // for errno +#include +#include "base/commandlineflags.h" // to get the program name +#include "glog/logging.h" +#include "glog/raw_logging.h" +#include "base/googleinit.h" + +#ifdef HAVE_STACKTRACE +# include "stacktrace.h" +#endif + +using std::string; +using std::vector; +using std::setw; +using std::setfill; +using std::hex; +using std::dec; +using std::min; +using std::ostream; +using std::ostringstream; + +using std::FILE; +using std::fwrite; +using std::fclose; +using std::fflush; +using std::fprintf; +using std::perror; + +#ifdef __QNX__ +using std::fdopen; +#endif + +#ifdef _WIN32 +#define fdopen _fdopen +#endif + +// There is no thread annotation support. +#define EXCLUSIVE_LOCKS_REQUIRED(mu) + +static bool BoolFromEnv(const char *varname, bool defval) { + const char* const valstr = getenv(varname); + if (!valstr) { + return defval; + } + return memchr("tTyY1\0", valstr[0], 6) != NULL; +} + +GLOG_DEFINE_bool(logtostderr, BoolFromEnv("GOOGLE_LOGTOSTDERR", false), + "log messages go to stderr instead of logfiles"); +GLOG_DEFINE_bool(alsologtostderr, BoolFromEnv("GOOGLE_ALSOLOGTOSTDERR", false), + "log messages go to stderr in addition to logfiles"); +GLOG_DEFINE_bool(colorlogtostderr, false, + "color messages logged to stderr (if supported by terminal)"); +#ifdef OS_LINUX +GLOG_DEFINE_bool(drop_log_memory, true, "Drop in-memory buffers of log contents. " + "Logs can grow very quickly and they are rarely read before they " + "need to be evicted from memory. Instead, drop them from memory " + "as soon as they are flushed to disk."); +_START_GOOGLE_NAMESPACE_ +namespace logging { +static const int64 kPageSize = getpagesize(); +} +_END_GOOGLE_NAMESPACE_ +#endif + +// By default, errors (including fatal errors) get logged to stderr as +// well as the file. +// +// The default is ERROR instead of FATAL so that users can see problems +// when they run a program without having to look in another file. +DEFINE_int32(stderrthreshold, + GOOGLE_NAMESPACE::GLOG_ERROR, + "log messages at or above this level are copied to stderr in " + "addition to logfiles. This flag obsoletes --alsologtostderr."); + +GLOG_DEFINE_string(alsologtoemail, "", + "log messages go to these email addresses " + "in addition to logfiles"); +GLOG_DEFINE_bool(log_prefix, true, + "Prepend the log prefix to the start of each log line"); +GLOG_DEFINE_int32(minloglevel, 0, "Messages logged at a lower level than this don't " + "actually get logged anywhere"); +GLOG_DEFINE_int32(logbuflevel, 0, + "Buffer log messages logged at this level or lower" + " (-1 means don't buffer; 0 means buffer INFO only;" + " ...)"); +GLOG_DEFINE_int32(logbufsecs, 30, + "Buffer log messages for at most this many seconds"); +GLOG_DEFINE_int32(logemaillevel, 999, + "Email log messages logged at this level or higher" + " (0 means email all; 3 means email FATAL only;" + " ...)"); +GLOG_DEFINE_string(logmailer, "/bin/mail", + "Mailer used to send logging email"); + +// Compute the default value for --log_dir +static const char* DefaultLogDir() { + const char* env; + env = getenv("GOOGLE_LOG_DIR"); + if (env != NULL && env[0] != '\0') { + return env; + } + env = getenv("TEST_TMPDIR"); + if (env != NULL && env[0] != '\0') { + return env; + } + return ""; +} + +GLOG_DEFINE_int32(logfile_mode, 0664, "Log file mode/permissions."); + +GLOG_DEFINE_string(log_dir, DefaultLogDir(), + "If specified, logfiles are written into this directory instead " + "of the default logging directory."); +GLOG_DEFINE_string(log_link, "", "Put additional links to the log " + "files in this directory"); + +GLOG_DEFINE_int32(max_log_size, 1800, + "approx. maximum log file size (in MB). A value of 0 will " + "be silently overridden to 1."); + +GLOG_DEFINE_bool(stop_logging_if_full_disk, false, + "Stop attempting to log to disk if the disk is full."); + +GLOG_DEFINE_string(log_backtrace_at, "", + "Emit a backtrace when logging at file:linenum."); + +// TODO(hamaji): consider windows +#define PATH_SEPARATOR '/' + +#ifndef HAVE_PREAD +#if defined(OS_WINDOWS) +#include +#define ssize_t SSIZE_T +#endif +ssize_t pread(int fd, void* buf, size_t count, off_t offset) { + off_t orig_offset = lseek(fd, 0, SEEK_CUR); + if (orig_offset == (off_t)-1) + return -1; + if (lseek(fd, offset, SEEK_CUR) == (off_t)-1) + return -1; + ssize_t len = read(fd, buf, count); + if (len < 0) + return len; + if (lseek(fd, orig_offset, SEEK_SET) == (off_t)-1) + return -1; + return len; +} +#endif // !HAVE_PREAD + +#ifndef HAVE_PWRITE +static ssize_t pwrite(int fd, void* buf, size_t count, off_t offset) { + off_t orig_offset = lseek(fd, 0, SEEK_CUR); + if (orig_offset == (off_t)-1) + return -1; + if (lseek(fd, offset, SEEK_CUR) == (off_t)-1) + return -1; + ssize_t len = write(fd, buf, count); + if (len < 0) + return len; + if (lseek(fd, orig_offset, SEEK_SET) == (off_t)-1) + return -1; + return len; +} +#endif // !HAVE_PWRITE + +static void GetHostName(string* hostname) { +#if defined(HAVE_SYS_UTSNAME_H) + struct utsname buf; + if (0 != uname(&buf)) { + // ensure null termination on failure + *buf.nodename = '\0'; + } + *hostname = buf.nodename; +#elif defined(OS_WINDOWS) + char buf[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD len = MAX_COMPUTERNAME_LENGTH + 1; + if (GetComputerNameA(buf, &len)) { + *hostname = buf; + } else { + hostname->clear(); + } +#else +# warning There is no way to retrieve the host name. + *hostname = "(unknown)"; +#endif +} + +// Returns true iff terminal supports using colors in output. +static bool TerminalSupportsColor() { + bool term_supports_color = false; +#ifdef OS_WINDOWS + // on Windows TERM variable is usually not set, but the console does + // support colors. + term_supports_color = true; +#else + // On non-Windows platforms, we rely on the TERM variable. + const char* const term = getenv("TERM"); + if (term != NULL && term[0] != '\0') { + term_supports_color = + !strcmp(term, "xterm") || + !strcmp(term, "xterm-color") || + !strcmp(term, "xterm-256color") || + !strcmp(term, "screen-256color") || + !strcmp(term, "screen") || + !strcmp(term, "linux") || + !strcmp(term, "cygwin"); + } +#endif + return term_supports_color; +} + +_START_GOOGLE_NAMESPACE_ + +enum GLogColor { + COLOR_DEFAULT, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW +}; + +static GLogColor SeverityToColor(LogSeverity severity) { + assert(severity >= 0 && severity < NUM_SEVERITIES); + GLogColor color = COLOR_DEFAULT; + switch (severity) { + case GLOG_INFO: + color = COLOR_DEFAULT; + break; + case GLOG_WARNING: + color = COLOR_YELLOW; + break; + case GLOG_ERROR: + case GLOG_FATAL: + color = COLOR_RED; + break; + default: + // should never get here. + assert(false); + } + return color; +} + +#ifdef OS_WINDOWS + +// Returns the character attribute for the given color. +WORD GetColorAttribute(GLogColor color) { + switch (color) { + case COLOR_RED: return FOREGROUND_RED; + case COLOR_GREEN: return FOREGROUND_GREEN; + case COLOR_YELLOW: return FOREGROUND_RED | FOREGROUND_GREEN; + default: return 0; + } +} + +#else + +// Returns the ANSI color code for the given color. +const char* GetAnsiColorCode(GLogColor color) { + switch (color) { + case COLOR_RED: return "1"; + case COLOR_GREEN: return "2"; + case COLOR_YELLOW: return "3"; + case COLOR_DEFAULT: return ""; + }; + return NULL; // stop warning about return type. +} + +#endif // OS_WINDOWS + +// Safely get max_log_size, overriding to 1 if it somehow gets defined as 0 +static int32 MaxLogSize() { + return (FLAGS_max_log_size > 0 ? FLAGS_max_log_size : 1); +} + +// An arbitrary limit on the length of a single log message. This +// is so that streaming can be done more efficiently. +const size_t LogMessage::kMaxLogMessageLen = 30000; + +struct LogMessage::LogMessageData { + LogMessageData(); + + int preserved_errno_; // preserved errno + // Buffer space; contains complete message text. + char message_text_[LogMessage::kMaxLogMessageLen+1]; + LogStream stream_; + char severity_; // What level is this LogMessage logged at? + int line_; // line number where logging call is. + void (LogMessage::*send_method_)(); // Call this in destructor to send + union { // At most one of these is used: union to keep the size low. + LogSink* sink_; // NULL or sink to send message to + std::vector* outvec_; // NULL or vector to push message onto + std::string* message_; // NULL or string to write message into + }; + time_t timestamp_; // Time of creation of LogMessage + struct ::tm tm_time_; // Time of creation of LogMessage + size_t num_prefix_chars_; // # of chars of prefix in this message + size_t num_chars_to_log_; // # of chars of msg to send to log + size_t num_chars_to_syslog_; // # of chars of msg to send to syslog + const char* basename_; // basename of file that called LOG + const char* fullname_; // fullname of file that called LOG + bool has_been_flushed_; // false => data has not been flushed + bool first_fatal_; // true => this was first fatal msg + + private: + LogMessageData(const LogMessageData&); + void operator=(const LogMessageData&); +}; + +// A mutex that allows only one thread to log at a time, to keep things from +// getting jumbled. Some other very uncommon logging operations (like +// changing the destination file for log messages of a given severity) also +// lock this mutex. Please be sure that anybody who might possibly need to +// lock it does so. +static Mutex log_mutex; + +// Number of messages sent at each severity. Under log_mutex. +int64 LogMessage::num_messages_[NUM_SEVERITIES] = {0, 0, 0, 0}; + +// Globally disable log writing (if disk is full) +static bool stop_writing = false; + +const char*const LogSeverityNames[NUM_SEVERITIES] = { + "INFO", "WARNING", "ERROR", "FATAL" +}; + +// Has the user called SetExitOnDFatal(true)? +static bool exit_on_dfatal = true; + +const char* GetLogSeverityName(LogSeverity severity) { + return LogSeverityNames[severity]; +} + +static bool SendEmailInternal(const char*dest, const char *subject, + const char*body, bool use_logging); + +base::Logger::~Logger() { +} + +namespace { + +// Encapsulates all file-system related state +class LogFileObject : public base::Logger { + public: + LogFileObject(LogSeverity severity, const char* base_filename); + ~LogFileObject(); + + virtual void Write(bool force_flush, // Should we force a flush here? + time_t timestamp, // Timestamp for this entry + const char* message, + int message_len); + + // Configuration options + void SetBasename(const char* basename); + void SetExtension(const char* ext); + void SetSymlinkBasename(const char* symlink_basename); + + // Normal flushing routine + virtual void Flush(); + + // It is the actual file length for the system loggers, + // i.e., INFO, ERROR, etc. + virtual uint32 LogSize() { + MutexLock l(&lock_); + return file_length_; + } + + // Internal flush routine. Exposed so that FlushLogFilesUnsafe() + // can avoid grabbing a lock. Usually Flush() calls it after + // acquiring lock_. + void FlushUnlocked(); + + private: + static const uint32 kRolloverAttemptFrequency = 0x20; + + Mutex lock_; + bool base_filename_selected_; + string base_filename_; + string symlink_basename_; + string filename_extension_; // option users can specify (eg to add port#) + FILE* file_; + LogSeverity severity_; + uint32 bytes_since_flush_; + uint32 file_length_; + unsigned int rollover_attempt_; + int64 next_flush_time_; // cycle count at which to flush log + + // Actually create a logfile using the value of base_filename_ and the + // supplied argument time_pid_string + // REQUIRES: lock_ is held + bool CreateLogfile(const string& time_pid_string); +}; + +} // namespace + +class LogDestination { + public: + friend class LogMessage; + friend void ReprintFatalMessage(); + friend base::Logger* base::GetLogger(LogSeverity); + friend void base::SetLogger(LogSeverity, base::Logger*); + + // These methods are just forwarded to by their global versions. + static void SetLogDestination(LogSeverity severity, + const char* base_filename); + static void SetLogSymlink(LogSeverity severity, + const char* symlink_basename); + static void AddLogSink(LogSink *destination); + static void RemoveLogSink(LogSink *destination); + static void SetLogFilenameExtension(const char* filename_extension); + static void SetStderrLogging(LogSeverity min_severity); + static void SetEmailLogging(LogSeverity min_severity, const char* addresses); + static void LogToStderr(); + // Flush all log files that are at least at the given severity level + static void FlushLogFiles(int min_severity); + static void FlushLogFilesUnsafe(int min_severity); + + // we set the maximum size of our packet to be 1400, the logic being + // to prevent fragmentation. + // Really this number is arbitrary. + static const int kNetworkBytes = 1400; + + static const string& hostname(); + static const bool& terminal_supports_color() { + return terminal_supports_color_; + } + + static void DeleteLogDestinations(); + + private: + LogDestination(LogSeverity severity, const char* base_filename); + ~LogDestination() { } + + // Take a log message of a particular severity and log it to stderr + // iff it's of a high enough severity to deserve it. + static void MaybeLogToStderr(LogSeverity severity, const char* message, + size_t len); + + // Take a log message of a particular severity and log it to email + // iff it's of a high enough severity to deserve it. + static void MaybeLogToEmail(LogSeverity severity, const char* message, + size_t len); + // Take a log message of a particular severity and log it to a file + // iff the base filename is not "" (which means "don't log to me") + static void MaybeLogToLogfile(LogSeverity severity, + time_t timestamp, + const char* message, size_t len); + // Take a log message of a particular severity and log it to the file + // for that severity and also for all files with severity less than + // this severity. + static void LogToAllLogfiles(LogSeverity severity, + time_t timestamp, + const char* message, size_t len); + + // Send logging info to all registered sinks. + static void LogToSinks(LogSeverity severity, + const char *full_filename, + const char *base_filename, + int line, + const struct ::tm* tm_time, + const char* message, + size_t message_len); + + // Wait for all registered sinks via WaitTillSent + // including the optional one in "data". + static void WaitForSinks(LogMessage::LogMessageData* data); + + static LogDestination* log_destination(LogSeverity severity); + + LogFileObject fileobject_; + base::Logger* logger_; // Either &fileobject_, or wrapper around it + + static LogDestination* log_destinations_[NUM_SEVERITIES]; + static LogSeverity email_logging_severity_; + static string addresses_; + static string hostname_; + static bool terminal_supports_color_; + + // arbitrary global logging destinations. + static vector* sinks_; + + // Protects the vector sinks_, + // but not the LogSink objects its elements reference. + static Mutex sink_mutex_; + + // Disallow + LogDestination(const LogDestination&); + LogDestination& operator=(const LogDestination&); +}; + +// Errors do not get logged to email by default. +LogSeverity LogDestination::email_logging_severity_ = 99999; + +string LogDestination::addresses_; +string LogDestination::hostname_; + +vector* LogDestination::sinks_ = NULL; +Mutex LogDestination::sink_mutex_; +bool LogDestination::terminal_supports_color_ = TerminalSupportsColor(); + +/* static */ +const string& LogDestination::hostname() { + if (hostname_.empty()) { + GetHostName(&hostname_); + if (hostname_.empty()) { + hostname_ = "(unknown)"; + } + } + return hostname_; +} + +LogDestination::LogDestination(LogSeverity severity, + const char* base_filename) + : fileobject_(severity, base_filename), + logger_(&fileobject_) { +} + +inline void LogDestination::FlushLogFilesUnsafe(int min_severity) { + // assume we have the log_mutex or we simply don't care + // about it + for (int i = min_severity; i < NUM_SEVERITIES; i++) { + LogDestination* log = log_destinations_[i]; + if (log != NULL) { + // Flush the base fileobject_ logger directly instead of going + // through any wrappers to reduce chance of deadlock. + log->fileobject_.FlushUnlocked(); + } + } +} + +inline void LogDestination::FlushLogFiles(int min_severity) { + // Prevent any subtle race conditions by wrapping a mutex lock around + // all this stuff. + MutexLock l(&log_mutex); + for (int i = min_severity; i < NUM_SEVERITIES; i++) { + LogDestination* log = log_destination(i); + if (log != NULL) { + log->logger_->Flush(); + } + } +} + +inline void LogDestination::SetLogDestination(LogSeverity severity, + const char* base_filename) { + assert(severity >= 0 && severity < NUM_SEVERITIES); + // Prevent any subtle race conditions by wrapping a mutex lock around + // all this stuff. + MutexLock l(&log_mutex); + log_destination(severity)->fileobject_.SetBasename(base_filename); +} + +inline void LogDestination::SetLogSymlink(LogSeverity severity, + const char* symlink_basename) { + CHECK_GE(severity, 0); + CHECK_LT(severity, NUM_SEVERITIES); + MutexLock l(&log_mutex); + log_destination(severity)->fileobject_.SetSymlinkBasename(symlink_basename); +} + +inline void LogDestination::AddLogSink(LogSink *destination) { + // Prevent any subtle race conditions by wrapping a mutex lock around + // all this stuff. + MutexLock l(&sink_mutex_); + if (!sinks_) sinks_ = new vector; + sinks_->push_back(destination); +} + +inline void LogDestination::RemoveLogSink(LogSink *destination) { + // Prevent any subtle race conditions by wrapping a mutex lock around + // all this stuff. + MutexLock l(&sink_mutex_); + // This doesn't keep the sinks in order, but who cares? + if (sinks_) { + for (int i = sinks_->size() - 1; i >= 0; i--) { + if ((*sinks_)[i] == destination) { + (*sinks_)[i] = (*sinks_)[sinks_->size() - 1]; + sinks_->pop_back(); + break; + } + } + } +} + +inline void LogDestination::SetLogFilenameExtension(const char* ext) { + // Prevent any subtle race conditions by wrapping a mutex lock around + // all this stuff. + MutexLock l(&log_mutex); + for ( int severity = 0; severity < NUM_SEVERITIES; ++severity ) { + log_destination(severity)->fileobject_.SetExtension(ext); + } +} + +inline void LogDestination::SetStderrLogging(LogSeverity min_severity) { + assert(min_severity >= 0 && min_severity < NUM_SEVERITIES); + // Prevent any subtle race conditions by wrapping a mutex lock around + // all this stuff. + MutexLock l(&log_mutex); + FLAGS_stderrthreshold = min_severity; +} + +inline void LogDestination::LogToStderr() { + // *Don't* put this stuff in a mutex lock, since SetStderrLogging & + // SetLogDestination already do the locking! + SetStderrLogging(0); // thus everything is "also" logged to stderr + for ( int i = 0; i < NUM_SEVERITIES; ++i ) { + SetLogDestination(i, ""); // "" turns off logging to a logfile + } +} + +inline void LogDestination::SetEmailLogging(LogSeverity min_severity, + const char* addresses) { + assert(min_severity >= 0 && min_severity < NUM_SEVERITIES); + // Prevent any subtle race conditions by wrapping a mutex lock around + // all this stuff. + MutexLock l(&log_mutex); + LogDestination::email_logging_severity_ = min_severity; + LogDestination::addresses_ = addresses; +} + +static void ColoredWriteToStderr(LogSeverity severity, + const char* message, size_t len) { + const GLogColor color = + (LogDestination::terminal_supports_color() && FLAGS_colorlogtostderr) ? + SeverityToColor(severity) : COLOR_DEFAULT; + + // Avoid using cerr from this module since we may get called during + // exit code, and cerr may be partially or fully destroyed by then. + if (COLOR_DEFAULT == color) { + fwrite(message, len, 1, stderr); + return; + } +#ifdef OS_WINDOWS + const HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); + + // Gets the current text color. + CONSOLE_SCREEN_BUFFER_INFO buffer_info; + GetConsoleScreenBufferInfo(stderr_handle, &buffer_info); + const WORD old_color_attrs = buffer_info.wAttributes; + + // We need to flush the stream buffers into the console before each + // SetConsoleTextAttribute call lest it affect the text that is already + // printed but has not yet reached the console. + fflush(stderr); + SetConsoleTextAttribute(stderr_handle, + GetColorAttribute(color) | FOREGROUND_INTENSITY); + fwrite(message, len, 1, stderr); + fflush(stderr); + // Restores the text color. + SetConsoleTextAttribute(stderr_handle, old_color_attrs); +#else + fprintf(stderr, "\033[0;3%sm", GetAnsiColorCode(color)); + fwrite(message, len, 1, stderr); + fprintf(stderr, "\033[m"); // Resets the terminal to default. +#endif // OS_WINDOWS +} + +static void WriteToStderr(const char* message, size_t len) { + // Avoid using cerr from this module since we may get called during + // exit code, and cerr may be partially or fully destroyed by then. + fwrite(message, len, 1, stderr); +} + +inline void LogDestination::MaybeLogToStderr(LogSeverity severity, + const char* message, size_t len) { + if ((severity >= FLAGS_stderrthreshold) || FLAGS_alsologtostderr) { + ColoredWriteToStderr(severity, message, len); +#ifdef OS_WINDOWS + // On Windows, also output to the debugger + ::OutputDebugStringA(string(message,len).c_str()); +#endif + } +} + + +inline void LogDestination::MaybeLogToEmail(LogSeverity severity, + const char* message, size_t len) { + if (severity >= email_logging_severity_ || + severity >= FLAGS_logemaillevel) { + string to(FLAGS_alsologtoemail); + if (!addresses_.empty()) { + if (!to.empty()) { + to += ","; + } + to += addresses_; + } + const string subject(string("[LOG] ") + LogSeverityNames[severity] + ": " + + glog_internal_namespace_::ProgramInvocationShortName()); + string body(hostname()); + body += "\n\n"; + body.append(message, len); + + // should NOT use SendEmail(). The caller of this function holds the + // log_mutex and SendEmail() calls LOG/VLOG which will block trying to + // acquire the log_mutex object. Use SendEmailInternal() and set + // use_logging to false. + SendEmailInternal(to.c_str(), subject.c_str(), body.c_str(), false); + } +} + + +inline void LogDestination::MaybeLogToLogfile(LogSeverity severity, + time_t timestamp, + const char* message, + size_t len) { + const bool should_flush = severity > FLAGS_logbuflevel; + LogDestination* destination = log_destination(severity); + destination->logger_->Write(should_flush, timestamp, message, len); +} + +inline void LogDestination::LogToAllLogfiles(LogSeverity severity, + time_t timestamp, + const char* message, + size_t len) { + + if ( FLAGS_logtostderr ) { // global flag: never log to file + ColoredWriteToStderr(severity, message, len); + } else { + for (int i = severity; i >= 0; --i) + LogDestination::MaybeLogToLogfile(i, timestamp, message, len); + } +} + +inline void LogDestination::LogToSinks(LogSeverity severity, + const char *full_filename, + const char *base_filename, + int line, + const struct ::tm* tm_time, + const char* message, + size_t message_len) { + ReaderMutexLock l(&sink_mutex_); + if (sinks_) { + for (int i = sinks_->size() - 1; i >= 0; i--) { + (*sinks_)[i]->send(severity, full_filename, base_filename, + line, tm_time, message, message_len); + } + } +} + +inline void LogDestination::WaitForSinks(LogMessage::LogMessageData* data) { + ReaderMutexLock l(&sink_mutex_); + if (sinks_) { + for (int i = sinks_->size() - 1; i >= 0; i--) { + (*sinks_)[i]->WaitTillSent(); + } + } + const bool send_to_sink = + (data->send_method_ == &LogMessage::SendToSink) || + (data->send_method_ == &LogMessage::SendToSinkAndLog); + if (send_to_sink && data->sink_ != NULL) { + data->sink_->WaitTillSent(); + } +} + +LogDestination* LogDestination::log_destinations_[NUM_SEVERITIES]; + +inline LogDestination* LogDestination::log_destination(LogSeverity severity) { + assert(severity >=0 && severity < NUM_SEVERITIES); + if (!log_destinations_[severity]) { + log_destinations_[severity] = new LogDestination(severity, NULL); + } + return log_destinations_[severity]; +} + +void LogDestination::DeleteLogDestinations() { + for (int severity = 0; severity < NUM_SEVERITIES; ++severity) { + delete log_destinations_[severity]; + log_destinations_[severity] = NULL; + } + MutexLock l(&sink_mutex_); + delete sinks_; + sinks_ = NULL; +} + +namespace { + +LogFileObject::LogFileObject(LogSeverity severity, + const char* base_filename) + : base_filename_selected_(base_filename != NULL), + base_filename_((base_filename != NULL) ? base_filename : ""), + symlink_basename_(glog_internal_namespace_::ProgramInvocationShortName()), + filename_extension_(), + file_(NULL), + severity_(severity), + bytes_since_flush_(0), + file_length_(0), + rollover_attempt_(kRolloverAttemptFrequency-1), + next_flush_time_(0) { + assert(severity >= 0); + assert(severity < NUM_SEVERITIES); +} + +LogFileObject::~LogFileObject() { + MutexLock l(&lock_); + if (file_ != NULL) { + fclose(file_); + file_ = NULL; + } +} + +void LogFileObject::SetBasename(const char* basename) { + MutexLock l(&lock_); + base_filename_selected_ = true; + if (base_filename_ != basename) { + // Get rid of old log file since we are changing names + if (file_ != NULL) { + fclose(file_); + file_ = NULL; + rollover_attempt_ = kRolloverAttemptFrequency-1; + } + base_filename_ = basename; + } +} + +void LogFileObject::SetExtension(const char* ext) { + MutexLock l(&lock_); + if (filename_extension_ != ext) { + // Get rid of old log file since we are changing names + if (file_ != NULL) { + fclose(file_); + file_ = NULL; + rollover_attempt_ = kRolloverAttemptFrequency-1; + } + filename_extension_ = ext; + } +} + +void LogFileObject::SetSymlinkBasename(const char* symlink_basename) { + MutexLock l(&lock_); + symlink_basename_ = symlink_basename; +} + +void LogFileObject::Flush() { + MutexLock l(&lock_); + FlushUnlocked(); +} + +void LogFileObject::FlushUnlocked(){ + if (file_ != NULL) { + fflush(file_); + bytes_since_flush_ = 0; + } + // Figure out when we are due for another flush. + const int64 next = (FLAGS_logbufsecs + * static_cast(1000000)); // in usec + next_flush_time_ = CycleClock_Now() + UsecToCycles(next); +} + +bool LogFileObject::CreateLogfile(const string& time_pid_string) { + string string_filename = base_filename_+filename_extension_+ + time_pid_string; + const char* filename = string_filename.c_str(); + int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, FLAGS_logfile_mode); + if (fd == -1) return false; +#ifdef HAVE_FCNTL + // Mark the file close-on-exec. We don't really care if this fails + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif + + file_ = fdopen(fd, "a"); // Make a FILE*. + if (file_ == NULL) { // Man, we're screwed! + close(fd); + unlink(filename); // Erase the half-baked evidence: an unusable log file + return false; + } + + // We try to create a symlink called ., + // which is easier to use. (Every time we create a new logfile, + // we destroy the old symlink and create a new one, so it always + // points to the latest logfile.) If it fails, we're sad but it's + // no error. + if (!symlink_basename_.empty()) { + // take directory from filename + const char* slash = strrchr(filename, PATH_SEPARATOR); + const string linkname = + symlink_basename_ + '.' + LogSeverityNames[severity_]; + string linkpath; + if ( slash ) linkpath = string(filename, slash-filename+1); // get dirname + linkpath += linkname; + unlink(linkpath.c_str()); // delete old one if it exists + +#if defined(OS_WINDOWS) + // TODO(hamaji): Create lnk file on Windows? +#elif defined(HAVE_UNISTD_H) + // We must have unistd.h. + // Make the symlink be relative (in the same dir) so that if the + // entire log directory gets relocated the link is still valid. + const char *linkdest = slash ? (slash + 1) : filename; + if (symlink(linkdest, linkpath.c_str()) != 0) { + // silently ignore failures + } + + // Make an additional link to the log file in a place specified by + // FLAGS_log_link, if indicated + if (!FLAGS_log_link.empty()) { + linkpath = FLAGS_log_link + "/" + linkname; + unlink(linkpath.c_str()); // delete old one if it exists + if (symlink(filename, linkpath.c_str()) != 0) { + // silently ignore failures + } + } +#endif + } + + return true; // Everything worked +} + +void LogFileObject::Write(bool force_flush, + time_t timestamp, + const char* message, + int message_len) { + MutexLock l(&lock_); + + // We don't log if the base_name_ is "" (which means "don't write") + if (base_filename_selected_ && base_filename_.empty()) { + return; + } + + if (static_cast(file_length_ >> 20) >= MaxLogSize() || + PidHasChanged()) { + if (file_ != NULL) fclose(file_); + file_ = NULL; + file_length_ = bytes_since_flush_ = 0; + rollover_attempt_ = kRolloverAttemptFrequency-1; + } + + // If there's no destination file, make one before outputting + if (file_ == NULL) { + // Try to rollover the log file every 32 log messages. The only time + // this could matter would be when we have trouble creating the log + // file. If that happens, we'll lose lots of log messages, of course! + if (++rollover_attempt_ != kRolloverAttemptFrequency) return; + rollover_attempt_ = 0; + + struct ::tm tm_time; + localtime_r(×tamp, &tm_time); + + // The logfile's filename will have the date/time & pid in it + ostringstream time_pid_stream; + time_pid_stream.fill('0'); + time_pid_stream << 1900+tm_time.tm_year + << setw(2) << 1+tm_time.tm_mon + << setw(2) << tm_time.tm_mday + << '-' + << setw(2) << tm_time.tm_hour + << setw(2) << tm_time.tm_min + << setw(2) << tm_time.tm_sec + << '.' + << GetMainThreadPid(); + const string& time_pid_string = time_pid_stream.str(); + + if (base_filename_selected_) { + if (!CreateLogfile(time_pid_string)) { + perror("Could not create log file"); + fprintf(stderr, "COULD NOT CREATE LOGFILE '%s'!\n", + time_pid_string.c_str()); + return; + } + } else { + // If no base filename for logs of this severity has been set, use a + // default base filename of + // "...log..". So + // logfiles will have names like + // webserver.examplehost.root.log.INFO.19990817-150000.4354, where + // 19990817 is a date (1999 August 17), 150000 is a time (15:00:00), + // and 4354 is the pid of the logging process. The date & time reflect + // when the file was created for output. + // + // Where does the file get put? Successively try the directories + // "/tmp", and "." + string stripped_filename( + glog_internal_namespace_::ProgramInvocationShortName()); + string hostname; + GetHostName(&hostname); + + string uidname = MyUserName(); + // We should not call CHECK() here because this function can be + // called after holding on to log_mutex. We don't want to + // attempt to hold on to the same mutex, and get into a + // deadlock. Simply use a name like invalid-user. + if (uidname.empty()) uidname = "invalid-user"; + + stripped_filename = stripped_filename+'.'+hostname+'.' + +uidname+".log." + +LogSeverityNames[severity_]+'.'; + // We're going to (potentially) try to put logs in several different dirs + const vector & log_dirs = GetLoggingDirectories(); + + // Go through the list of dirs, and try to create the log file in each + // until we succeed or run out of options + bool success = false; + for (vector::const_iterator dir = log_dirs.begin(); + dir != log_dirs.end(); + ++dir) { + base_filename_ = *dir + "/" + stripped_filename; + if ( CreateLogfile(time_pid_string) ) { + success = true; + break; + } + } + // If we never succeeded, we have to give up + if ( success == false ) { + perror("Could not create logging file"); + fprintf(stderr, "COULD NOT CREATE A LOGGINGFILE %s!", + time_pid_string.c_str()); + return; + } + } + + // Write a header message into the log file + ostringstream file_header_stream; + file_header_stream.fill('0'); + file_header_stream << "Log file created at: " + << 1900+tm_time.tm_year << '/' + << setw(2) << 1+tm_time.tm_mon << '/' + << setw(2) << tm_time.tm_mday + << ' ' + << setw(2) << tm_time.tm_hour << ':' + << setw(2) << tm_time.tm_min << ':' + << setw(2) << tm_time.tm_sec << '\n' + << "Running on machine: " + << LogDestination::hostname() << '\n' + << "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu " + << "threadid file:line] msg" << '\n'; + const string& file_header_string = file_header_stream.str(); + + const int header_len = file_header_string.size(); + fwrite(file_header_string.data(), 1, header_len, file_); + file_length_ += header_len; + bytes_since_flush_ += header_len; + } + + // Write to LOG file + if ( !stop_writing ) { + // fwrite() doesn't return an error when the disk is full, for + // messages that are less than 4096 bytes. When the disk is full, + // it returns the message length for messages that are less than + // 4096 bytes. fwrite() returns 4096 for message lengths that are + // greater than 4096, thereby indicating an error. + errno = 0; + fwrite(message, 1, message_len, file_); + if ( FLAGS_stop_logging_if_full_disk && + errno == ENOSPC ) { // disk full, stop writing to disk + stop_writing = true; // until the disk is + return; + } else { + file_length_ += message_len; + bytes_since_flush_ += message_len; + } + } else { + if ( CycleClock_Now() >= next_flush_time_ ) + stop_writing = false; // check to see if disk has free space. + return; // no need to flush + } + + // See important msgs *now*. Also, flush logs at least every 10^6 chars, + // or every "FLAGS_logbufsecs" seconds. + if ( force_flush || + (bytes_since_flush_ >= 1000000) || + (CycleClock_Now() >= next_flush_time_) ) { + FlushUnlocked(); +#ifdef OS_LINUX + if (FLAGS_drop_log_memory) { + if (file_length_ >= logging::kPageSize) { + // don't evict the most recent page + uint32 len = file_length_ & ~(logging::kPageSize - 1); + posix_fadvise(fileno(file_), 0, len, POSIX_FADV_DONTNEED); + } + } +#endif + } +} + +} // namespace + + +// Static log data space to avoid alloc failures in a LOG(FATAL) +// +// Since multiple threads may call LOG(FATAL), and we want to preserve +// the data from the first call, we allocate two sets of space. One +// for exclusive use by the first thread, and one for shared use by +// all other threads. +static Mutex fatal_msg_lock; +static CrashReason crash_reason; +static bool fatal_msg_exclusive = true; +static LogMessage::LogMessageData fatal_msg_data_exclusive; +static LogMessage::LogMessageData fatal_msg_data_shared; + +LogMessage::LogMessageData::LogMessageData() + : stream_(message_text_, LogMessage::kMaxLogMessageLen, 0) { +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity, + int ctr, void (LogMessage::*send_method)()) + : allocated_(NULL) { + Init(file, line, severity, send_method); + data_->stream_.set_ctr(ctr); +} + +LogMessage::LogMessage(const char* file, int line, + const CheckOpString& result) + : allocated_(NULL) { + Init(file, line, GLOG_FATAL, &LogMessage::SendToLog); + stream() << "Check failed: " << (*result.str_) << " "; +} + +LogMessage::LogMessage(const char* file, int line) + : allocated_(NULL) { + Init(file, line, GLOG_INFO, &LogMessage::SendToLog); +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity) + : allocated_(NULL) { + Init(file, line, severity, &LogMessage::SendToLog); +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity, + LogSink* sink, bool also_send_to_log) + : allocated_(NULL) { + Init(file, line, severity, also_send_to_log ? &LogMessage::SendToSinkAndLog : + &LogMessage::SendToSink); + data_->sink_ = sink; // override Init()'s setting to NULL +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity, + vector *outvec) + : allocated_(NULL) { + Init(file, line, severity, &LogMessage::SaveOrSendToLog); + data_->outvec_ = outvec; // override Init()'s setting to NULL +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity, + string *message) + : allocated_(NULL) { + Init(file, line, severity, &LogMessage::WriteToStringAndLog); + data_->message_ = message; // override Init()'s setting to NULL +} + +void LogMessage::Init(const char* file, + int line, + LogSeverity severity, + void (LogMessage::*send_method)()) { + allocated_ = NULL; + if (severity != GLOG_FATAL || !exit_on_dfatal) { + allocated_ = new LogMessageData(); + data_ = allocated_; + data_->first_fatal_ = false; + } else { + MutexLock l(&fatal_msg_lock); + if (fatal_msg_exclusive) { + fatal_msg_exclusive = false; + data_ = &fatal_msg_data_exclusive; + data_->first_fatal_ = true; + } else { + data_ = &fatal_msg_data_shared; + data_->first_fatal_ = false; + } + } + + stream().fill('0'); + data_->preserved_errno_ = errno; + data_->severity_ = severity; + data_->line_ = line; + data_->send_method_ = send_method; + data_->sink_ = NULL; + data_->outvec_ = NULL; + WallTime now = WallTime_Now(); + data_->timestamp_ = static_cast(now); + localtime_r(&data_->timestamp_, &data_->tm_time_); + int usecs = static_cast((now - data_->timestamp_) * 1000000); + RawLog__SetLastTime(data_->tm_time_, usecs); + + data_->num_chars_to_log_ = 0; + data_->num_chars_to_syslog_ = 0; + data_->basename_ = const_basename(file); + data_->fullname_ = file; + data_->has_been_flushed_ = false; + + // If specified, prepend a prefix to each line. For example: + // I1018 160715 f5d4fbb0 logging.cc:1153] + // (log level, GMT month, date, time, thread_id, file basename, line) + // We exclude the thread_id for the default thread. + if (FLAGS_log_prefix && (line != kNoLogPrefix)) { + stream() << LogSeverityNames[severity][0] + << setw(2) << 1+data_->tm_time_.tm_mon + << setw(2) << data_->tm_time_.tm_mday + << ' ' + << setw(2) << data_->tm_time_.tm_hour << ':' + << setw(2) << data_->tm_time_.tm_min << ':' + << setw(2) << data_->tm_time_.tm_sec << "." + << setw(6) << usecs + << ' ' + << setfill(' ') << setw(5) + << static_cast(GetTID()) << setfill('0') + << ' ' + << data_->basename_ << ':' << data_->line_ << "] "; + } + data_->num_prefix_chars_ = data_->stream_.pcount(); + + if (!FLAGS_log_backtrace_at.empty()) { + char fileline[128]; + snprintf(fileline, sizeof(fileline), "%s:%d", data_->basename_, line); +#ifdef HAVE_STACKTRACE + if (!strcmp(FLAGS_log_backtrace_at.c_str(), fileline)) { + string stacktrace; + DumpStackTraceToString(&stacktrace); + stream() << " (stacktrace:\n" << stacktrace << ") "; + } +#endif + } +} + +LogMessage::~LogMessage() { + Flush(); + delete allocated_; +} + +int LogMessage::preserved_errno() const { + return data_->preserved_errno_; +} + +ostream& LogMessage::stream() { + return data_->stream_; +} + +// Flush buffered message, called by the destructor, or any other function +// that needs to synchronize the log. +void LogMessage::Flush() { + if (data_->has_been_flushed_ || data_->severity_ < FLAGS_minloglevel) + return; + + data_->num_chars_to_log_ = data_->stream_.pcount(); + data_->num_chars_to_syslog_ = + data_->num_chars_to_log_ - data_->num_prefix_chars_; + + // Do we need to add a \n to the end of this message? + bool append_newline = + (data_->message_text_[data_->num_chars_to_log_-1] != '\n'); + char original_final_char = '\0'; + + // If we do need to add a \n, we'll do it by violating the memory of the + // ostrstream buffer. This is quick, and we'll make sure to undo our + // modification before anything else is done with the ostrstream. It + // would be preferable not to do things this way, but it seems to be + // the best way to deal with this. + if (append_newline) { + original_final_char = data_->message_text_[data_->num_chars_to_log_]; + data_->message_text_[data_->num_chars_to_log_++] = '\n'; + } + + // Prevent any subtle race conditions by wrapping a mutex lock around + // the actual logging action per se. + { + MutexLock l(&log_mutex); + (this->*(data_->send_method_))(); + ++num_messages_[static_cast(data_->severity_)]; + } + LogDestination::WaitForSinks(data_); + + if (append_newline) { + // Fix the ostrstream back how it was before we screwed with it. + // It's 99.44% certain that we don't need to worry about doing this. + data_->message_text_[data_->num_chars_to_log_-1] = original_final_char; + } + + // If errno was already set before we enter the logging call, we'll + // set it back to that value when we return from the logging call. + // It happens often that we log an error message after a syscall + // failure, which can potentially set the errno to some other + // values. We would like to preserve the original errno. + if (data_->preserved_errno_ != 0) { + errno = data_->preserved_errno_; + } + + // Note that this message is now safely logged. If we're asked to flush + // again, as a result of destruction, say, we'll do nothing on future calls. + data_->has_been_flushed_ = true; +} + +// Copy of first FATAL log message so that we can print it out again +// after all the stack traces. To preserve legacy behavior, we don't +// use fatal_msg_data_exclusive. +static time_t fatal_time; +static char fatal_message[256]; + +void ReprintFatalMessage() { + if (fatal_message[0]) { + const int n = strlen(fatal_message); + if (!FLAGS_logtostderr) { + // Also write to stderr (don't color to avoid terminal checks) + WriteToStderr(fatal_message, n); + } + LogDestination::LogToAllLogfiles(GLOG_ERROR, fatal_time, fatal_message, n); + } +} + +// L >= log_mutex (callers must hold the log_mutex). +void LogMessage::SendToLog() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) { + static bool already_warned_before_initgoogle = false; + + log_mutex.AssertHeld(); + + RAW_DCHECK(data_->num_chars_to_log_ > 0 && + data_->message_text_[data_->num_chars_to_log_-1] == '\n', ""); + + // Messages of a given severity get logged to lower severity logs, too + + if (!already_warned_before_initgoogle && !IsGoogleLoggingInitialized()) { + const char w[] = "WARNING: Logging before InitGoogleLogging() is " + "written to STDERR\n"; + WriteToStderr(w, strlen(w)); + already_warned_before_initgoogle = true; + } + + // global flag: never log to file if set. Also -- don't log to a + // file if we haven't parsed the command line flags to get the + // program name. + if (FLAGS_logtostderr || !IsGoogleLoggingInitialized()) { + ColoredWriteToStderr(data_->severity_, + data_->message_text_, data_->num_chars_to_log_); + + // this could be protected by a flag if necessary. + LogDestination::LogToSinks(data_->severity_, + data_->fullname_, data_->basename_, + data_->line_, &data_->tm_time_, + data_->message_text_ + data_->num_prefix_chars_, + (data_->num_chars_to_log_ - + data_->num_prefix_chars_ - 1)); + } else { + + // log this message to all log files of severity <= severity_ + LogDestination::LogToAllLogfiles(data_->severity_, data_->timestamp_, + data_->message_text_, + data_->num_chars_to_log_); + + LogDestination::MaybeLogToStderr(data_->severity_, data_->message_text_, + data_->num_chars_to_log_); + LogDestination::MaybeLogToEmail(data_->severity_, data_->message_text_, + data_->num_chars_to_log_); + LogDestination::LogToSinks(data_->severity_, + data_->fullname_, data_->basename_, + data_->line_, &data_->tm_time_, + data_->message_text_ + data_->num_prefix_chars_, + (data_->num_chars_to_log_ + - data_->num_prefix_chars_ - 1)); + // NOTE: -1 removes trailing \n + } + + // If we log a FATAL message, flush all the log destinations, then toss + // a signal for others to catch. We leave the logs in a state that + // someone else can use them (as long as they flush afterwards) + if (data_->severity_ == GLOG_FATAL && exit_on_dfatal) { + if (data_->first_fatal_) { + // Store crash information so that it is accessible from within signal + // handlers that may be invoked later. + RecordCrashReason(&crash_reason); + SetCrashReason(&crash_reason); + + // Store shortened fatal message for other logs and GWQ status + const int copy = min(data_->num_chars_to_log_, + sizeof(fatal_message)-1); + memcpy(fatal_message, data_->message_text_, copy); + fatal_message[copy] = '\0'; + fatal_time = data_->timestamp_; + } + + if (!FLAGS_logtostderr) { + for (int i = 0; i < NUM_SEVERITIES; ++i) { + if ( LogDestination::log_destinations_[i] ) + LogDestination::log_destinations_[i]->logger_->Write(true, 0, "", 0); + } + } + + // release the lock that our caller (directly or indirectly) + // LogMessage::~LogMessage() grabbed so that signal handlers + // can use the logging facility. Alternately, we could add + // an entire unsafe logging interface to bypass locking + // for signal handlers but this seems simpler. + log_mutex.Unlock(); + LogDestination::WaitForSinks(data_); + + const char* message = "*** Check failure stack trace: ***\n"; + if (write(STDERR_FILENO, message, strlen(message)) < 0) { + // Ignore errors. + } + Fail(); + } +} + +void LogMessage::RecordCrashReason( + glog_internal_namespace_::CrashReason* reason) { + reason->filename = fatal_msg_data_exclusive.fullname_; + reason->line_number = fatal_msg_data_exclusive.line_; + reason->message = fatal_msg_data_exclusive.message_text_ + + fatal_msg_data_exclusive.num_prefix_chars_; +#ifdef HAVE_STACKTRACE + // Retrieve the stack trace, omitting the logging frames that got us here. + reason->depth = GetStackTrace(reason->stack, ARRAYSIZE(reason->stack), 4); +#else + reason->depth = 0; +#endif +} + +#ifdef HAVE___ATTRIBUTE__ +# define ATTRIBUTE_NORETURN __attribute__((noreturn)) +#else +# define ATTRIBUTE_NORETURN +#endif + +static void logging_fail() ATTRIBUTE_NORETURN; + +static void logging_fail() { +#if defined(_DEBUG) && defined(_MSC_VER) + // When debugging on windows, avoid the obnoxious dialog and make + // it possible to continue past a LOG(FATAL) in the debugger + __debugbreak(); +#else + abort(); +#endif +} + +typedef void (*logging_fail_func_t)() ATTRIBUTE_NORETURN; + +GOOGLE_GLOG_DLL_DECL +logging_fail_func_t g_logging_fail_func = &logging_fail; + +void InstallFailureFunction(void (*fail_func)()) { + g_logging_fail_func = (logging_fail_func_t)fail_func; +} + +void LogMessage::Fail() { + g_logging_fail_func(); +} + +// L >= log_mutex (callers must hold the log_mutex). +void LogMessage::SendToSink() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) { + if (data_->sink_ != NULL) { + RAW_DCHECK(data_->num_chars_to_log_ > 0 && + data_->message_text_[data_->num_chars_to_log_-1] == '\n', ""); + data_->sink_->send(data_->severity_, data_->fullname_, data_->basename_, + data_->line_, &data_->tm_time_, + data_->message_text_ + data_->num_prefix_chars_, + (data_->num_chars_to_log_ - + data_->num_prefix_chars_ - 1)); + } +} + +// L >= log_mutex (callers must hold the log_mutex). +void LogMessage::SendToSinkAndLog() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) { + SendToSink(); + SendToLog(); +} + +// L >= log_mutex (callers must hold the log_mutex). +void LogMessage::SaveOrSendToLog() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) { + if (data_->outvec_ != NULL) { + RAW_DCHECK(data_->num_chars_to_log_ > 0 && + data_->message_text_[data_->num_chars_to_log_-1] == '\n', ""); + // Omit prefix of message and trailing newline when recording in outvec_. + const char *start = data_->message_text_ + data_->num_prefix_chars_; + int len = data_->num_chars_to_log_ - data_->num_prefix_chars_ - 1; + data_->outvec_->push_back(string(start, len)); + } else { + SendToLog(); + } +} + +void LogMessage::WriteToStringAndLog() EXCLUSIVE_LOCKS_REQUIRED(log_mutex) { + if (data_->message_ != NULL) { + RAW_DCHECK(data_->num_chars_to_log_ > 0 && + data_->message_text_[data_->num_chars_to_log_-1] == '\n', ""); + // Omit prefix of message and trailing newline when writing to message_. + const char *start = data_->message_text_ + data_->num_prefix_chars_; + int len = data_->num_chars_to_log_ - data_->num_prefix_chars_ - 1; + data_->message_->assign(start, len); + } + SendToLog(); +} + +// L >= log_mutex (callers must hold the log_mutex). +void LogMessage::SendToSyslogAndLog() { +#ifdef HAVE_SYSLOG_H + // Before any calls to syslog(), make a single call to openlog() + static bool openlog_already_called = false; + if (!openlog_already_called) { + openlog(glog_internal_namespace_::ProgramInvocationShortName(), + LOG_CONS | LOG_NDELAY | LOG_PID, + LOG_USER); + openlog_already_called = true; + } + + // This array maps Google severity levels to syslog levels + const int SEVERITY_TO_LEVEL[] = { LOG_INFO, LOG_WARNING, LOG_ERR, LOG_EMERG }; + syslog(LOG_USER | SEVERITY_TO_LEVEL[static_cast(data_->severity_)], "%.*s", + int(data_->num_chars_to_syslog_), + data_->message_text_ + data_->num_prefix_chars_); + SendToLog(); +#else + LOG(ERROR) << "No syslog support: message=" << data_->message_text_; +#endif +} + +base::Logger* base::GetLogger(LogSeverity severity) { + MutexLock l(&log_mutex); + return LogDestination::log_destination(severity)->logger_; +} + +void base::SetLogger(LogSeverity severity, base::Logger* logger) { + MutexLock l(&log_mutex); + LogDestination::log_destination(severity)->logger_ = logger; +} + +// L < log_mutex. Acquires and releases mutex_. +int64 LogMessage::num_messages(int severity) { + MutexLock l(&log_mutex); + return num_messages_[severity]; +} + +// Output the COUNTER value. This is only valid if ostream is a +// LogStream. +ostream& operator<<(ostream &os, const PRIVATE_Counter&) { +#ifdef DISABLE_RTTI + LogMessage::LogStream *log = static_cast(&os); +#else + LogMessage::LogStream *log = dynamic_cast(&os); +#endif + CHECK(log && log == log->self()) + << "You must not use COUNTER with non-glog ostream"; + os << log->ctr(); + return os; +} + +ErrnoLogMessage::ErrnoLogMessage(const char* file, int line, + LogSeverity severity, int ctr, + void (LogMessage::*send_method)()) + : LogMessage(file, line, severity, ctr, send_method) { +} + +ErrnoLogMessage::~ErrnoLogMessage() { + // Don't access errno directly because it may have been altered + // while streaming the message. + stream() << ": " << StrError(preserved_errno()) << " [" + << preserved_errno() << "]"; +} + +void FlushLogFiles(LogSeverity min_severity) { + LogDestination::FlushLogFiles(min_severity); +} + +void FlushLogFilesUnsafe(LogSeverity min_severity) { + LogDestination::FlushLogFilesUnsafe(min_severity); +} + +void SetLogDestination(LogSeverity severity, const char* base_filename) { + LogDestination::SetLogDestination(severity, base_filename); +} + +void SetLogSymlink(LogSeverity severity, const char* symlink_basename) { + LogDestination::SetLogSymlink(severity, symlink_basename); +} + +LogSink::~LogSink() { +} + +void LogSink::WaitTillSent() { + // noop default +} + +string LogSink::ToString(LogSeverity severity, const char* file, int line, + const struct ::tm* tm_time, + const char* message, size_t message_len) { + ostringstream stream(string(message, message_len)); + stream.fill('0'); + + // FIXME(jrvb): Updating this to use the correct value for usecs + // requires changing the signature for both this method and + // LogSink::send(). This change needs to be done in a separate CL + // so subclasses of LogSink can be updated at the same time. + int usecs = 0; + + stream << LogSeverityNames[severity][0] + << setw(2) << 1+tm_time->tm_mon + << setw(2) << tm_time->tm_mday + << ' ' + << setw(2) << tm_time->tm_hour << ':' + << setw(2) << tm_time->tm_min << ':' + << setw(2) << tm_time->tm_sec << '.' + << setw(6) << usecs + << ' ' + << setfill(' ') << setw(5) << GetTID() << setfill('0') + << ' ' + << file << ':' << line << "] "; + + stream << string(message, message_len); + return stream.str(); +} + +void AddLogSink(LogSink *destination) { + LogDestination::AddLogSink(destination); +} + +void RemoveLogSink(LogSink *destination) { + LogDestination::RemoveLogSink(destination); +} + +void SetLogFilenameExtension(const char* ext) { + LogDestination::SetLogFilenameExtension(ext); +} + +void SetStderrLogging(LogSeverity min_severity) { + LogDestination::SetStderrLogging(min_severity); +} + +void SetEmailLogging(LogSeverity min_severity, const char* addresses) { + LogDestination::SetEmailLogging(min_severity, addresses); +} + +void LogToStderr() { + LogDestination::LogToStderr(); +} + +namespace base { +namespace internal { + +bool GetExitOnDFatal() { + MutexLock l(&log_mutex); + return exit_on_dfatal; +} + +// Determines whether we exit the program for a LOG(DFATAL) message in +// debug mode. It does this by skipping the call to Fail/FailQuietly. +// This is intended for testing only. +// +// This can have some effects on LOG(FATAL) as well. Failure messages +// are always allocated (rather than sharing a buffer), the crash +// reason is not recorded, the "gwq" status message is not updated, +// and the stack trace is not recorded. The LOG(FATAL) *will* still +// exit the program. Since this function is used only in testing, +// these differences are acceptable. +void SetExitOnDFatal(bool value) { + MutexLock l(&log_mutex); + exit_on_dfatal = value; +} + +} // namespace internal +} // namespace base + +// use_logging controls whether the logging functions LOG/VLOG are used +// to log errors. It should be set to false when the caller holds the +// log_mutex. +static bool SendEmailInternal(const char*dest, const char *subject, + const char*body, bool use_logging) { + if (dest && *dest) { + if ( use_logging ) { + VLOG(1) << "Trying to send TITLE:" << subject + << " BODY:" << body << " to " << dest; + } else { + fprintf(stderr, "Trying to send TITLE: %s BODY: %s to %s\n", + subject, body, dest); + } + + string cmd = + FLAGS_logmailer + " -s\"" + subject + "\" " + dest; + FILE* pipe = popen(cmd.c_str(), "w"); + if (pipe != NULL) { + // Add the body if we have one + if (body) + fwrite(body, sizeof(char), strlen(body), pipe); + bool ok = pclose(pipe) != -1; + if ( !ok ) { + if ( use_logging ) { + LOG(ERROR) << "Problems sending mail to " << dest << ": " + << StrError(errno); + } else { + fprintf(stderr, "Problems sending mail to %s: %s\n", + dest, StrError(errno).c_str()); + } + } + return ok; + } else { + if ( use_logging ) { + LOG(ERROR) << "Unable to send mail to " << dest; + } else { + fprintf(stderr, "Unable to send mail to %s\n", dest); + } + } + } + return false; +} + +bool SendEmail(const char*dest, const char *subject, const char*body){ + return SendEmailInternal(dest, subject, body, true); +} + +static void GetTempDirectories(vector* list) { + list->clear(); +#ifdef OS_WINDOWS + // On windows we'll try to find a directory in this order: + // C:/Documents & Settings/whomever/TEMP (or whatever GetTempPath() is) + // C:/TMP/ + // C:/TEMP/ + // C:/WINDOWS/ or C:/WINNT/ + // . + char tmp[MAX_PATH]; + if (GetTempPathA(MAX_PATH, tmp)) + list->push_back(tmp); + list->push_back("C:\\tmp\\"); + list->push_back("C:\\temp\\"); +#else + // Directories, in order of preference. If we find a dir that + // exists, we stop adding other less-preferred dirs + const char * candidates[] = { + // Non-null only during unittest/regtest + getenv("TEST_TMPDIR"), + + // Explicitly-supplied temp dirs + getenv("TMPDIR"), getenv("TMP"), + + // If all else fails + "/tmp", + }; + + for (size_t i = 0; i < ARRAYSIZE(candidates); i++) { + const char *d = candidates[i]; + if (!d) continue; // Empty env var + + // Make sure we don't surprise anyone who's expecting a '/' + string dstr = d; + if (dstr[dstr.size() - 1] != '/') { + dstr += "/"; + } + list->push_back(dstr); + + struct stat statbuf; + if (!stat(d, &statbuf) && S_ISDIR(statbuf.st_mode)) { + // We found a dir that exists - we're done. + return; + } + } + +#endif +} + +static vector* logging_directories_list; + +const vector& GetLoggingDirectories() { + // Not strictly thread-safe but we're called early in InitGoogle(). + if (logging_directories_list == NULL) { + logging_directories_list = new vector; + + if ( !FLAGS_log_dir.empty() ) { + // A dir was specified, we should use it + logging_directories_list->push_back(FLAGS_log_dir.c_str()); + } else { + GetTempDirectories(logging_directories_list); +#ifdef OS_WINDOWS + char tmp[MAX_PATH]; + if (GetWindowsDirectoryA(tmp, MAX_PATH)) + logging_directories_list->push_back(tmp); + logging_directories_list->push_back(".\\"); +#else + logging_directories_list->push_back("./"); +#endif + } + } + return *logging_directories_list; +} + +void TestOnly_ClearLoggingDirectoriesList() { + fprintf(stderr, "TestOnly_ClearLoggingDirectoriesList should only be " + "called from test code.\n"); + delete logging_directories_list; + logging_directories_list = NULL; +} + +void GetExistingTempDirectories(vector* list) { + GetTempDirectories(list); + vector::iterator i_dir = list->begin(); + while( i_dir != list->end() ) { + // zero arg to access means test for existence; no constant + // defined on windows + if ( access(i_dir->c_str(), 0) ) { + i_dir = list->erase(i_dir); + } else { + ++i_dir; + } + } +} + +void TruncateLogFile(const char *path, int64 limit, int64 keep) { +#ifdef HAVE_UNISTD_H + struct stat statbuf; + const int kCopyBlockSize = 8 << 10; + char copybuf[kCopyBlockSize]; + int64 read_offset, write_offset; + // Don't follow symlinks unless they're our own fd symlinks in /proc + int flags = O_RDWR; + // TODO(hamaji): Support other environments. +#ifdef OS_LINUX + const char *procfd_prefix = "/proc/self/fd/"; + if (strncmp(procfd_prefix, path, strlen(procfd_prefix))) flags |= O_NOFOLLOW; +#endif + + int fd = open(path, flags); + if (fd == -1) { + if (errno == EFBIG) { + // The log file in question has got too big for us to open. The + // real fix for this would be to compile logging.cc (or probably + // all of base/...) with -D_FILE_OFFSET_BITS=64 but that's + // rather scary. + // Instead just truncate the file to something we can manage + if (truncate(path, 0) == -1) { + PLOG(ERROR) << "Unable to truncate " << path; + } else { + LOG(ERROR) << "Truncated " << path << " due to EFBIG error"; + } + } else { + PLOG(ERROR) << "Unable to open " << path; + } + return; + } + + if (fstat(fd, &statbuf) == -1) { + PLOG(ERROR) << "Unable to fstat()"; + goto out_close_fd; + } + + // See if the path refers to a regular file bigger than the + // specified limit + if (!S_ISREG(statbuf.st_mode)) goto out_close_fd; + if (statbuf.st_size <= limit) goto out_close_fd; + if (statbuf.st_size <= keep) goto out_close_fd; + + // This log file is too large - we need to truncate it + LOG(INFO) << "Truncating " << path << " to " << keep << " bytes"; + + // Copy the last "keep" bytes of the file to the beginning of the file + read_offset = statbuf.st_size - keep; + write_offset = 0; + int bytesin, bytesout; + while ((bytesin = pread(fd, copybuf, sizeof(copybuf), read_offset)) > 0) { + bytesout = pwrite(fd, copybuf, bytesin, write_offset); + if (bytesout == -1) { + PLOG(ERROR) << "Unable to write to " << path; + break; + } else if (bytesout != bytesin) { + LOG(ERROR) << "Expected to write " << bytesin << ", wrote " << bytesout; + } + read_offset += bytesin; + write_offset += bytesout; + } + if (bytesin == -1) PLOG(ERROR) << "Unable to read from " << path; + + // Truncate the remainder of the file. If someone else writes to the + // end of the file after our last read() above, we lose their latest + // data. Too bad ... + if (ftruncate(fd, write_offset) == -1) { + PLOG(ERROR) << "Unable to truncate " << path; + } + + out_close_fd: + close(fd); +#else + LOG(ERROR) << "No log truncation support."; +#endif +} + +void TruncateStdoutStderr() { +#ifdef HAVE_UNISTD_H + int64 limit = MaxLogSize() << 20; + int64 keep = 1 << 20; + TruncateLogFile("/proc/self/fd/1", limit, keep); + TruncateLogFile("/proc/self/fd/2", limit, keep); +#else + LOG(ERROR) << "No log truncation support."; +#endif +} + + +// Helper functions for string comparisons. +#define DEFINE_CHECK_STROP_IMPL(name, func, expected) \ + string* Check##func##expected##Impl(const char* s1, const char* s2, \ + const char* names) { \ + bool equal = s1 == s2 || (s1 && s2 && !func(s1, s2)); \ + if (equal == expected) return NULL; \ + else { \ + ostringstream ss; \ + if (!s1) s1 = ""; \ + if (!s2) s2 = ""; \ + ss << #name " failed: " << names << " (" << s1 << " vs. " << s2 << ")"; \ + return new string(ss.str()); \ + } \ + } +DEFINE_CHECK_STROP_IMPL(CHECK_STREQ, strcmp, true) +DEFINE_CHECK_STROP_IMPL(CHECK_STRNE, strcmp, false) +DEFINE_CHECK_STROP_IMPL(CHECK_STRCASEEQ, strcasecmp, true) +DEFINE_CHECK_STROP_IMPL(CHECK_STRCASENE, strcasecmp, false) +#undef DEFINE_CHECK_STROP_IMPL + +int posix_strerror_r(int err, char *buf, size_t len) { + // Sanity check input parameters + if (buf == NULL || len <= 0) { + errno = EINVAL; + return -1; + } + + // Reset buf and errno, and try calling whatever version of strerror_r() + // is implemented by glibc + buf[0] = '\000'; + int old_errno = errno; + errno = 0; + char *rc = reinterpret_cast(strerror_r(err, buf, len)); + + // Both versions set errno on failure + if (errno) { + // Should already be there, but better safe than sorry + buf[0] = '\000'; + return -1; + } + errno = old_errno; + + // POSIX is vague about whether the string will be terminated, although + // is indirectly implies that typically ERANGE will be returned, instead + // of truncating the string. This is different from the GNU implementation. + // We play it safe by always terminating the string explicitly. + buf[len-1] = '\000'; + + // If the function succeeded, we can use its exit code to determine the + // semantics implemented by glibc + if (!rc) { + return 0; + } else { + // GNU semantics detected + if (rc == buf) { + return 0; + } else { + buf[0] = '\000'; +#if defined(OS_MACOSX) || defined(OS_FREEBSD) || defined(OS_OPENBSD) + if (reinterpret_cast(rc) < sys_nerr) { + // This means an error on MacOSX or FreeBSD. + return -1; + } +#endif + strncat(buf, rc, len-1); + return 0; + } + } +} + +string StrError(int err) { + char buf[100]; + int rc = posix_strerror_r(err, buf, sizeof(buf)); + if ((rc < 0) || (buf[0] == '\000')) { + snprintf(buf, sizeof(buf), "Error number %d", err); + } + return buf; +} + +LogMessageFatal::LogMessageFatal(const char* file, int line) : + LogMessage(file, line, GLOG_FATAL) {} + +LogMessageFatal::LogMessageFatal(const char* file, int line, + const CheckOpString& result) : + LogMessage(file, line, result) {} + +LogMessageFatal::~LogMessageFatal() { + Flush(); + LogMessage::Fail(); +} + +namespace base { + +CheckOpMessageBuilder::CheckOpMessageBuilder(const char *exprtext) + : stream_(new ostringstream) { + *stream_ << exprtext << " ("; +} + +CheckOpMessageBuilder::~CheckOpMessageBuilder() { + delete stream_; +} + +ostream* CheckOpMessageBuilder::ForVar2() { + *stream_ << " vs. "; + return stream_; +} + +string* CheckOpMessageBuilder::NewString() { + *stream_ << ")"; + return new string(stream_->str()); +} + +} // namespace base + +template <> +void MakeCheckOpValueString(std::ostream* os, const char& v) { + if (v >= 32 && v <= 126) { + (*os) << "'" << v << "'"; + } else { + (*os) << "char value " << (short)v; + } +} + +template <> +void MakeCheckOpValueString(std::ostream* os, const signed char& v) { + if (v >= 32 && v <= 126) { + (*os) << "'" << v << "'"; + } else { + (*os) << "signed char value " << (short)v; + } +} + +template <> +void MakeCheckOpValueString(std::ostream* os, const unsigned char& v) { + if (v >= 32 && v <= 126) { + (*os) << "'" << v << "'"; + } else { + (*os) << "unsigned char value " << (unsigned short)v; + } +} + +void InitGoogleLogging(const char* argv0) { + glog_internal_namespace_::InitGoogleLoggingUtilities(argv0); +} + +void ShutdownGoogleLogging() { + glog_internal_namespace_::ShutdownGoogleLoggingUtilities(); + LogDestination::DeleteLogDestinations(); + delete logging_directories_list; + logging_directories_list = NULL; +} + +_END_GOOGLE_NAMESPACE_ diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..6a444bec6 --- /dev/null +++ b/build.gradle @@ -0,0 +1,68 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This is a general purpose Gradle build. + * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds/ + */ + +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.1.2' + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:${GRADLE_BINTRAY_PLUGIN_VERSION}" + classpath "com.github.dcendents:android-maven-gradle-plugin:${ANDROID_MAVEN_GRADLE_PLUGIN_VERSION}" + classpath "com.github.ben-manes:gradle-versions-plugin:${GRADLE_VERSIONS_PLUGIN_VERSION}" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${KOTLIN_VERSION}" + classpath 'de.undercouch:gradle-download-task:3.1.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +subprojects { + repositories { + jcenter() + google() + } +} + +ext { + minSdkVersion = 15 + targetSdkVersion = 25 + compileSdkVersion = 26 + buildToolsVersion = '27.0.3' + sourceCompatibilityVersion = JavaVersion.VERSION_1_7 + targetCompatibilityVersion = JavaVersion.VERSION_1_7 +} + +ext.deps = [ + // Android support + supportAnnotations : 'com.android.support:support-annotations:27.0.2', + supportAppCompat : 'com.android.support:appcompat-v7:26.1.0', + supportCoreUi : 'com.android.support:support-core-ui:26.1.0', + supportRecyclerView: 'com.android.support:recyclerview-v7:26.1.0', + supportEspresso : 'com.android.support.test.espresso:espresso-core:2.2.2', + supportEspressoIntents : 'com.android.support.test.espresso:espresso-intents:2.2.2', + supportTestRunner : 'com.android.support.test:runner:1.0.1', + // Arch + archPaging : 'android.arch.paging:runtime:1.0.0-alpha3', + // First-party + soloader : 'com.facebook.soloader:soloader:0.4.1', + screenshot : 'com.facebook.testing.screenshot:core:0.5.0', + // Annotations + jsr305 : 'com.google.code.findbugs:jsr305:3.0.1', + inferAnnotations : 'com.facebook.infer.annotation:infer-annotation:0.11.2', + lithoAnnotations : 'com.facebook.litho:litho-annotations:0.15.0', + // Debugging and testing + guava : 'com.google.guava:guava:20.0', + robolectric : 'org.robolectric:robolectric:3.0', + junit : 'junit:junit:4.12', + guava : 'com.google.guava:guava:20.0', + stetho : 'com.facebook.stetho:stetho:1.5.0', + okhttp3 : 'com.squareup.okhttp3:okhttp:3.10.0' + +] diff --git a/docs/assets/initial.png b/docs/assets/initial.png new file mode 100644 index 000000000..f8a2bf9a5 Binary files /dev/null and b/docs/assets/initial.png differ diff --git a/docs/assets/layout.png b/docs/assets/layout.png new file mode 100644 index 000000000..6ba7a37a1 Binary files /dev/null and b/docs/assets/layout.png differ diff --git a/docs/assets/logs.png b/docs/assets/logs.png new file mode 100644 index 000000000..38edd03ad Binary files /dev/null and b/docs/assets/logs.png differ diff --git a/docs/assets/network.png b/docs/assets/network.png new file mode 100644 index 000000000..520aa3dfe Binary files /dev/null and b/docs/assets/network.png differ diff --git a/docs/communitcating.md b/docs/communitcating.md new file mode 100644 index 000000000..33c6596af --- /dev/null +++ b/docs/communitcating.md @@ -0,0 +1,46 @@ +--- +id: communicating +title: Device Communication +sidebar_label: Device Communication +--- + +To start communicating with a client your plugin must implement the init function. Once this function has been called the active client can also be accessed via `this.client`. This `id` of the plugin in JavaScript much match the native plugin `id` to allow for them to communicate. + +```javascript +class extends SonarPlugin { + static title = "MyPlugin"; + static id = 'MyPlugin'; + + init() { + // Setup subscriptions etc using this.client + } +} +``` + +There are three main ways your desktop plugin can communicate with connected devices. + +## Remote method calls + +With remote method calls your plugin can call a method on the attached client. This is useful for querying information from the client that your plugin wants to display. The first parameter is the name of the client API and the second is the specific method on that API. Optionally a JSON object can be passed as an argument to the client. + +```javascript +this.client.call('methodName', DATA).then(res => { + // res contains client response +}); +``` + +This function return as promise so that you can await a potential response from the client. If you are calling a method on the client but don't expect a response then you should instead opt for using the `send()` function. + +```javascript +this.client.send('methodName', DATA); +``` + +## Subscriptions + +A client is not only able to respond to method calls but also push data directly to the Sonar desktop app. With the subscribe API your plugin can subscribe to there pushes from the client. Pass the name of the method and the API it is part of as well as a callback function to start a subscription. Any time the client sends a push matching this method the callback will be called with any attached data as a javascript object. + +```javascript +this.client.subscribe('methodName', data => { + // data contains any payload sent by the client +}); +``` diff --git a/docs/create-plugin.md b/docs/create-plugin.md new file mode 100644 index 000000000..5fd61115f --- /dev/null +++ b/docs/create-plugin.md @@ -0,0 +1,140 @@ +--- +id: create-plugin +title: Mobile Setup +sidebar_label: Mobile Setup +--- + +## Implement SonarPlugin + +Create a class implementing `SonarPlugin`. + +### Android + +```java +public class MySonarPlugin implements SonarPlugin { + private SonarConnection mConnection; + + @Override + public String getId() { + return "MySonarPlugin"; + } + + @Override + public void onConnect(SonarConnection connection) throws Exception { + mConnection = connection; + } + + @Override + public void onDisconnect() throws Exception { + mConnection = null; + } +} +``` + +### iOS + +```objective-c +@interface MySonarPlugin : NSObject +@end + +@implementation MySonarPlugin + +- (NSString*)identifier { return @"MySonarPlugin"; } +- (void)didConnect:(SonarConnection*)connection {} +- (void)didDisonnect {} + +@end +``` + +### C++ + +```c++ +class MySonarPlugin : public SonarPlugin { +public: + std::string identifier() const override { return "MySonarPlugin"; } + void didConnect(std::shared_ptr conn) override; + void didDisconnect() override; +}; +``` + +## Using SonarConnection + +Using the `SonarConnection` object you can register a receiver of a desktop method call and respond with data. + +### Android + +```java +connection.receive("getData", new SonarReceiver() { + @Override + public void onReceive(SonarObject params, SonarResponder responder) throws Exception { + responder.success( + new SonarObject.Builder() + .put("data", MyData.get()) + .build()); + } +}); +``` + +### iOS + +```objective-c +@interface MySonarPlugin : NSObject +@end + +@implementation MySonarPlugin + +- (NSString*)identifier { return @"MySonarPlugin"; } + +- (void)didConnect:(SonarConnection*)connection +{ + [connection receive:@"getData" withBlock:^(NSDictionary *params, SonarResponder *responder) { + [responder success:@{ + @"data":[MyData get], + }]; + }]; +} + +- (void)didDisonnect {} + +@end +``` + +### C++ + +```c++ +void MySonarPlugin::didConnect(std::shared_ptr conn) { + conn->receive("getData", [](const folly::dynamic ¶ms, + std::unique_ptr responder) { + dynamic response = folly::dynamic::object("data", getMyData()); + responder->success(response); + }); +} +``` + +## Push data to the desktop + +You don't have to wait for the desktop to request data though, you can also push data directly to the desktop. + +### Android + +```java +connection.send("MyMessage", + new SonarObject.Builder() + .put("message", "Hello") + .build() +``` + +### iOS + +```objective-c +[connection send:@"getData" withParams:@{@"message":@"hello"}]; +``` + +### C++ + +```c++ +void MySonarPlugin::didConnect(std::shared_ptr conn) { + dynamic message = folly::dynamic::object("message", "hello"); + conn->send("getData", message); +} +``` diff --git a/docs/create-table-plugin.md b/docs/create-table-plugin.md new file mode 100644 index 000000000..323955e09 --- /dev/null +++ b/docs/create-table-plugin.md @@ -0,0 +1,86 @@ +--- +id: create-table-plugin +title: Create Table Plugin +sidebar_label: Create Table Plugin +--- + +A very common kind of Sonar plugin is a plugin which fetches some structured data from the device and presents it in a table. + +To make building these kinds of plugins as easy as possible we have created an abstraction we call `createTablePlugin`. This is a function which manages the complexities of building a table plugin but still allows you to customize many things to suite your needs. + +Below is a sample implementation of a desktop plugin based on `createTablePlugin`. It subscribes to updates from a client plugin with id `myplugin` sending rows to with the `newRow` method. A row can have any structure you want as long as it has a unique field `id` of type `string`. + +See "[Create Plugin](create-plugin.md)" for how to create the native counterpart for your plugin. + +```javascript +import {ManagedDataInspector, Panel, Text, createTablePlugin} from 'sonar'; + +type Id = string; + +type Row = { + id: Id, + column1: string, + column2: string, + column3: string, + extras: Object, +}; + +function buildRow(row: Row) { + return { + columns: { + column1: { + value: {row.column1}, + filterValue: row.column1, + }, + column2: { + value: {row.column2}, + filterValue: row.column2, + }, + column3: { + value: {row.column3}, + filterValue: row.column3, + }, + }, + key: row.id, + copyText: JSON.stringify(row), + filterValue: `${row.column1} ${row.column2} ${row.column3}`, + }; +} + +function renderSidebar(row: Row) { + return ( + + + + ); +} + +const columns = { + time: { + value: 'Column1', + }, + module: { + value: 'Column2', + }, + name: { + value: 'Column3', + }, +}; + +const columnSizes = { + time: '15%', + module: '20%', + name: 'flex', +}; + +export default createTablePlugin({ + title: 'My Plugin', // Title of plugin + id: 'myplugin', // ID of plugin + method: 'newRow', // Method which should be subscribed to to get new rows with share Row (from above), + icon: 'washing-machine', + columns, + columnSizes, + renderSidebar, + buildRow, +}); +``` diff --git a/docs/error-handling.md b/docs/error-handling.md new file mode 100644 index 000000000..12a5f9725 --- /dev/null +++ b/docs/error-handling.md @@ -0,0 +1,32 @@ +--- +id: error-handling +title: Error Handling +sidebar_label: Error Handling +--- + +Errors in Sonar plugins should be hidden from the user while providing actionable data to the plugin developer. + +## Android + +To gracefully handle errors in Sonar we provide the `ErrorReportingRunnable` class. This is a custom runnable which catches all exceptions, stopping them from crashing the application and reporting them to the plugin developer. + +```java +new ErrorReportingRunnable(mConnection) { + @Override + public void runOrThrow() throws Exception { + mightThrowException(); + } +}.run(); +``` + +Executing this block of code will always finish without error but may transfer any silences error to the Sonar desktop app. During plugin development these java stack traces are surfaced in the chrome dev console. In production the errors are instead sent to and a task is assigned so that you can quickly deploy a fix. + +Always use `ErrorReportingRunnable` for error handling instead of `try`/`catch` or even worse letting errors crash the app. With ErrorReportingRunnable you won't block anyone and you won't hide any stack traces. + +## C++ + +To gracefully handle errors in Sonar we perform all transactions inside of a try block which catches all exceptions, stopping them from crashing the application and reporting them to the plugin developer. This includes your own customs implementations of `SonarPlugin::didConnect()` and `SonarConnection::send()` and `::receive()`! + +That means you can safely throw exceptions in your plugin code. The exception messages will be sent to the Sonar desktop app. During plugin development the exception messages are surfaced in the Chrome dev console. + +If your plugin performs asynchronous work in which exceptions are thrown, these exceptions will not be caught by the Sonar infrastructure. You should handle them appropriately. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 000000000..53fc98a95 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,82 @@ +--- +id: getting-started +title: Getting Started +sidebar_label: Getting Started +--- + +Sonar helps you debug Android and iOS apps running in an emulator/simulator or connected physical development devices. Sonar consists of two parts: + +* The desktop app for macOS +* The native mobile SDKs for Android and iOS + +To use Sonar, you need to add the mobile SDK to your app. + +## Setup + +### Desktop app + +The desktop part of Sonar doesn't need any particular setup. Simply [download the latest build](https://www.facebook.com/sonar/public/mac) of our app and launch it. The desktop app is available for macOS and requires a working installation of the Android/iOS development tools on your system. + +Once you start Sonar and launch an emulator/simulator or connect a device, you will already be able to see the device logs in Sonar. To see app specific data, you need to integrate our native SDKs with your app. + +![Logs plugin](/docs/assets/initial.png) + +### Setup your Android app + +TODO: Install dependencies + +TODO: Add dependencies to your `build.gradle` file. + +Now you can initialize Sonar in your Application's `onCreate`-method like this: + +```java +public class MyApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + + if (BuildConfig.DEBUG && SonarUtils.isMainProcess(mApplicationContext)) { + final SonarClient client = AndroidSonarClient.getInstance(this); + client.addPlugin(new MySonarPlugin()); + client.start(); + } + } +} +``` + +### Setup your iOS app + +To integrate with our iOS app, you can use [CocoaPods](https://cocoapods.org). Add the mobile Sonar SDK to your `Podfile`: + +```ruby +platform :ios, '8.0' +use_frameworks! + +target 'MyApp' do + pod 'Sonar', '~> 1.0' +end +``` + +and install the dependencies by running `pod install`. When you open the Xcode workspace file for your app, you now can import and initialize Sonar in your AppDelegate. + +```objective-c +#import + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ +#if DEBUG + SonarClient *client = [SonarClient sharedClient]; + [client addPlugin:[MySonarPlugin new]]; + [client start]; +#endif + ... +} +@end +``` + +## Ready for takeoff + +Finally you need to add plugins to your Sonar client. See [Network Plugin](network-plugin.md) and [Layout Inspector Plugin](layout-plugin.md) on how to add them. diff --git a/docs/jssetup.md b/docs/jssetup.md new file mode 100644 index 000000000..83f76ca1f --- /dev/null +++ b/docs/jssetup.md @@ -0,0 +1,58 @@ +--- +id: js-setup +title: JavaScript Setup +sidebar_label: JavaScript Setup +--- + +## Creating the plugin UI + +To create the desktop part of your plugin, initiate a new JavaScript project using `yarn init` and make sure your package name starts with `sonar-plugin-` and a file called `index.js`, which is the entry point to your plugin. A sample `package.json`-file could look like this: + +``` +{ + "name": "sonar-plugin-myplugin", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": {} +} +``` + +In `index.js` you can now create your plugin. We expect this file to have a default export of type `SonarPlugin`. A hello-world-plugin could look like this: + +```js +import {SonarPlugin} from 'sonar'; + +export default class extends SonarPlugin { + render() { + return 'hello world'; + } +} +``` + +Learn more on how to use [Sonar's UI components](ui-components.md). + +### Dynamically loading plugins + +Once a plugin is created, Sonar can load it from it's folder. The path from where the plugins are loaded is specified in `~/.sonar/config.json`. Add the parent folder of your plugin to `pluginPaths` and start Sonar. + +### npm dependencies + +If you need any dependencies in your plugin, you can install them using `yarn add`. The Sonar UI components exported from `sonar`, as well as `react` and `react-dom` don't need to be installed as dependencies. Our plugin-loader makes these dependencies available to your plugin. + +### ES6, babel-transforms and bundling + +Our plugin-loader is capable of all ES6 goodness, flow annotations and JSX and applies the required babel-transforms without you having to care about this. Also you don't need to bundle your plugin, you can simply use ES6 imports and it will work out of the box. + +## Working on the core + +If you only want to work on a plugin, you don't need to run the development build of Sonar, but you can use the production release. However, if you want to contribute to Sonar's core, add additional UI components, or do anything outside the scope of a single plugins this is how you run the development version of Sonar. + +Make sure you have a recent version of node.js and yarn installed on your system (node ≥ 8, yarn ≥ 1.5). Then run the following commands: + +``` +git clone https://github.com/facebook/Sonar.git +cd Sonar +yarn +yarn start +``` diff --git a/docs/layout-plugin.md b/docs/layout-plugin.md new file mode 100644 index 000000000..58d847bc8 --- /dev/null +++ b/docs/layout-plugin.md @@ -0,0 +1,54 @@ +--- +id: layout-plugin +title: Layout Inspector +--- + +The Layout Inspector in Sonar is useful for a ton of different debugging scenarios. First of all you can inspect what views the hierarchy is made up of as well as what properties each view has. This is incredibly useful when debugging issues with your product. + +The Layout tab supports [Litho](https://fblitho.com) and [ComponentKit](https://componentkit.org) components as well! We integrate with these frameworks to present components in the hierarchy just as if they were native views. We show you all the layout properties, props, and state of the components. The layout inspector is further extensible to support other UI frameworks. + +If you hover over a view or component in Sonar we will highlight the corresponding item in your app! This is perfect for debugging the bounds of your views and making sure you have the correct visual padding. + +![Layout plugin](/docs/assets/layout.png) + +## Setup + +To use the layout inspector plugin, you need to add the plugin to your Sonar client instance. + +### Android + +```java +import com.facebook.sonar.plugins.inspector.DescriptorMapping; +import com.facebook.sonar.plugins.inspector.InspectorSonarPlugin; + +final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults(); +client.addPlugin(new InspectorSonarPlugin(mApplicationContext, descriptorMapping)); +``` + +### iOS + +```objective-c +#import +#import + +SKDescriptorMapper *mapper = [[SKDescriptorMapper alloc] initWithDefaults]; +[client addPlugin:[[SonarKitLayoutPlugin alloc] initWithRootNode:context.application withDescriptorMapper:mapper]] +``` + +## Quick edits + +The Layout Inspector not only allows you to view the hierarchy and inspect each item's properties, but it also allows you to edit things such as layout attributes, background colors, props, and state. Most things actually! This allows you to quickly tweak paddings, margins, and colors until you are happy with them, all without re-compiling. This can save you many hours implementing a new design. + +## Target mode + +Enable target mode by clicking on the crosshairs icon. Now, you can touch any view on the device and Layout Inspector will jump to the correct position within your layout hierarchy. + +### Blocking fullscreen views (Android only) + +The issue is that if you have some view that occupies big part of the screen but draws nothing and its Z-position is higher than your main content, then selecting view/component through Layout Inspector doesn't work as you intended, as it will always hit that transparent view and you need to manually navigate to the view you need which is time-consuming and should not be necessary. + +Add the following tag to your view to skip it from Sonar's view picker. The view will still be shown in the layout hierarchy, but it will not be selected while using the view picker. + +```java +view.setTag("sonar_skip_view_traversal", true); +``` diff --git a/docs/logs-plugin.md b/docs/logs-plugin.md new file mode 100644 index 000000000..0a1460c49 --- /dev/null +++ b/docs/logs-plugin.md @@ -0,0 +1,18 @@ +--- +id: logs-plugin +title: Logs +--- + +The Logs plugin shows device logs, without any additional setup. This is a device plugin, meaning that it is not tied to any specific app and there is no additional setup needed to see the logs. + +![Logs plugin](/docs/assets/logs.png) + +## Filtering + +The search bar can be used to search for logs and filter for certain types. From the context menu on the table headers you can show more infos like timestamp, PID or TID. Click on a tag, PID or TID in the table to filter only for logs with the same value. + +## Expression watcher + +The expression watcher in the sidebar can be used to watch for certain logs to happen and count how often the occur. An expression can be a simple string, or a regular expression which is matched against the logs. + +When the notify checkbox is enabled, Sonar will send notifications once the log is happening. This let's you know when the watcher triggered, even if Sonar is in the background. diff --git a/docs/network-plugin.md b/docs/network-plugin.md new file mode 100644 index 000000000..90d479a27 --- /dev/null +++ b/docs/network-plugin.md @@ -0,0 +1,32 @@ +--- +id: network-plugin +title: Network +--- + +Use the Network inspector to inspect outgoing network traffic our apps. You can easily browse all requests being made and their responses. The plugin also supports gzipped responses. + +![Netowrk plugin](/docs/assets/network.png) + +## Setup + +To use the network plugin, you need to add the plugin to your Sonar client instance. + +### Android + +```java +import com.facebook.sonar.plugins.network.NetworkSonarPlugin; + +client.addPlugin(new NetworkSonarPlugin()); +``` + +### iOS + +```objective-c +#import + +[client addPlugin: [SonarKitNetworkPlugin new]] +``` + +## Usage + +All request sent from the device will be listed in the plugin. Click on a request to see details like headers and body. You can filter the table for domain, method or status by clicking on the corresponding value in the table. diff --git a/docs/send-data.md b/docs/send-data.md new file mode 100644 index 000000000..5308dfcd9 --- /dev/null +++ b/docs/send-data.md @@ -0,0 +1,50 @@ +--- +id: send-data +title: Sending Data to Plugins +sidebar_label: Send Data +--- + +It is often useful to get an instance of a sonar plugin to send data to it. Sonar makes this simple with built in support. + +Plugins should be treated as singleton instances as there can only be one `SonarClient` and each `SonarClient` can only have one instance of a certain plugin. The Sonar API makes this simple by offering a way to get the current client and query it for plugins. + +Plugins are identified by the string that their identifier method returns, in this example, "MySonarPlugin": + +### Android + +```java +final SonarClient client = AndroidSonarClient.getInstance(context); +// Client may be null if AndroidSonarClient.createInstance() was never called +// which is the case in production builds. +if (client != null) { + final MySonarPlugin plugin = client.getPlugin("MySonarPlugin"); + plugin.sendData(myData); +} +``` + +### iOS + +```objective-c +SonarClient *client = [SonarClient sharedClient]; +MySonarPlugin *myPlugin = [client pluginWithIdentifier:@"MySonarPlugin"]; +[myPlugin sendData:myData]; +``` + +### C++ + +```c++ +auto &client = SonarClient::instance(); + +// "MySonarPlugin is the return value of MySonarPlugin::identifier() +auto aPlugin = client.getPlugin("MySonarPlugin"); + +// aPlugin is a std::shared_ptr. Downcast to expected type. +auto myPlugin = std::static_pointer_cast(aPlugin); + +// Alternatively, use the templated version +myPlugin = client.getPlugin("MySonarPlugin"); + +myPlugin->sendData(myData); +``` + +Here, `sendData` is an example of a method that might be implemented by the sonar plugin. diff --git a/docs/stetho.md b/docs/stetho.md new file mode 100644 index 000000000..2ff631642 --- /dev/null +++ b/docs/stetho.md @@ -0,0 +1,11 @@ +--- +id: stetho +title: Stetho Guidance +sidebar_label: Stetho Guidance +--- + +In 2015, we introduced [Stetho](http://facebook.github.io/stetho/), an Android debugging bridge built on top of Chrome dev tools. While it was a valuable tool to us and many members of the community, we felt that it limited us in what we could do with it. Stetho is Android-only and while Chrome dev tools gave us a nice foundation to build upon, they also limited us in what we could build. Stetho is an Android tool and Chrome dev tools is built for web developers. This means we can only provide a good experience for the intersection of those two development environments, which was very limiting. With Sonar being built as a standalone app, we can do more things, like handling adb connections and supporting iOS, which wouldn't easily achievable with stetho. + +This is why we built Sonar. We wanted to create a platform that gives us all the flexibility we need to build more advanced features and support for iOS. One of Sonar's core concept is it's extensibility using [plugins](create-plugin.md). Plugins are written in react and we provide a set of ready-to-use UI components that allows developers to build great plugin UIs with a few lines of code. + +While offering many new features, Sonar also already covers most of the features provided by Stetho. This includes network and layout inspection, and an advanced log viewer. We are committed to continuously improving Sonar with new features and plugins, however we are aware that not all stetho features are currently supported by Sonar. If you are using a particular feature of Stetho which isn't supported by Sonar, we are more than happy to hear about your use-case. Stetho will continue to work and we are not abandoning it so you can choose the tool that fits your needs best. We are confident that Sonar will work well for most use-cases and are more than happy to accept contributions from the open-source community adding new features. diff --git a/docs/styling-components.md b/docs/styling-components.md new file mode 100644 index 000000000..462c08b32 --- /dev/null +++ b/docs/styling-components.md @@ -0,0 +1,75 @@ +--- +id: styling-components +title: Styling Components +sidebar_label: Styling Components +--- + +We use a styled-component based approach to styling our views. This means styles are defined in JavaScript and are written as CSS-stylesheets to the DOM. A component and it's styles are coupled. Styled components can extend another to inherit their styles. + +## Basic tags + +For basic building blocks (views, texts, ...) you can use the styled object. + +```javascript +import {styled} from 'sonar'; + +const MyView = styled.view({ + fontSize: 10, + color: colors.red +}); +const MyText = styled.text({ ... }); +const MyImage = styled.image({ ... }); +const MyInput = styled.input({ ... }); +``` + +But you can use any other HTML-tags like this: + +```javascript +styled.customHTMLTag('canvas', { ... }); +``` + +## Extending Sonar Components + +It's very common for components to require customizing Sonar's components in some way. For example changing colors, alignment, or wrapping behavior. There is a `extends` method on all styled components which allows adding or overwriting existing style rules. + +For these use cases when a styled component is only used within the context of a single component we encourage declaring it as a inner static instance. This makes it clear where the component is used and avoids polluting the global namespace. + +```javascript +class MyComponent extends Component { + static Container = FlexRow.extends({ + alignItems: 'center', + }); + + render() { + return ...; + } +} +``` + +## CSS + +The CSS-in-JS object passed to the styled components takes just any CSS rule, with the difference that it uses camel cased keys for the properties. Pixel-values can be numbers, any other values need to be strings. + +Dynamic values also can be functions, receiving the react props as argument. (Make sure to add properties passed to a component to the `ignoredAttributes` array to not be written to the DOM as an attribute.) + +```javascript +const MyView = styled.view( + { + fontSize: 10, + color: props => (props.disabled ? colors.red : colors.black), + }, + { + ignoredAttributes: ['disabled'], + } +); +``` + +Pseudo-classes can be used like this: + +```javascript +'&:hover': {color: colors.red}` +``` + +## Colors + +The colors module contains all standard colors used by Sonar. All the available colors are defined in `src/ui/components/colors.js` with comments about suggested usage of them. And we strongly encourage to use them. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 000000000..3817d32b9 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,96 @@ +--- +id: testing +title: Testing +sidebar_label: Testing +--- + +Developer tools are only used if they work. We have built APIs to test plugins. + +## Android + +Start by creating your first test file in this directory `MySonarPluginTest.java`. In the test method body we create our plugin which we want to test as well as a `SonarConnectionMock`. In this contrived example we simply assert that our plugin's connected status is what we expect. + +```java +@RunWith(WithTestDefaultsRunner.class) +public class MySonarPluginTest { + + @Test + public void myTest() { + final MySonarPlugin plugin = new MySonarPlugin(); + final SonarConnectionMock connection = new SonarConnectionMock(); + + plugin.onConnect(connection); + assertThat(plugin.connected(), equalTo(true)); + } +} +``` + +There are two mock classes that are used to construct tests `SonarConnectionMock` and `SonarResponderMock`. Together these can be used to write very powerful tests to verify the end to end behavior of your plugin. For example we can test if for a given incoming message our plugin responds as we expect. + +```java +@Test +public void myTest() { + final MySonarPlugin plugin = new MySonarPlugin(); + final SonarConnectionMock connection = new SonarConnectionMock(); + final SonarResponderMock responder = new SonarResponderMock(); + + plugin.onConnect(connection); + + final SonarObject params = new SonarObject.Builder() + .put("phrase", "sonar") + .build(); + connection.receivers.get("myMethod").onReceive(params, responder); + + assertThat(responder.successes, hasItem( + new SonarObject.Builder() + .put("phrase", "ranos") + .build())); +} +``` + +## C++ + +Start by creating your first test file in this directory `MySonarPluginTests.cpp` and import the testing utilities from `//xplat/sonar-client:SonarTestLib`. These utilities mock out core pieces of the communication channel so that you can test your plugin in isolation. + +``` +#include +#include +#include + +#include +#include + +namespace facebook { +namespace sonar { +namespace test { + +TEST(MySonarPluginTests, testDummy) { + EXPECT_EQ(1 + 1, 2); +} + +} // namespace test +} // namespace sonar +} // namespace facebook +``` + +Here is a simple test using these mock utilities to create a plugin, send some data, and assert that the result is as expected. + +``` +TEST(MySonarPluginTests, testDummy) { + std::vector successfulResponses; + auto responder = std::make_unique(&successfulResponses); + auto conn = std::make_shared(); + + MySonarPlugin plugin; + plugin.didConnect(conn); + + folly::dynamic message = folly::dynamic::object("param1", "hello"); + folly::dynamic expectedResponse = folly::dynamic::object("response", "Hi there"); + + auto receiver = conn->receivers_["someMethod"]; + receiver(message, std::move(responder)); + + EXPECT_EQ(successfulResponses.size(), 1); + EXPECT_EQ(successfulResponses.back(), expectedResponse); +} +``` diff --git a/docs/ui-components.md b/docs/ui-components.md new file mode 100644 index 000000000..2e4150ccf --- /dev/null +++ b/docs/ui-components.md @@ -0,0 +1,254 @@ +--- +id: ui-components +title: UI Components +sidebar_label: UI Components +--- + +Sonar has a lot of built in React components to build UIs. You can find all of these in [`src/ui/components`](https://github.com/facebook/Sonar/tree/master/src/ui/components) and can import them directly using `import {Buttom} from 'sonar'`. + +## FlexBox + +In Sonar we make heavy use of flexbox for layout. FlexBox is layout system on the web which has been specifically design for building application like UIs. Sonar provides two flexbox components `FlexRow` and `FlexColumn`. These are flexbox components with some sane defaults such as automatically scrolling content that overflows. + +```javascript +import { FlexRow, FlexColumn } from 'sonar'; + +// Align children horizontally + + {children} + + +// Align children vertically + + {children} + +``` + +To control other flexbox properties than the direction you can extend existing components, detailed in [Styling Components](styling-components.md). + +```javascript +import {FlexRow, styled} from 'sonar'; + +const CenterFlexRow = FlexRow.extends({ + justifyContent: 'center', +}); + +// Align children horizontally in the center +{children}; +``` + +## Text + +The `Text` component is available to render any text in your plugin. To render headers and subtitle differently for example, we used the styled module. With this we can also change the color, text alignment, and any other properties typically found on a `span`. + +```javascript +import {Text, styled, colors} from 'sonar'; + +const Title = Text.extends({ + color: colors.red, +}); + +Sonar Subtitle; +``` + +## Buttons + +Sonar comes with a couple of button styles built in! As always you can style then further using the styled module but we expect the pre-defined buttons to fit most UIs. + +```javascript +import {Button} from 'sonar'; + +; +``` + +You can create a group of buttons by surrounding it with ``. + +## Sidebar + +The `Sidebar` component provides a nice abstraction around some common but complex behavior. The `Sidebar` is used by all major Sonar plugins and using it in your plugin will ensure your plugin behaves similarly, such as allowing for resizing. + +```javascript +import {FlexRow, Sidebar, colors, styled} from 'infinity-ui'; +import {SonarPlugin} from 'sonar'; + +type State = {}; + +export default class MySonarPlugin extends SonarPlugin { + static title = 'My Plugin'; + static id = 'my-plugin'; + + static Red = styled.view({ + backgroundColor: colors.red, + }); + + static Blue = styled.view({ + backgroundColor: colors.blue, + }); + + render() { + return ( + + + + + + + + ); + } +} +``` + +## Panel + +Panels are a way to section data, and make it collapsible. They are often used in sidebars. Just give the Panel a heading and some content and it makes sure that it displays in the same style as the rest of Sonar. + +```javascript +import { + FlexColumn, + FlexRow, + Sidebar, + Panel, + colors, + styled, + SonarPlugin, +} from 'sonar'; + +type State = {}; + +export default class MySonarPlugin extends SonarPlugin { + static title = 'My Plugin'; + static id = 'my-plugin'; + + static Red = styled.view({ + backgroundColor: colors.red, + }); + + static Blue = styled.view({ + backgroundColor: colors.blue, + height: 200, + }); + + static Green = styled.view({ + backgroundColor: colors.green, + height: 200, + }); + + render() { + return ( + + + + + + + + + + + + + + + + ); + } +} +``` + +## DataInspector + +The `DataInspector` component is used to unpack and display a javascript object. It is used to show View properties in the layout inspector, and to show event data in the analytics plugins. + +```javascript +import {FlexColumn, DataInspector, SonarPlugin} from 'sonar'; + +type State = {}; + +export default class MySonarPlugin extends SonarPlugin { + static title = 'My Plugin'; + static id = 'my-plugin'; + + static data = { + one: 1, + two: '2', + three: [1, 2, 3], + }; + + render() { + return ( + + + + ); + } +} +``` + +## Toolbar + +The `Toolbar` component can display a toolbar with buttons, inputs, etc. A `` can be used to fill the space between items. + +```javascript +import {Toolbar, Spacer, Button, SonarPlugin} from 'sonar'; + +export default class MySonarPlugin extends SonarPlugin { + render() { + return ( + + + + + + ); + } +} +``` + +## Popover + +Used to display content in an overlay. + +```javascript +import {Popover, SonarPlugin} from 'sonar'; + +export default class MySonarPlugin extends SonarPlugin { + render() { + return ( + {this.state.popoverVisible && this.setState({popoverVisible: false})}> + ... + } + ); + } +} +``` + +## ContextMenu + +Add a native context menu to a component by wrapping it with the ContextMenu component. + +```javascript +import {ContextMenu, SonarPlugin} from 'sonar'; + +export default class MySonarPlugin extends SonarPlugin { + contextMenuItems = [ + { + label: 'Copy', + click: this.copy, + }, + { + type: 'separator', + }, + { + label: 'Clear All', + click: this.clear, + }, + ]; + + render() { + return ...; + } +} +``` diff --git a/docs/unterstanding-sonar.md b/docs/unterstanding-sonar.md new file mode 100644 index 000000000..36af832b4 --- /dev/null +++ b/docs/unterstanding-sonar.md @@ -0,0 +1,13 @@ +--- +id: understand +title: Understanding Sonar +sidebar_label: Understanding Sonar +--- + +The Sonar desktop app and the mobile native SDK establish an [rsocket](http://rsocket.io) connection which is used to send data to and from the device. Sonar does not make any restrictions on what kind of data is being sent. This enables a lot of different use-cases where you want to better understand what is going inside your app. For example you can visualize the state of local caches, events happening or trigger actions on your app from the desktop. + +## Plugins + +Sonar itself only provides the architectural platform. What makes it useful are the plugins built on top of it: [Logs](logs-plugin.md), [Layout Inspector](layout-plugin.md) and [Network Inspector](network-plugin.md) are all plugins. Plugins can be built very specific to your business logic and the use-cases you have in your app. We are shipping Sonar with a couple of built-in all-purpose plugins, but we encourage you to build your own. + +A plugin always consists of the native implementation sending and receiving data and the desktop plugin visualizing data. Learn more on how to [create a plugin](create-plugin.md). The native implementations are written in Java, Objective-C, or C++, the desktop UI is written in React. diff --git a/flow-typed/electron-menu.js b/flow-typed/electron-menu.js new file mode 100644 index 000000000..c18519784 --- /dev/null +++ b/flow-typed/electron-menu.js @@ -0,0 +1,78 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +type Electron$BrowserWindow = any; +type Electron$NativeImage = any; + +type Electron$MenuRoles = + | 'undo' + | 'redo' + | 'cut' + | 'copy' + | 'paste' + | 'pasteandmatchstyle' + | 'selectall' + | 'delete' + | 'minimize' + | 'close' + | 'quit' + | 'togglefullscreen' // macOS-only + | 'about' + | 'hide' + | 'hideothers' + | 'unhide' + | 'front' + | 'zoom' + | 'window' + | 'help' + | 'services'; + +type Electron$MenuType = 'normal' | 'separator' | 'submenu' | 'checkbox' | 'radio'; + +type Electron$MenuItemOptions = { + click?: ( + menuItem: Electron$MenuItem, + browserWindow: Object, + event: Object, + ) => void, + role?: Electron$MenuRoles, + type?: Electron$MenuType, + label?: string, + sublabel?: string, + accelerator?: string, + icon?: Object, + enabled?: boolean, + visible?: boolean, + checked?: boolean, + submenu?: Electron$MenuItem | Electron$MenuItemOptions, + id?: string, + position?: string, +}; + +declare class Electron$MenuItem { + constructor: (options: Electron$MenuItemOptions) => void, + enabled: boolean, + visible: boolean, + checked: boolean, +} + +declare class Electron$Menu { + static setApplicationMenu: (menu: Electron$Menu) => void, + static getApplicationMenu: () => ?Electron$Menu, + static sendActionToFirstResponder: (action: string) => void, + static buildFromTemplate: (templates: Array) => Electron$Menu, + popup: ( + browserWindow: Object, + x?: number, + y?: number, + positioningItem?: number, + ) => void, + popup: (x?: number, y?: number, positioningItem?: number) => void, + append: (menuItem: Electron$MenuItem) => void, + insert: (pos: number, menuItem: Electron$MenuItem) => void, + items: Array, +} diff --git a/flow-typed/npm/JSONStream_vx.x.x.js b/flow-typed/npm/JSONStream_vx.x.x.js new file mode 100644 index 000000000..acc5481b3 --- /dev/null +++ b/flow-typed/npm/JSONStream_vx.x.x.js @@ -0,0 +1,192 @@ +// flow-typed signature: 2e82c8be2483588f05be3476cd2af900 +// flow-typed version: <>/JSONStream_v^1.3.1/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'JSONStream' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'JSONStream' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'JSONStream/examples/all_docs' { + declare module.exports: any; +} + +declare module 'JSONStream/test/bool' { + declare module.exports: any; +} + +declare module 'JSONStream/test/browser' { + declare module.exports: any; +} + +declare module 'JSONStream/test/destroy_missing' { + declare module.exports: any; +} + +declare module 'JSONStream/test/disabled/doubledot1' { + declare module.exports: any; +} + +declare module 'JSONStream/test/disabled/doubledot2' { + declare module.exports: any; +} + +declare module 'JSONStream/test/empty' { + declare module.exports: any; +} + +declare module 'JSONStream/test/error_contents' { + declare module.exports: any; +} + +declare module 'JSONStream/test/fn' { + declare module.exports: any; +} + +declare module 'JSONStream/test/gen' { + declare module.exports: any; +} + +declare module 'JSONStream/test/header_footer' { + declare module.exports: any; +} + +declare module 'JSONStream/test/issues' { + declare module.exports: any; +} + +declare module 'JSONStream/test/keys' { + declare module.exports: any; +} + +declare module 'JSONStream/test/map' { + declare module.exports: any; +} + +declare module 'JSONStream/test/multiple_objects_error' { + declare module.exports: any; +} + +declare module 'JSONStream/test/multiple_objects' { + declare module.exports: any; +} + +declare module 'JSONStream/test/null' { + declare module.exports: any; +} + +declare module 'JSONStream/test/parsejson' { + declare module.exports: any; +} + +declare module 'JSONStream/test/stringify_object' { + declare module.exports: any; +} + +declare module 'JSONStream/test/stringify' { + declare module.exports: any; +} + +declare module 'JSONStream/test/test' { + declare module.exports: any; +} + +declare module 'JSONStream/test/test2' { + declare module.exports: any; +} + +declare module 'JSONStream/test/two-ways' { + declare module.exports: any; +} + +// Filename aliases +declare module 'JSONStream/examples/all_docs.js' { + declare module.exports: $Exports<'JSONStream/examples/all_docs'>; +} +declare module 'JSONStream/index' { + declare module.exports: $Exports<'JSONStream'>; +} +declare module 'JSONStream/index.js' { + declare module.exports: $Exports<'JSONStream'>; +} +declare module 'JSONStream/test/bool.js' { + declare module.exports: $Exports<'JSONStream/test/bool'>; +} +declare module 'JSONStream/test/browser.js' { + declare module.exports: $Exports<'JSONStream/test/browser'>; +} +declare module 'JSONStream/test/destroy_missing.js' { + declare module.exports: $Exports<'JSONStream/test/destroy_missing'>; +} +declare module 'JSONStream/test/disabled/doubledot1.js' { + declare module.exports: $Exports<'JSONStream/test/disabled/doubledot1'>; +} +declare module 'JSONStream/test/disabled/doubledot2.js' { + declare module.exports: $Exports<'JSONStream/test/disabled/doubledot2'>; +} +declare module 'JSONStream/test/empty.js' { + declare module.exports: $Exports<'JSONStream/test/empty'>; +} +declare module 'JSONStream/test/error_contents.js' { + declare module.exports: $Exports<'JSONStream/test/error_contents'>; +} +declare module 'JSONStream/test/fn.js' { + declare module.exports: $Exports<'JSONStream/test/fn'>; +} +declare module 'JSONStream/test/gen.js' { + declare module.exports: $Exports<'JSONStream/test/gen'>; +} +declare module 'JSONStream/test/header_footer.js' { + declare module.exports: $Exports<'JSONStream/test/header_footer'>; +} +declare module 'JSONStream/test/issues.js' { + declare module.exports: $Exports<'JSONStream/test/issues'>; +} +declare module 'JSONStream/test/keys.js' { + declare module.exports: $Exports<'JSONStream/test/keys'>; +} +declare module 'JSONStream/test/map.js' { + declare module.exports: $Exports<'JSONStream/test/map'>; +} +declare module 'JSONStream/test/multiple_objects_error.js' { + declare module.exports: $Exports<'JSONStream/test/multiple_objects_error'>; +} +declare module 'JSONStream/test/multiple_objects.js' { + declare module.exports: $Exports<'JSONStream/test/multiple_objects'>; +} +declare module 'JSONStream/test/null.js' { + declare module.exports: $Exports<'JSONStream/test/null'>; +} +declare module 'JSONStream/test/parsejson.js' { + declare module.exports: $Exports<'JSONStream/test/parsejson'>; +} +declare module 'JSONStream/test/stringify_object.js' { + declare module.exports: $Exports<'JSONStream/test/stringify_object'>; +} +declare module 'JSONStream/test/stringify.js' { + declare module.exports: $Exports<'JSONStream/test/stringify'>; +} +declare module 'JSONStream/test/test.js' { + declare module.exports: $Exports<'JSONStream/test/test'>; +} +declare module 'JSONStream/test/test2.js' { + declare module.exports: $Exports<'JSONStream/test/test2'>; +} +declare module 'JSONStream/test/two-ways.js' { + declare module.exports: $Exports<'JSONStream/test/two-ways'>; +} diff --git a/flow-typed/npm/adbkit-fb_vx.x.x.js b/flow-typed/npm/adbkit-fb_vx.x.x.js new file mode 100644 index 000000000..c1368acfb --- /dev/null +++ b/flow-typed/npm/adbkit-fb_vx.x.x.js @@ -0,0 +1,507 @@ +// flow-typed signature: 7849d9ce4b390afcfc917a08445a20eb +// flow-typed version: <>/adbkit-fb_v2.10.1/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'adbkit-fb' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'adbkit-fb' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'adbkit-fb/lib/adb' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/auth' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/client' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-serial/forward' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-serial/getdevicepath' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-serial/getserialno' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-serial/getstate' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-serial/listforwards' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-serial/waitfordevice' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/clear' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/framebuffer' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/getfeatures' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/getpackages' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/getproperties' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/install' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/isinstalled' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/listreverses' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/local' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/log' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/logcat' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/monkey' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/reboot' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/remount' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/reverse' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/root' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/screencap' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/shell' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/startactivity' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/startservice' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/sync' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/tcp' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/tcpip' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/trackjdwp' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/uninstall' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/usb' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host-transport/waitbootcomplete' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host/connect' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host/devices' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host/deviceswithpaths' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host/disconnect' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host/kill' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host/trackdevices' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host/transport' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/command/host/version' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/connection' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/dump' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/framebuffer/rgbtransform' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/keycode' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/linetransform' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/parser' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/proc/stat' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/protocol' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/sync' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/sync/entry' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/sync/pulltransfer' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/sync/pushtransfer' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/sync/stats' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/tcpusb/packet' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/tcpusb/packetreader' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/tcpusb/rollingcounter' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/tcpusb/server' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/tcpusb/service' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/tcpusb/servicemap' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/tcpusb/socket' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/tracker' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/adb/util' { + declare module.exports: any; +} + +declare module 'adbkit-fb/lib/cli' { + declare module.exports: any; +} + +// Filename aliases +declare module 'adbkit-fb/index' { + declare module.exports: $Exports<'adbkit-fb'>; +} +declare module 'adbkit-fb/index.js' { + declare module.exports: $Exports<'adbkit-fb'>; +} +declare module 'adbkit-fb/lib/adb.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb'>; +} +declare module 'adbkit-fb/lib/adb/auth.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/auth'>; +} +declare module 'adbkit-fb/lib/adb/client.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/client'>; +} +declare module 'adbkit-fb/lib/adb/command.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command'>; +} +declare module 'adbkit-fb/lib/adb/command/host-serial/forward.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/forward'>; +} +declare module 'adbkit-fb/lib/adb/command/host-serial/getdevicepath.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/getdevicepath'>; +} +declare module 'adbkit-fb/lib/adb/command/host-serial/getserialno.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/getserialno'>; +} +declare module 'adbkit-fb/lib/adb/command/host-serial/getstate.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/getstate'>; +} +declare module 'adbkit-fb/lib/adb/command/host-serial/listforwards.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/listforwards'>; +} +declare module 'adbkit-fb/lib/adb/command/host-serial/waitfordevice.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-serial/waitfordevice'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/clear.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/clear'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/framebuffer.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/framebuffer'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/getfeatures.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/getfeatures'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/getpackages.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/getpackages'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/getproperties.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/getproperties'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/install.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/install'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/isinstalled.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/isinstalled'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/listreverses.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/listreverses'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/local.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/local'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/log.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/log'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/logcat.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/logcat'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/monkey.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/monkey'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/reboot.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/reboot'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/remount.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/remount'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/reverse.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/reverse'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/root.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/root'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/screencap.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/screencap'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/shell.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/shell'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/startactivity.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/startactivity'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/startservice.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/startservice'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/sync.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/sync'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/tcp.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/tcp'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/tcpip.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/tcpip'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/trackjdwp.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/trackjdwp'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/uninstall.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/uninstall'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/usb.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/usb'>; +} +declare module 'adbkit-fb/lib/adb/command/host-transport/waitbootcomplete.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host-transport/waitbootcomplete'>; +} +declare module 'adbkit-fb/lib/adb/command/host/connect.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/connect'>; +} +declare module 'adbkit-fb/lib/adb/command/host/devices.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/devices'>; +} +declare module 'adbkit-fb/lib/adb/command/host/deviceswithpaths.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/deviceswithpaths'>; +} +declare module 'adbkit-fb/lib/adb/command/host/disconnect.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/disconnect'>; +} +declare module 'adbkit-fb/lib/adb/command/host/kill.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/kill'>; +} +declare module 'adbkit-fb/lib/adb/command/host/trackdevices.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/trackdevices'>; +} +declare module 'adbkit-fb/lib/adb/command/host/transport.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/transport'>; +} +declare module 'adbkit-fb/lib/adb/command/host/version.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/command/host/version'>; +} +declare module 'adbkit-fb/lib/adb/connection.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/connection'>; +} +declare module 'adbkit-fb/lib/adb/dump.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/dump'>; +} +declare module 'adbkit-fb/lib/adb/framebuffer/rgbtransform.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/framebuffer/rgbtransform'>; +} +declare module 'adbkit-fb/lib/adb/keycode.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/keycode'>; +} +declare module 'adbkit-fb/lib/adb/linetransform.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/linetransform'>; +} +declare module 'adbkit-fb/lib/adb/parser.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/parser'>; +} +declare module 'adbkit-fb/lib/adb/proc/stat.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/proc/stat'>; +} +declare module 'adbkit-fb/lib/adb/protocol.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/protocol'>; +} +declare module 'adbkit-fb/lib/adb/sync.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/sync'>; +} +declare module 'adbkit-fb/lib/adb/sync/entry.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/sync/entry'>; +} +declare module 'adbkit-fb/lib/adb/sync/pulltransfer.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/sync/pulltransfer'>; +} +declare module 'adbkit-fb/lib/adb/sync/pushtransfer.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/sync/pushtransfer'>; +} +declare module 'adbkit-fb/lib/adb/sync/stats.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/sync/stats'>; +} +declare module 'adbkit-fb/lib/adb/tcpusb/packet.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/packet'>; +} +declare module 'adbkit-fb/lib/adb/tcpusb/packetreader.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/packetreader'>; +} +declare module 'adbkit-fb/lib/adb/tcpusb/rollingcounter.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/rollingcounter'>; +} +declare module 'adbkit-fb/lib/adb/tcpusb/server.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/server'>; +} +declare module 'adbkit-fb/lib/adb/tcpusb/service.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/service'>; +} +declare module 'adbkit-fb/lib/adb/tcpusb/servicemap.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/servicemap'>; +} +declare module 'adbkit-fb/lib/adb/tcpusb/socket.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/tcpusb/socket'>; +} +declare module 'adbkit-fb/lib/adb/tracker.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/tracker'>; +} +declare module 'adbkit-fb/lib/adb/util.js' { + declare module.exports: $Exports<'adbkit-fb/lib/adb/util'>; +} +declare module 'adbkit-fb/lib/cli.js' { + declare module.exports: $Exports<'adbkit-fb/lib/cli'>; +} diff --git a/flow-typed/npm/adbkit-logcat-fb_vx.x.x.js b/flow-typed/npm/adbkit-logcat-fb_vx.x.x.js new file mode 100644 index 000000000..ee6b08588 --- /dev/null +++ b/flow-typed/npm/adbkit-logcat-fb_vx.x.x.js @@ -0,0 +1,80 @@ +// flow-typed signature: 4c4fcc7d39703336b04af263e250bdd1 +// flow-typed version: <>/adbkit-logcat-fb_v1.1.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'adbkit-logcat-fb' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'adbkit-logcat-fb' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'adbkit-logcat-fb/lib/logcat' { + declare module.exports: any; +} + +declare module 'adbkit-logcat-fb/lib/logcat/entry' { + declare module.exports: any; +} + +declare module 'adbkit-logcat-fb/lib/logcat/parser' { + declare module.exports: any; +} + +declare module 'adbkit-logcat-fb/lib/logcat/parser/binary' { + declare module.exports: any; +} + +declare module 'adbkit-logcat-fb/lib/logcat/priority' { + declare module.exports: any; +} + +declare module 'adbkit-logcat-fb/lib/logcat/reader' { + declare module.exports: any; +} + +declare module 'adbkit-logcat-fb/lib/logcat/transform' { + declare module.exports: any; +} + +// Filename aliases +declare module 'adbkit-logcat-fb/index' { + declare module.exports: $Exports<'adbkit-logcat-fb'>; +} +declare module 'adbkit-logcat-fb/index.js' { + declare module.exports: $Exports<'adbkit-logcat-fb'>; +} +declare module 'adbkit-logcat-fb/lib/logcat.js' { + declare module.exports: $Exports<'adbkit-logcat-fb/lib/logcat'>; +} +declare module 'adbkit-logcat-fb/lib/logcat/entry.js' { + declare module.exports: $Exports<'adbkit-logcat-fb/lib/logcat/entry'>; +} +declare module 'adbkit-logcat-fb/lib/logcat/parser.js' { + declare module.exports: $Exports<'adbkit-logcat-fb/lib/logcat/parser'>; +} +declare module 'adbkit-logcat-fb/lib/logcat/parser/binary.js' { + declare module.exports: $Exports<'adbkit-logcat-fb/lib/logcat/parser/binary'>; +} +declare module 'adbkit-logcat-fb/lib/logcat/priority.js' { + declare module.exports: $Exports<'adbkit-logcat-fb/lib/logcat/priority'>; +} +declare module 'adbkit-logcat-fb/lib/logcat/reader.js' { + declare module.exports: $Exports<'adbkit-logcat-fb/lib/logcat/reader'>; +} +declare module 'adbkit-logcat-fb/lib/logcat/transform.js' { + declare module.exports: $Exports<'adbkit-logcat-fb/lib/logcat/transform'>; +} diff --git a/flow-typed/npm/babel-cli_vx.x.x.js b/flow-typed/npm/babel-cli_vx.x.x.js new file mode 100644 index 000000000..42c472acb --- /dev/null +++ b/flow-typed/npm/babel-cli_vx.x.x.js @@ -0,0 +1,108 @@ +// flow-typed signature: 0f7f1ee584ef43d0f9403cfe273cc39d +// flow-typed version: <>/babel-cli_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-cli' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-cli' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-cli/bin/babel-doctor' { + declare module.exports: any; +} + +declare module 'babel-cli/bin/babel-external-helpers' { + declare module.exports: any; +} + +declare module 'babel-cli/bin/babel-node' { + declare module.exports: any; +} + +declare module 'babel-cli/bin/babel' { + declare module.exports: any; +} + +declare module 'babel-cli/lib/_babel-node' { + declare module.exports: any; +} + +declare module 'babel-cli/lib/babel-external-helpers' { + declare module.exports: any; +} + +declare module 'babel-cli/lib/babel-node' { + declare module.exports: any; +} + +declare module 'babel-cli/lib/babel/dir' { + declare module.exports: any; +} + +declare module 'babel-cli/lib/babel/file' { + declare module.exports: any; +} + +declare module 'babel-cli/lib/babel/index' { + declare module.exports: any; +} + +declare module 'babel-cli/lib/babel/util' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-cli/bin/babel-doctor.js' { + declare module.exports: $Exports<'babel-cli/bin/babel-doctor'>; +} +declare module 'babel-cli/bin/babel-external-helpers.js' { + declare module.exports: $Exports<'babel-cli/bin/babel-external-helpers'>; +} +declare module 'babel-cli/bin/babel-node.js' { + declare module.exports: $Exports<'babel-cli/bin/babel-node'>; +} +declare module 'babel-cli/bin/babel.js' { + declare module.exports: $Exports<'babel-cli/bin/babel'>; +} +declare module 'babel-cli/index' { + declare module.exports: $Exports<'babel-cli'>; +} +declare module 'babel-cli/index.js' { + declare module.exports: $Exports<'babel-cli'>; +} +declare module 'babel-cli/lib/_babel-node.js' { + declare module.exports: $Exports<'babel-cli/lib/_babel-node'>; +} +declare module 'babel-cli/lib/babel-external-helpers.js' { + declare module.exports: $Exports<'babel-cli/lib/babel-external-helpers'>; +} +declare module 'babel-cli/lib/babel-node.js' { + declare module.exports: $Exports<'babel-cli/lib/babel-node'>; +} +declare module 'babel-cli/lib/babel/dir.js' { + declare module.exports: $Exports<'babel-cli/lib/babel/dir'>; +} +declare module 'babel-cli/lib/babel/file.js' { + declare module.exports: $Exports<'babel-cli/lib/babel/file'>; +} +declare module 'babel-cli/lib/babel/index.js' { + declare module.exports: $Exports<'babel-cli/lib/babel/index'>; +} +declare module 'babel-cli/lib/babel/util.js' { + declare module.exports: $Exports<'babel-cli/lib/babel/util'>; +} diff --git a/flow-typed/npm/babel-code-frame_vx.x.x.js b/flow-typed/npm/babel-code-frame_vx.x.x.js new file mode 100644 index 000000000..1f6832cf1 --- /dev/null +++ b/flow-typed/npm/babel-code-frame_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: d1d2d301b3f753cb6d40f8ac6d8df8c8 +// flow-typed version: <>/babel-code-frame_v^6.22.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-code-frame' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-code-frame' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-code-frame/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-code-frame/lib/index.js' { + declare module.exports: $Exports<'babel-code-frame/lib/index'>; +} diff --git a/flow-typed/npm/babel-core_vx.x.x.js b/flow-typed/npm/babel-core_vx.x.x.js new file mode 100644 index 000000000..6a02a9515 --- /dev/null +++ b/flow-typed/npm/babel-core_vx.x.x.js @@ -0,0 +1,171 @@ +// flow-typed signature: 8f20b8fc8e610a76416674f109b1d8bc +// flow-typed version: <>/babel-core_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-core' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-core' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-core/lib/config/build-config-chain' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/caching' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/helpers/environment' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/index' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/loading/files/configuration' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/loading/files/index-browser' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/loading/files/index' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/loading/files/plugins' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/option-manager' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/plugin' { + declare module.exports: any; +} + +declare module 'babel-core/lib/config/removed' { + declare module.exports: any; +} + +declare module 'babel-core/lib/index' { + declare module.exports: any; +} + +declare module 'babel-core/lib/tools/build-external-helpers' { + declare module.exports: any; +} + +declare module 'babel-core/lib/transformation/file/index' { + declare module.exports: any; +} + +declare module 'babel-core/lib/transformation/file/metadata' { + declare module.exports: any; +} + +declare module 'babel-core/lib/transformation/internal-plugins/block-hoist' { + declare module.exports: any; +} + +declare module 'babel-core/lib/transformation/pipeline' { + declare module.exports: any; +} + +declare module 'babel-core/lib/transformation/plugin-pass' { + declare module.exports: any; +} + +declare module 'babel-core/lib/transformation/store' { + declare module.exports: any; +} + +declare module 'babel-core/register' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-core/index' { + declare module.exports: $Exports<'babel-core'>; +} +declare module 'babel-core/index.js' { + declare module.exports: $Exports<'babel-core'>; +} +declare module 'babel-core/lib/config/build-config-chain.js' { + declare module.exports: $Exports<'babel-core/lib/config/build-config-chain'>; +} +declare module 'babel-core/lib/config/caching.js' { + declare module.exports: $Exports<'babel-core/lib/config/caching'>; +} +declare module 'babel-core/lib/config/helpers/environment.js' { + declare module.exports: $Exports<'babel-core/lib/config/helpers/environment'>; +} +declare module 'babel-core/lib/config/index.js' { + declare module.exports: $Exports<'babel-core/lib/config/index'>; +} +declare module 'babel-core/lib/config/loading/files/configuration.js' { + declare module.exports: $Exports<'babel-core/lib/config/loading/files/configuration'>; +} +declare module 'babel-core/lib/config/loading/files/index-browser.js' { + declare module.exports: $Exports<'babel-core/lib/config/loading/files/index-browser'>; +} +declare module 'babel-core/lib/config/loading/files/index.js' { + declare module.exports: $Exports<'babel-core/lib/config/loading/files/index'>; +} +declare module 'babel-core/lib/config/loading/files/plugins.js' { + declare module.exports: $Exports<'babel-core/lib/config/loading/files/plugins'>; +} +declare module 'babel-core/lib/config/option-manager.js' { + declare module.exports: $Exports<'babel-core/lib/config/option-manager'>; +} +declare module 'babel-core/lib/config/plugin.js' { + declare module.exports: $Exports<'babel-core/lib/config/plugin'>; +} +declare module 'babel-core/lib/config/removed.js' { + declare module.exports: $Exports<'babel-core/lib/config/removed'>; +} +declare module 'babel-core/lib/index.js' { + declare module.exports: $Exports<'babel-core/lib/index'>; +} +declare module 'babel-core/lib/tools/build-external-helpers.js' { + declare module.exports: $Exports<'babel-core/lib/tools/build-external-helpers'>; +} +declare module 'babel-core/lib/transformation/file/index.js' { + declare module.exports: $Exports<'babel-core/lib/transformation/file/index'>; +} +declare module 'babel-core/lib/transformation/file/metadata.js' { + declare module.exports: $Exports<'babel-core/lib/transformation/file/metadata'>; +} +declare module 'babel-core/lib/transformation/internal-plugins/block-hoist.js' { + declare module.exports: $Exports<'babel-core/lib/transformation/internal-plugins/block-hoist'>; +} +declare module 'babel-core/lib/transformation/pipeline.js' { + declare module.exports: $Exports<'babel-core/lib/transformation/pipeline'>; +} +declare module 'babel-core/lib/transformation/plugin-pass.js' { + declare module.exports: $Exports<'babel-core/lib/transformation/plugin-pass'>; +} +declare module 'babel-core/lib/transformation/store.js' { + declare module.exports: $Exports<'babel-core/lib/transformation/store'>; +} +declare module 'babel-core/register.js' { + declare module.exports: $Exports<'babel-core/register'>; +} diff --git a/flow-typed/npm/babel-eslint_vx.x.x.js b/flow-typed/npm/babel-eslint_vx.x.x.js new file mode 100644 index 000000000..5d5691246 --- /dev/null +++ b/flow-typed/npm/babel-eslint_vx.x.x.js @@ -0,0 +1,80 @@ +// flow-typed signature: 1fb22ed9a932c432e0fdd6cf6450c58e +// flow-typed version: <>/babel-eslint_v7.2.3/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-eslint' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-eslint' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-eslint/babylon-to-espree/attachComments' { + declare module.exports: any; +} + +declare module 'babel-eslint/babylon-to-espree/convertComments' { + declare module.exports: any; +} + +declare module 'babel-eslint/babylon-to-espree/convertTemplateType' { + declare module.exports: any; +} + +declare module 'babel-eslint/babylon-to-espree/index' { + declare module.exports: any; +} + +declare module 'babel-eslint/babylon-to-espree/toAST' { + declare module.exports: any; +} + +declare module 'babel-eslint/babylon-to-espree/toToken' { + declare module.exports: any; +} + +declare module 'babel-eslint/babylon-to-espree/toTokens' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-eslint/babylon-to-espree/attachComments.js' { + declare module.exports: $Exports<'babel-eslint/babylon-to-espree/attachComments'>; +} +declare module 'babel-eslint/babylon-to-espree/convertComments.js' { + declare module.exports: $Exports<'babel-eslint/babylon-to-espree/convertComments'>; +} +declare module 'babel-eslint/babylon-to-espree/convertTemplateType.js' { + declare module.exports: $Exports<'babel-eslint/babylon-to-espree/convertTemplateType'>; +} +declare module 'babel-eslint/babylon-to-espree/index.js' { + declare module.exports: $Exports<'babel-eslint/babylon-to-espree/index'>; +} +declare module 'babel-eslint/babylon-to-espree/toAST.js' { + declare module.exports: $Exports<'babel-eslint/babylon-to-espree/toAST'>; +} +declare module 'babel-eslint/babylon-to-espree/toToken.js' { + declare module.exports: $Exports<'babel-eslint/babylon-to-espree/toToken'>; +} +declare module 'babel-eslint/babylon-to-espree/toTokens.js' { + declare module.exports: $Exports<'babel-eslint/babylon-to-espree/toTokens'>; +} +declare module 'babel-eslint/index' { + declare module.exports: $Exports<'babel-eslint'>; +} +declare module 'babel-eslint/index.js' { + declare module.exports: $Exports<'babel-eslint'>; +} diff --git a/flow-typed/npm/babel-generator_vx.x.x.js b/flow-typed/npm/babel-generator_vx.x.x.js new file mode 100644 index 000000000..ac06bae27 --- /dev/null +++ b/flow-typed/npm/babel-generator_vx.x.x.js @@ -0,0 +1,151 @@ +// flow-typed signature: 0b1639e0bffdd43bd3a51636725d6b05 +// flow-typed version: <>/babel-generator_v^6.24.1/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-generator' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-generator' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-generator/lib/buffer' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/base' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/classes' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/expressions' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/flow' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/jsx' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/methods' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/modules' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/statements' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/template-literals' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/generators/types' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/index' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/node/index' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/node/parentheses' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/node/whitespace' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/printer' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/source-map' { + declare module.exports: any; +} + +declare module 'babel-generator/lib/whitespace' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-generator/lib/buffer.js' { + declare module.exports: $Exports<'babel-generator/lib/buffer'>; +} +declare module 'babel-generator/lib/generators/base.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/base'>; +} +declare module 'babel-generator/lib/generators/classes.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/classes'>; +} +declare module 'babel-generator/lib/generators/expressions.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/expressions'>; +} +declare module 'babel-generator/lib/generators/flow.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/flow'>; +} +declare module 'babel-generator/lib/generators/jsx.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/jsx'>; +} +declare module 'babel-generator/lib/generators/methods.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/methods'>; +} +declare module 'babel-generator/lib/generators/modules.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/modules'>; +} +declare module 'babel-generator/lib/generators/statements.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/statements'>; +} +declare module 'babel-generator/lib/generators/template-literals.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/template-literals'>; +} +declare module 'babel-generator/lib/generators/types.js' { + declare module.exports: $Exports<'babel-generator/lib/generators/types'>; +} +declare module 'babel-generator/lib/index.js' { + declare module.exports: $Exports<'babel-generator/lib/index'>; +} +declare module 'babel-generator/lib/node/index.js' { + declare module.exports: $Exports<'babel-generator/lib/node/index'>; +} +declare module 'babel-generator/lib/node/parentheses.js' { + declare module.exports: $Exports<'babel-generator/lib/node/parentheses'>; +} +declare module 'babel-generator/lib/node/whitespace.js' { + declare module.exports: $Exports<'babel-generator/lib/node/whitespace'>; +} +declare module 'babel-generator/lib/printer.js' { + declare module.exports: $Exports<'babel-generator/lib/printer'>; +} +declare module 'babel-generator/lib/source-map.js' { + declare module.exports: $Exports<'babel-generator/lib/source-map'>; +} +declare module 'babel-generator/lib/whitespace.js' { + declare module.exports: $Exports<'babel-generator/lib/whitespace'>; +} diff --git a/flow-typed/npm/babel-plugin-transform-class-properties_vx.x.x.js b/flow-typed/npm/babel-plugin-transform-class-properties_vx.x.x.js new file mode 100644 index 000000000..e28205880 --- /dev/null +++ b/flow-typed/npm/babel-plugin-transform-class-properties_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: b952c89ce390b876119ae568acff43b6 +// flow-typed version: <>/babel-plugin-transform-class-properties_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-plugin-transform-class-properties' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-plugin-transform-class-properties' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-plugin-transform-class-properties/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-plugin-transform-class-properties/lib/index.js' { + declare module.exports: $Exports<'babel-plugin-transform-class-properties/lib/index'>; +} diff --git a/flow-typed/npm/babel-plugin-transform-es2015-destructuring_vx.x.x.js b/flow-typed/npm/babel-plugin-transform-es2015-destructuring_vx.x.x.js new file mode 100644 index 000000000..c9f302562 --- /dev/null +++ b/flow-typed/npm/babel-plugin-transform-es2015-destructuring_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: 1bad2e410546918dc0e7272dc87e0a34 +// flow-typed version: <>/babel-plugin-transform-es2015-destructuring_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-plugin-transform-es2015-destructuring' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-plugin-transform-es2015-destructuring' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-plugin-transform-es2015-destructuring/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-plugin-transform-es2015-destructuring/lib/index.js' { + declare module.exports: $Exports<'babel-plugin-transform-es2015-destructuring/lib/index'>; +} diff --git a/flow-typed/npm/babel-plugin-transform-es2015-modules-commonjs_vx.x.x.js b/flow-typed/npm/babel-plugin-transform-es2015-modules-commonjs_vx.x.x.js new file mode 100644 index 000000000..b8bde563e --- /dev/null +++ b/flow-typed/npm/babel-plugin-transform-es2015-modules-commonjs_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: bc928daf5d3cf67f45a4ae5920314b13 +// flow-typed version: <>/babel-plugin-transform-es2015-modules-commonjs_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-plugin-transform-es2015-modules-commonjs' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-plugin-transform-es2015-modules-commonjs' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-plugin-transform-es2015-modules-commonjs/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-plugin-transform-es2015-modules-commonjs/lib/index.js' { + declare module.exports: $Exports<'babel-plugin-transform-es2015-modules-commonjs/lib/index'>; +} diff --git a/flow-typed/npm/babel-plugin-transform-flow-strip-types_vx.x.x.js b/flow-typed/npm/babel-plugin-transform-flow-strip-types_vx.x.x.js new file mode 100644 index 000000000..8caeef16d --- /dev/null +++ b/flow-typed/npm/babel-plugin-transform-flow-strip-types_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: 8f14e19a39c2bd3974954eea41f59410 +// flow-typed version: <>/babel-plugin-transform-flow-strip-types_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-plugin-transform-flow-strip-types' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-plugin-transform-flow-strip-types' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-plugin-transform-flow-strip-types/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-plugin-transform-flow-strip-types/lib/index.js' { + declare module.exports: $Exports<'babel-plugin-transform-flow-strip-types/lib/index'>; +} diff --git a/flow-typed/npm/babel-plugin-transform-object-rest-spread_vx.x.x.js b/flow-typed/npm/babel-plugin-transform-object-rest-spread_vx.x.x.js new file mode 100644 index 000000000..1788c5ba0 --- /dev/null +++ b/flow-typed/npm/babel-plugin-transform-object-rest-spread_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: 529d59fc1e249a090e2a76a3835db876 +// flow-typed version: <>/babel-plugin-transform-object-rest-spread_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-plugin-transform-object-rest-spread' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-plugin-transform-object-rest-spread' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-plugin-transform-object-rest-spread/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-plugin-transform-object-rest-spread/lib/index.js' { + declare module.exports: $Exports<'babel-plugin-transform-object-rest-spread/lib/index'>; +} diff --git a/flow-typed/npm/babel-preset-react_vx.x.x.js b/flow-typed/npm/babel-preset-react_vx.x.x.js new file mode 100644 index 000000000..5d64556c3 --- /dev/null +++ b/flow-typed/npm/babel-preset-react_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: 1daf2e279938e1a1932c392cf775b7dc +// flow-typed version: <>/babel-preset-react_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-preset-react' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-preset-react' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-preset-react/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-preset-react/lib/index.js' { + declare module.exports: $Exports<'babel-preset-react/lib/index'>; +} diff --git a/flow-typed/npm/babel-register_vx.x.x.js b/flow-typed/npm/babel-register_vx.x.x.js new file mode 100644 index 000000000..66bd624c1 --- /dev/null +++ b/flow-typed/npm/babel-register_vx.x.x.js @@ -0,0 +1,46 @@ +// flow-typed signature: d1ce10c85f971481323ed89a9b5029e0 +// flow-typed version: <>/babel-register_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-register' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-register' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-register/lib/browser' { + declare module.exports: any; +} + +declare module 'babel-register/lib/cache' { + declare module.exports: any; +} + +declare module 'babel-register/lib/node' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-register/lib/browser.js' { + declare module.exports: $Exports<'babel-register/lib/browser'>; +} +declare module 'babel-register/lib/cache.js' { + declare module.exports: $Exports<'babel-register/lib/cache'>; +} +declare module 'babel-register/lib/node.js' { + declare module.exports: $Exports<'babel-register/lib/node'>; +} diff --git a/flow-typed/npm/babel-traverse_vx.x.x.js b/flow-typed/npm/babel-traverse_vx.x.x.js new file mode 100644 index 000000000..ed78f5990 --- /dev/null +++ b/flow-typed/npm/babel-traverse_vx.x.x.js @@ -0,0 +1,200 @@ +// flow-typed signature: b39ccdff10eaf6fe22d8bbd6a6415324 +// flow-typed version: <>/babel-traverse_v7.0.0-alpha.12/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babel-traverse' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babel-traverse' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babel-traverse/lib/cache' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/context' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/hub' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/index' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/ancestry' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/comments' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/context' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/conversion' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/evaluation' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/family' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/index' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/inference/index' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/inference/inferer-reference' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/inference/inferers' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/introspection' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/lib/hoister' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/lib/removal-hooks' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/lib/virtual-types' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/modification' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/removal' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/path/replacement' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/scope/binding' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/scope/index' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/scope/lib/renamer' { + declare module.exports: any; +} + +declare module 'babel-traverse/lib/visitors' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babel-traverse/lib/cache.js' { + declare module.exports: $Exports<'babel-traverse/lib/cache'>; +} +declare module 'babel-traverse/lib/context.js' { + declare module.exports: $Exports<'babel-traverse/lib/context'>; +} +declare module 'babel-traverse/lib/hub.js' { + declare module.exports: $Exports<'babel-traverse/lib/hub'>; +} +declare module 'babel-traverse/lib/index.js' { + declare module.exports: $Exports<'babel-traverse/lib/index'>; +} +declare module 'babel-traverse/lib/path/ancestry.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/ancestry'>; +} +declare module 'babel-traverse/lib/path/comments.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/comments'>; +} +declare module 'babel-traverse/lib/path/context.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/context'>; +} +declare module 'babel-traverse/lib/path/conversion.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/conversion'>; +} +declare module 'babel-traverse/lib/path/evaluation.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/evaluation'>; +} +declare module 'babel-traverse/lib/path/family.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/family'>; +} +declare module 'babel-traverse/lib/path/index.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/index'>; +} +declare module 'babel-traverse/lib/path/inference/index.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/inference/index'>; +} +declare module 'babel-traverse/lib/path/inference/inferer-reference.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/inference/inferer-reference'>; +} +declare module 'babel-traverse/lib/path/inference/inferers.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/inference/inferers'>; +} +declare module 'babel-traverse/lib/path/introspection.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/introspection'>; +} +declare module 'babel-traverse/lib/path/lib/hoister.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/lib/hoister'>; +} +declare module 'babel-traverse/lib/path/lib/removal-hooks.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/lib/removal-hooks'>; +} +declare module 'babel-traverse/lib/path/lib/virtual-types.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/lib/virtual-types'>; +} +declare module 'babel-traverse/lib/path/modification.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/modification'>; +} +declare module 'babel-traverse/lib/path/removal.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/removal'>; +} +declare module 'babel-traverse/lib/path/replacement.js' { + declare module.exports: $Exports<'babel-traverse/lib/path/replacement'>; +} +declare module 'babel-traverse/lib/scope/binding.js' { + declare module.exports: $Exports<'babel-traverse/lib/scope/binding'>; +} +declare module 'babel-traverse/lib/scope/index.js' { + declare module.exports: $Exports<'babel-traverse/lib/scope/index'>; +} +declare module 'babel-traverse/lib/scope/lib/renamer.js' { + declare module.exports: $Exports<'babel-traverse/lib/scope/lib/renamer'>; +} +declare module 'babel-traverse/lib/visitors.js' { + declare module.exports: $Exports<'babel-traverse/lib/visitors'>; +} diff --git a/flow-typed/npm/babylon_vx.x.x.js b/flow-typed/npm/babylon_vx.x.x.js new file mode 100644 index 000000000..06d23c2be --- /dev/null +++ b/flow-typed/npm/babylon_vx.x.x.js @@ -0,0 +1,46 @@ +// flow-typed signature: f472557a9bcea4a046c88cdcdd21ff40 +// flow-typed version: <>/babylon_v7.0.0-beta.14/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'babylon' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'babylon' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'babylon/bin/babylon' { + declare module.exports: any; +} + +declare module 'babylon/bin/generate-identifier-regex' { + declare module.exports: any; +} + +declare module 'babylon/lib/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'babylon/bin/babylon.js' { + declare module.exports: $Exports<'babylon/bin/babylon'>; +} +declare module 'babylon/bin/generate-identifier-regex.js' { + declare module.exports: $Exports<'babylon/bin/generate-identifier-regex'>; +} +declare module 'babylon/lib/index.js' { + declare module.exports: $Exports<'babylon/lib/index'>; +} diff --git a/flow-typed/npm/chalk_v1.x.x.js b/flow-typed/npm/chalk_v1.x.x.js new file mode 100644 index 000000000..9cabf16bc --- /dev/null +++ b/flow-typed/npm/chalk_v1.x.x.js @@ -0,0 +1,114 @@ +// flow-typed signature: b1a2d646047879188d7e44cb218212b5 +// flow-typed version: b43dff3e0e/chalk_v1.x.x/flow_>=v0.19.x + +type $npm$chalk$StyleElement = { + open: string; + close: string; +}; + +type $npm$chalk$Chain = $npm$chalk$Style & (...text: any[]) => string; + +type $npm$chalk$Style = { + // General + reset: $npm$chalk$Chain; + bold: $npm$chalk$Chain; + dim: $npm$chalk$Chain; + italic: $npm$chalk$Chain; + underline: $npm$chalk$Chain; + inverse: $npm$chalk$Chain; + strikethrough: $npm$chalk$Chain; + + // Text colors + black: $npm$chalk$Chain; + red: $npm$chalk$Chain; + green: $npm$chalk$Chain; + yellow: $npm$chalk$Chain; + blue: $npm$chalk$Chain; + magenta: $npm$chalk$Chain; + cyan: $npm$chalk$Chain; + white: $npm$chalk$Chain; + gray: $npm$chalk$Chain; + grey: $npm$chalk$Chain; + + // Background colors + bgBlack: $npm$chalk$Chain; + bgRed: $npm$chalk$Chain; + bgGreen: $npm$chalk$Chain; + bgYellow: $npm$chalk$Chain; + bgBlue: $npm$chalk$Chain; + bgMagenta: $npm$chalk$Chain; + bgCyan: $npm$chalk$Chain; + bgWhite: $npm$chalk$Chain; +}; + +type $npm$chalk$StyleMap = { + // General + reset: $npm$chalk$StyleElement; + bold: $npm$chalk$StyleElement; + dim: $npm$chalk$StyleElement; + italic: $npm$chalk$StyleElement; + underline: $npm$chalk$StyleElement; + inverse: $npm$chalk$StyleElement; + strikethrough: $npm$chalk$StyleElement; + + // Text colors + black: $npm$chalk$StyleElement; + red: $npm$chalk$StyleElement; + green: $npm$chalk$StyleElement; + yellow: $npm$chalk$StyleElement; + blue: $npm$chalk$StyleElement; + magenta: $npm$chalk$StyleElement; + cyan: $npm$chalk$StyleElement; + white: $npm$chalk$StyleElement; + gray: $npm$chalk$StyleElement; + + // Background colors + bgBlack: $npm$chalk$StyleElement; + bgRed: $npm$chalk$StyleElement; + bgGreen: $npm$chalk$StyleElement; + bgYellow: $npm$chalk$StyleElement; + bgBlue: $npm$chalk$StyleElement; + bgMagenta: $npm$chalk$StyleElement; + bgCyan: $npm$chalk$StyleElement; + bgWhite: $npm$chalk$StyleElement; +}; + +declare module "chalk" { + declare var enabled: boolean; + declare var supportsColor: boolean; + declare var styles: $npm$chalk$StyleMap; + + declare function stripColor(value: string): any; + declare function hasColor(str: string): boolean; + + // General + declare var reset: $npm$chalk$Chain; + declare var bold: $npm$chalk$Chain; + declare var dim: $npm$chalk$Chain; + declare var italic: $npm$chalk$Chain; + declare var underline: $npm$chalk$Chain; + declare var inverse: $npm$chalk$Chain; + declare var strikethrough: $npm$chalk$Chain; + + // Text colors + declare var black: $npm$chalk$Chain; + declare var red: $npm$chalk$Chain; + declare var green: $npm$chalk$Chain; + declare var yellow: $npm$chalk$Chain; + declare var blue: $npm$chalk$Chain; + declare var magenta: $npm$chalk$Chain; + declare var cyan: $npm$chalk$Chain; + declare var white: $npm$chalk$Chain; + declare var gray: $npm$chalk$Chain; + declare var grey: $npm$chalk$Chain; + + // Background colors + declare var bgBlack: $npm$chalk$Chain; + declare var bgRed: $npm$chalk$Chain; + declare var bgGreen: $npm$chalk$Chain; + declare var bgYellow: $npm$chalk$Chain; + declare var bgBlue: $npm$chalk$Chain; + declare var bgMagenta: $npm$chalk$Chain; + declare var bgCyan: $npm$chalk$Chain; + declare var bgWhite: $npm$chalk$Chain; +} diff --git a/flow-typed/npm/codemirror_vx.x.x.js b/flow-typed/npm/codemirror_vx.x.x.js new file mode 100644 index 000000000..3898aefd4 --- /dev/null +++ b/flow-typed/npm/codemirror_vx.x.x.js @@ -0,0 +1,1761 @@ +// flow-typed signature: 56334b937a6991fded5d2f2312821ccd +// flow-typed version: <>/codemirror_v^5.25.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'codemirror' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'codemirror' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'codemirror/addon/comment/comment' { + declare module.exports: any; +} + +declare module 'codemirror/addon/comment/continuecomment' { + declare module.exports: any; +} + +declare module 'codemirror/addon/dialog/dialog' { + declare module.exports: any; +} + +declare module 'codemirror/addon/display/autorefresh' { + declare module.exports: any; +} + +declare module 'codemirror/addon/display/fullscreen' { + declare module.exports: any; +} + +declare module 'codemirror/addon/display/panel' { + declare module.exports: any; +} + +declare module 'codemirror/addon/display/placeholder' { + declare module.exports: any; +} + +declare module 'codemirror/addon/display/rulers' { + declare module.exports: any; +} + +declare module 'codemirror/addon/edit/closebrackets' { + declare module.exports: any; +} + +declare module 'codemirror/addon/edit/closetag' { + declare module.exports: any; +} + +declare module 'codemirror/addon/edit/continuelist' { + declare module.exports: any; +} + +declare module 'codemirror/addon/edit/matchbrackets' { + declare module.exports: any; +} + +declare module 'codemirror/addon/edit/matchtags' { + declare module.exports: any; +} + +declare module 'codemirror/addon/edit/trailingspace' { + declare module.exports: any; +} + +declare module 'codemirror/addon/fold/brace-fold' { + declare module.exports: any; +} + +declare module 'codemirror/addon/fold/comment-fold' { + declare module.exports: any; +} + +declare module 'codemirror/addon/fold/foldcode' { + declare module.exports: any; +} + +declare module 'codemirror/addon/fold/foldgutter' { + declare module.exports: any; +} + +declare module 'codemirror/addon/fold/indent-fold' { + declare module.exports: any; +} + +declare module 'codemirror/addon/fold/markdown-fold' { + declare module.exports: any; +} + +declare module 'codemirror/addon/fold/xml-fold' { + declare module.exports: any; +} + +declare module 'codemirror/addon/hint/anyword-hint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/hint/css-hint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/hint/html-hint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/hint/javascript-hint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/hint/show-hint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/hint/sql-hint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/hint/xml-hint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/lint/coffeescript-lint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/lint/css-lint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/lint/html-lint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/lint/javascript-lint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/lint/json-lint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/lint/lint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/lint/yaml-lint' { + declare module.exports: any; +} + +declare module 'codemirror/addon/merge/merge' { + declare module.exports: any; +} + +declare module 'codemirror/addon/mode/loadmode' { + declare module.exports: any; +} + +declare module 'codemirror/addon/mode/multiplex_test' { + declare module.exports: any; +} + +declare module 'codemirror/addon/mode/multiplex' { + declare module.exports: any; +} + +declare module 'codemirror/addon/mode/overlay' { + declare module.exports: any; +} + +declare module 'codemirror/addon/mode/simple' { + declare module.exports: any; +} + +declare module 'codemirror/addon/runmode/colorize' { + declare module.exports: any; +} + +declare module 'codemirror/addon/runmode/runmode-standalone' { + declare module.exports: any; +} + +declare module 'codemirror/addon/runmode/runmode' { + declare module.exports: any; +} + +declare module 'codemirror/addon/runmode/runmode.node' { + declare module.exports: any; +} + +declare module 'codemirror/addon/scroll/annotatescrollbar' { + declare module.exports: any; +} + +declare module 'codemirror/addon/scroll/scrollpastend' { + declare module.exports: any; +} + +declare module 'codemirror/addon/scroll/simplescrollbars' { + declare module.exports: any; +} + +declare module 'codemirror/addon/search/jump-to-line' { + declare module.exports: any; +} + +declare module 'codemirror/addon/search/match-highlighter' { + declare module.exports: any; +} + +declare module 'codemirror/addon/search/matchesonscrollbar' { + declare module.exports: any; +} + +declare module 'codemirror/addon/search/search' { + declare module.exports: any; +} + +declare module 'codemirror/addon/search/searchcursor' { + declare module.exports: any; +} + +declare module 'codemirror/addon/selection/active-line' { + declare module.exports: any; +} + +declare module 'codemirror/addon/selection/mark-selection' { + declare module.exports: any; +} + +declare module 'codemirror/addon/selection/selection-pointer' { + declare module.exports: any; +} + +declare module 'codemirror/addon/tern/tern' { + declare module.exports: any; +} + +declare module 'codemirror/addon/tern/worker' { + declare module.exports: any; +} + +declare module 'codemirror/addon/wrap/hardwrap' { + declare module.exports: any; +} + +declare module 'codemirror/keymap/emacs' { + declare module.exports: any; +} + +declare module 'codemirror/keymap/sublime' { + declare module.exports: any; +} + +declare module 'codemirror/keymap/vim' { + declare module.exports: any; +} + +declare module 'codemirror/lib/codemirror' { + declare module.exports: any; +} + +declare module 'codemirror/mode/apl/apl' { + declare module.exports: any; +} + +declare module 'codemirror/mode/asciiarmor/asciiarmor' { + declare module.exports: any; +} + +declare module 'codemirror/mode/asn.1/asn.1' { + declare module.exports: any; +} + +declare module 'codemirror/mode/asterisk/asterisk' { + declare module.exports: any; +} + +declare module 'codemirror/mode/brainfuck/brainfuck' { + declare module.exports: any; +} + +declare module 'codemirror/mode/clike/clike' { + declare module.exports: any; +} + +declare module 'codemirror/mode/clojure/clojure' { + declare module.exports: any; +} + +declare module 'codemirror/mode/cmake/cmake' { + declare module.exports: any; +} + +declare module 'codemirror/mode/cobol/cobol' { + declare module.exports: any; +} + +declare module 'codemirror/mode/coffeescript/coffeescript' { + declare module.exports: any; +} + +declare module 'codemirror/mode/commonlisp/commonlisp' { + declare module.exports: any; +} + +declare module 'codemirror/mode/crystal/crystal' { + declare module.exports: any; +} + +declare module 'codemirror/mode/css/css' { + declare module.exports: any; +} + +declare module 'codemirror/mode/cypher/cypher' { + declare module.exports: any; +} + +declare module 'codemirror/mode/d/d' { + declare module.exports: any; +} + +declare module 'codemirror/mode/dart/dart' { + declare module.exports: any; +} + +declare module 'codemirror/mode/diff/diff' { + declare module.exports: any; +} + +declare module 'codemirror/mode/django/django' { + declare module.exports: any; +} + +declare module 'codemirror/mode/dockerfile/dockerfile' { + declare module.exports: any; +} + +declare module 'codemirror/mode/dtd/dtd' { + declare module.exports: any; +} + +declare module 'codemirror/mode/dylan/dylan' { + declare module.exports: any; +} + +declare module 'codemirror/mode/ebnf/ebnf' { + declare module.exports: any; +} + +declare module 'codemirror/mode/ecl/ecl' { + declare module.exports: any; +} + +declare module 'codemirror/mode/eiffel/eiffel' { + declare module.exports: any; +} + +declare module 'codemirror/mode/elm/elm' { + declare module.exports: any; +} + +declare module 'codemirror/mode/erlang/erlang' { + declare module.exports: any; +} + +declare module 'codemirror/mode/factor/factor' { + declare module.exports: any; +} + +declare module 'codemirror/mode/fcl/fcl' { + declare module.exports: any; +} + +declare module 'codemirror/mode/forth/forth' { + declare module.exports: any; +} + +declare module 'codemirror/mode/fortran/fortran' { + declare module.exports: any; +} + +declare module 'codemirror/mode/gas/gas' { + declare module.exports: any; +} + +declare module 'codemirror/mode/gfm/gfm' { + declare module.exports: any; +} + +declare module 'codemirror/mode/gherkin/gherkin' { + declare module.exports: any; +} + +declare module 'codemirror/mode/go/go' { + declare module.exports: any; +} + +declare module 'codemirror/mode/groovy/groovy' { + declare module.exports: any; +} + +declare module 'codemirror/mode/haml/haml' { + declare module.exports: any; +} + +declare module 'codemirror/mode/handlebars/handlebars' { + declare module.exports: any; +} + +declare module 'codemirror/mode/haskell-literate/haskell-literate' { + declare module.exports: any; +} + +declare module 'codemirror/mode/haskell/haskell' { + declare module.exports: any; +} + +declare module 'codemirror/mode/haxe/haxe' { + declare module.exports: any; +} + +declare module 'codemirror/mode/htmlembedded/htmlembedded' { + declare module.exports: any; +} + +declare module 'codemirror/mode/htmlmixed/htmlmixed' { + declare module.exports: any; +} + +declare module 'codemirror/mode/http/http' { + declare module.exports: any; +} + +declare module 'codemirror/mode/idl/idl' { + declare module.exports: any; +} + +declare module 'codemirror/mode/javascript/javascript' { + declare module.exports: any; +} + +declare module 'codemirror/mode/jinja2/jinja2' { + declare module.exports: any; +} + +declare module 'codemirror/mode/jsx/jsx' { + declare module.exports: any; +} + +declare module 'codemirror/mode/julia/julia' { + declare module.exports: any; +} + +declare module 'codemirror/mode/livescript/livescript' { + declare module.exports: any; +} + +declare module 'codemirror/mode/lua/lua' { + declare module.exports: any; +} + +declare module 'codemirror/mode/markdown/markdown' { + declare module.exports: any; +} + +declare module 'codemirror/mode/mathematica/mathematica' { + declare module.exports: any; +} + +declare module 'codemirror/mode/mbox/mbox' { + declare module.exports: any; +} + +declare module 'codemirror/mode/meta' { + declare module.exports: any; +} + +declare module 'codemirror/mode/mirc/mirc' { + declare module.exports: any; +} + +declare module 'codemirror/mode/mllike/mllike' { + declare module.exports: any; +} + +declare module 'codemirror/mode/modelica/modelica' { + declare module.exports: any; +} + +declare module 'codemirror/mode/mscgen/mscgen' { + declare module.exports: any; +} + +declare module 'codemirror/mode/mumps/mumps' { + declare module.exports: any; +} + +declare module 'codemirror/mode/nginx/nginx' { + declare module.exports: any; +} + +declare module 'codemirror/mode/nsis/nsis' { + declare module.exports: any; +} + +declare module 'codemirror/mode/ntriples/ntriples' { + declare module.exports: any; +} + +declare module 'codemirror/mode/octave/octave' { + declare module.exports: any; +} + +declare module 'codemirror/mode/oz/oz' { + declare module.exports: any; +} + +declare module 'codemirror/mode/pascal/pascal' { + declare module.exports: any; +} + +declare module 'codemirror/mode/pegjs/pegjs' { + declare module.exports: any; +} + +declare module 'codemirror/mode/perl/perl' { + declare module.exports: any; +} + +declare module 'codemirror/mode/php/php' { + declare module.exports: any; +} + +declare module 'codemirror/mode/pig/pig' { + declare module.exports: any; +} + +declare module 'codemirror/mode/powershell/powershell' { + declare module.exports: any; +} + +declare module 'codemirror/mode/properties/properties' { + declare module.exports: any; +} + +declare module 'codemirror/mode/protobuf/protobuf' { + declare module.exports: any; +} + +declare module 'codemirror/mode/pug/pug' { + declare module.exports: any; +} + +declare module 'codemirror/mode/puppet/puppet' { + declare module.exports: any; +} + +declare module 'codemirror/mode/python/python' { + declare module.exports: any; +} + +declare module 'codemirror/mode/q/q' { + declare module.exports: any; +} + +declare module 'codemirror/mode/r/r' { + declare module.exports: any; +} + +declare module 'codemirror/mode/rpm/rpm' { + declare module.exports: any; +} + +declare module 'codemirror/mode/rst/rst' { + declare module.exports: any; +} + +declare module 'codemirror/mode/ruby/ruby' { + declare module.exports: any; +} + +declare module 'codemirror/mode/rust/rust' { + declare module.exports: any; +} + +declare module 'codemirror/mode/sas/sas' { + declare module.exports: any; +} + +declare module 'codemirror/mode/sass/sass' { + declare module.exports: any; +} + +declare module 'codemirror/mode/scheme/scheme' { + declare module.exports: any; +} + +declare module 'codemirror/mode/shell/shell' { + declare module.exports: any; +} + +declare module 'codemirror/mode/sieve/sieve' { + declare module.exports: any; +} + +declare module 'codemirror/mode/slim/slim' { + declare module.exports: any; +} + +declare module 'codemirror/mode/smalltalk/smalltalk' { + declare module.exports: any; +} + +declare module 'codemirror/mode/smarty/smarty' { + declare module.exports: any; +} + +declare module 'codemirror/mode/solr/solr' { + declare module.exports: any; +} + +declare module 'codemirror/mode/soy/soy' { + declare module.exports: any; +} + +declare module 'codemirror/mode/sparql/sparql' { + declare module.exports: any; +} + +declare module 'codemirror/mode/spreadsheet/spreadsheet' { + declare module.exports: any; +} + +declare module 'codemirror/mode/sql/sql' { + declare module.exports: any; +} + +declare module 'codemirror/mode/stex/stex' { + declare module.exports: any; +} + +declare module 'codemirror/mode/stylus/stylus' { + declare module.exports: any; +} + +declare module 'codemirror/mode/swift/swift' { + declare module.exports: any; +} + +declare module 'codemirror/mode/tcl/tcl' { + declare module.exports: any; +} + +declare module 'codemirror/mode/textile/textile' { + declare module.exports: any; +} + +declare module 'codemirror/mode/tiddlywiki/tiddlywiki' { + declare module.exports: any; +} + +declare module 'codemirror/mode/tiki/tiki' { + declare module.exports: any; +} + +declare module 'codemirror/mode/toml/toml' { + declare module.exports: any; +} + +declare module 'codemirror/mode/tornado/tornado' { + declare module.exports: any; +} + +declare module 'codemirror/mode/troff/troff' { + declare module.exports: any; +} + +declare module 'codemirror/mode/ttcn-cfg/ttcn-cfg' { + declare module.exports: any; +} + +declare module 'codemirror/mode/ttcn/ttcn' { + declare module.exports: any; +} + +declare module 'codemirror/mode/turtle/turtle' { + declare module.exports: any; +} + +declare module 'codemirror/mode/twig/twig' { + declare module.exports: any; +} + +declare module 'codemirror/mode/vb/vb' { + declare module.exports: any; +} + +declare module 'codemirror/mode/vbscript/vbscript' { + declare module.exports: any; +} + +declare module 'codemirror/mode/velocity/velocity' { + declare module.exports: any; +} + +declare module 'codemirror/mode/verilog/verilog' { + declare module.exports: any; +} + +declare module 'codemirror/mode/vhdl/vhdl' { + declare module.exports: any; +} + +declare module 'codemirror/mode/vue/vue' { + declare module.exports: any; +} + +declare module 'codemirror/mode/webidl/webidl' { + declare module.exports: any; +} + +declare module 'codemirror/mode/xml/xml' { + declare module.exports: any; +} + +declare module 'codemirror/mode/xquery/xquery' { + declare module.exports: any; +} + +declare module 'codemirror/mode/yacas/yacas' { + declare module.exports: any; +} + +declare module 'codemirror/mode/yaml-frontmatter/yaml-frontmatter' { + declare module.exports: any; +} + +declare module 'codemirror/mode/yaml/yaml' { + declare module.exports: any; +} + +declare module 'codemirror/mode/z80/z80' { + declare module.exports: any; +} + +declare module 'codemirror/rollup.config' { + declare module.exports: any; +} + +declare module 'codemirror/src/codemirror' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/Display' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/focus' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/gutters' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/highlight_worker' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/line_numbers' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/mode_state' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/operations' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/scroll_events' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/scrollbars' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/scrolling' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/selection' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/update_display' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/update_line' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/update_lines' { + declare module.exports: any; +} + +declare module 'codemirror/src/display/view_tracking' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/CodeMirror' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/commands' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/deleteNearSelection' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/drop_events' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/fromTextArea' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/global_events' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/key_events' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/legacy' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/main' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/methods' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/mouse_events' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/options' { + declare module.exports: any; +} + +declare module 'codemirror/src/edit/utils' { + declare module.exports: any; +} + +declare module 'codemirror/src/input/ContentEditableInput' { + declare module.exports: any; +} + +declare module 'codemirror/src/input/indent' { + declare module.exports: any; +} + +declare module 'codemirror/src/input/input' { + declare module.exports: any; +} + +declare module 'codemirror/src/input/keymap' { + declare module.exports: any; +} + +declare module 'codemirror/src/input/keynames' { + declare module.exports: any; +} + +declare module 'codemirror/src/input/movement' { + declare module.exports: any; +} + +declare module 'codemirror/src/input/TextareaInput' { + declare module.exports: any; +} + +declare module 'codemirror/src/line/highlight' { + declare module.exports: any; +} + +declare module 'codemirror/src/line/line_data' { + declare module.exports: any; +} + +declare module 'codemirror/src/line/pos' { + declare module.exports: any; +} + +declare module 'codemirror/src/line/saw_special_spans' { + declare module.exports: any; +} + +declare module 'codemirror/src/line/spans' { + declare module.exports: any; +} + +declare module 'codemirror/src/line/utils_line' { + declare module.exports: any; +} + +declare module 'codemirror/src/measurement/position_measurement' { + declare module.exports: any; +} + +declare module 'codemirror/src/measurement/widgets' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/change_measurement' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/changes' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/chunk' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/Doc' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/document_data' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/history' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/line_widget' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/mark_text' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/selection_updates' { + declare module.exports: any; +} + +declare module 'codemirror/src/model/selection' { + declare module.exports: any; +} + +declare module 'codemirror/src/modes' { + declare module.exports: any; +} + +declare module 'codemirror/src/util/bidi' { + declare module.exports: any; +} + +declare module 'codemirror/src/util/browser' { + declare module.exports: any; +} + +declare module 'codemirror/src/util/dom' { + declare module.exports: any; +} + +declare module 'codemirror/src/util/event' { + declare module.exports: any; +} + +declare module 'codemirror/src/util/feature_detection' { + declare module.exports: any; +} + +declare module 'codemirror/src/util/misc' { + declare module.exports: any; +} + +declare module 'codemirror/src/util/operation_group' { + declare module.exports: any; +} + +declare module 'codemirror/src/util/StringStream' { + declare module.exports: any; +} + +// Filename aliases +declare module 'codemirror/addon/comment/comment.js' { + declare module.exports: $Exports<'codemirror/addon/comment/comment'>; +} +declare module 'codemirror/addon/comment/continuecomment.js' { + declare module.exports: $Exports<'codemirror/addon/comment/continuecomment'>; +} +declare module 'codemirror/addon/dialog/dialog.js' { + declare module.exports: $Exports<'codemirror/addon/dialog/dialog'>; +} +declare module 'codemirror/addon/display/autorefresh.js' { + declare module.exports: $Exports<'codemirror/addon/display/autorefresh'>; +} +declare module 'codemirror/addon/display/fullscreen.js' { + declare module.exports: $Exports<'codemirror/addon/display/fullscreen'>; +} +declare module 'codemirror/addon/display/panel.js' { + declare module.exports: $Exports<'codemirror/addon/display/panel'>; +} +declare module 'codemirror/addon/display/placeholder.js' { + declare module.exports: $Exports<'codemirror/addon/display/placeholder'>; +} +declare module 'codemirror/addon/display/rulers.js' { + declare module.exports: $Exports<'codemirror/addon/display/rulers'>; +} +declare module 'codemirror/addon/edit/closebrackets.js' { + declare module.exports: $Exports<'codemirror/addon/edit/closebrackets'>; +} +declare module 'codemirror/addon/edit/closetag.js' { + declare module.exports: $Exports<'codemirror/addon/edit/closetag'>; +} +declare module 'codemirror/addon/edit/continuelist.js' { + declare module.exports: $Exports<'codemirror/addon/edit/continuelist'>; +} +declare module 'codemirror/addon/edit/matchbrackets.js' { + declare module.exports: $Exports<'codemirror/addon/edit/matchbrackets'>; +} +declare module 'codemirror/addon/edit/matchtags.js' { + declare module.exports: $Exports<'codemirror/addon/edit/matchtags'>; +} +declare module 'codemirror/addon/edit/trailingspace.js' { + declare module.exports: $Exports<'codemirror/addon/edit/trailingspace'>; +} +declare module 'codemirror/addon/fold/brace-fold.js' { + declare module.exports: $Exports<'codemirror/addon/fold/brace-fold'>; +} +declare module 'codemirror/addon/fold/comment-fold.js' { + declare module.exports: $Exports<'codemirror/addon/fold/comment-fold'>; +} +declare module 'codemirror/addon/fold/foldcode.js' { + declare module.exports: $Exports<'codemirror/addon/fold/foldcode'>; +} +declare module 'codemirror/addon/fold/foldgutter.js' { + declare module.exports: $Exports<'codemirror/addon/fold/foldgutter'>; +} +declare module 'codemirror/addon/fold/indent-fold.js' { + declare module.exports: $Exports<'codemirror/addon/fold/indent-fold'>; +} +declare module 'codemirror/addon/fold/markdown-fold.js' { + declare module.exports: $Exports<'codemirror/addon/fold/markdown-fold'>; +} +declare module 'codemirror/addon/fold/xml-fold.js' { + declare module.exports: $Exports<'codemirror/addon/fold/xml-fold'>; +} +declare module 'codemirror/addon/hint/anyword-hint.js' { + declare module.exports: $Exports<'codemirror/addon/hint/anyword-hint'>; +} +declare module 'codemirror/addon/hint/css-hint.js' { + declare module.exports: $Exports<'codemirror/addon/hint/css-hint'>; +} +declare module 'codemirror/addon/hint/html-hint.js' { + declare module.exports: $Exports<'codemirror/addon/hint/html-hint'>; +} +declare module 'codemirror/addon/hint/javascript-hint.js' { + declare module.exports: $Exports<'codemirror/addon/hint/javascript-hint'>; +} +declare module 'codemirror/addon/hint/show-hint.js' { + declare module.exports: $Exports<'codemirror/addon/hint/show-hint'>; +} +declare module 'codemirror/addon/hint/sql-hint.js' { + declare module.exports: $Exports<'codemirror/addon/hint/sql-hint'>; +} +declare module 'codemirror/addon/hint/xml-hint.js' { + declare module.exports: $Exports<'codemirror/addon/hint/xml-hint'>; +} +declare module 'codemirror/addon/lint/coffeescript-lint.js' { + declare module.exports: $Exports<'codemirror/addon/lint/coffeescript-lint'>; +} +declare module 'codemirror/addon/lint/css-lint.js' { + declare module.exports: $Exports<'codemirror/addon/lint/css-lint'>; +} +declare module 'codemirror/addon/lint/html-lint.js' { + declare module.exports: $Exports<'codemirror/addon/lint/html-lint'>; +} +declare module 'codemirror/addon/lint/javascript-lint.js' { + declare module.exports: $Exports<'codemirror/addon/lint/javascript-lint'>; +} +declare module 'codemirror/addon/lint/json-lint.js' { + declare module.exports: $Exports<'codemirror/addon/lint/json-lint'>; +} +declare module 'codemirror/addon/lint/lint.js' { + declare module.exports: $Exports<'codemirror/addon/lint/lint'>; +} +declare module 'codemirror/addon/lint/yaml-lint.js' { + declare module.exports: $Exports<'codemirror/addon/lint/yaml-lint'>; +} +declare module 'codemirror/addon/merge/merge.js' { + declare module.exports: $Exports<'codemirror/addon/merge/merge'>; +} +declare module 'codemirror/addon/mode/loadmode.js' { + declare module.exports: $Exports<'codemirror/addon/mode/loadmode'>; +} +declare module 'codemirror/addon/mode/multiplex_test.js' { + declare module.exports: $Exports<'codemirror/addon/mode/multiplex_test'>; +} +declare module 'codemirror/addon/mode/multiplex.js' { + declare module.exports: $Exports<'codemirror/addon/mode/multiplex'>; +} +declare module 'codemirror/addon/mode/overlay.js' { + declare module.exports: $Exports<'codemirror/addon/mode/overlay'>; +} +declare module 'codemirror/addon/mode/simple.js' { + declare module.exports: $Exports<'codemirror/addon/mode/simple'>; +} +declare module 'codemirror/addon/runmode/colorize.js' { + declare module.exports: $Exports<'codemirror/addon/runmode/colorize'>; +} +declare module 'codemirror/addon/runmode/runmode-standalone.js' { + declare module.exports: $Exports<'codemirror/addon/runmode/runmode-standalone'>; +} +declare module 'codemirror/addon/runmode/runmode.js' { + declare module.exports: $Exports<'codemirror/addon/runmode/runmode'>; +} +declare module 'codemirror/addon/runmode/runmode.node.js' { + declare module.exports: $Exports<'codemirror/addon/runmode/runmode.node'>; +} +declare module 'codemirror/addon/scroll/annotatescrollbar.js' { + declare module.exports: $Exports<'codemirror/addon/scroll/annotatescrollbar'>; +} +declare module 'codemirror/addon/scroll/scrollpastend.js' { + declare module.exports: $Exports<'codemirror/addon/scroll/scrollpastend'>; +} +declare module 'codemirror/addon/scroll/simplescrollbars.js' { + declare module.exports: $Exports<'codemirror/addon/scroll/simplescrollbars'>; +} +declare module 'codemirror/addon/search/jump-to-line.js' { + declare module.exports: $Exports<'codemirror/addon/search/jump-to-line'>; +} +declare module 'codemirror/addon/search/match-highlighter.js' { + declare module.exports: $Exports<'codemirror/addon/search/match-highlighter'>; +} +declare module 'codemirror/addon/search/matchesonscrollbar.js' { + declare module.exports: $Exports<'codemirror/addon/search/matchesonscrollbar'>; +} +declare module 'codemirror/addon/search/search.js' { + declare module.exports: $Exports<'codemirror/addon/search/search'>; +} +declare module 'codemirror/addon/search/searchcursor.js' { + declare module.exports: $Exports<'codemirror/addon/search/searchcursor'>; +} +declare module 'codemirror/addon/selection/active-line.js' { + declare module.exports: $Exports<'codemirror/addon/selection/active-line'>; +} +declare module 'codemirror/addon/selection/mark-selection.js' { + declare module.exports: $Exports<'codemirror/addon/selection/mark-selection'>; +} +declare module 'codemirror/addon/selection/selection-pointer.js' { + declare module.exports: $Exports<'codemirror/addon/selection/selection-pointer'>; +} +declare module 'codemirror/addon/tern/tern.js' { + declare module.exports: $Exports<'codemirror/addon/tern/tern'>; +} +declare module 'codemirror/addon/tern/worker.js' { + declare module.exports: $Exports<'codemirror/addon/tern/worker'>; +} +declare module 'codemirror/addon/wrap/hardwrap.js' { + declare module.exports: $Exports<'codemirror/addon/wrap/hardwrap'>; +} +declare module 'codemirror/keymap/emacs.js' { + declare module.exports: $Exports<'codemirror/keymap/emacs'>; +} +declare module 'codemirror/keymap/sublime.js' { + declare module.exports: $Exports<'codemirror/keymap/sublime'>; +} +declare module 'codemirror/keymap/vim.js' { + declare module.exports: $Exports<'codemirror/keymap/vim'>; +} +declare module 'codemirror/lib/codemirror.js' { + declare module.exports: $Exports<'codemirror/lib/codemirror'>; +} +declare module 'codemirror/mode/apl/apl.js' { + declare module.exports: $Exports<'codemirror/mode/apl/apl'>; +} +declare module 'codemirror/mode/asciiarmor/asciiarmor.js' { + declare module.exports: $Exports<'codemirror/mode/asciiarmor/asciiarmor'>; +} +declare module 'codemirror/mode/asn.1/asn.1.js' { + declare module.exports: $Exports<'codemirror/mode/asn.1/asn.1'>; +} +declare module 'codemirror/mode/asterisk/asterisk.js' { + declare module.exports: $Exports<'codemirror/mode/asterisk/asterisk'>; +} +declare module 'codemirror/mode/brainfuck/brainfuck.js' { + declare module.exports: $Exports<'codemirror/mode/brainfuck/brainfuck'>; +} +declare module 'codemirror/mode/clike/clike.js' { + declare module.exports: $Exports<'codemirror/mode/clike/clike'>; +} +declare module 'codemirror/mode/clojure/clojure.js' { + declare module.exports: $Exports<'codemirror/mode/clojure/clojure'>; +} +declare module 'codemirror/mode/cmake/cmake.js' { + declare module.exports: $Exports<'codemirror/mode/cmake/cmake'>; +} +declare module 'codemirror/mode/cobol/cobol.js' { + declare module.exports: $Exports<'codemirror/mode/cobol/cobol'>; +} +declare module 'codemirror/mode/coffeescript/coffeescript.js' { + declare module.exports: $Exports<'codemirror/mode/coffeescript/coffeescript'>; +} +declare module 'codemirror/mode/commonlisp/commonlisp.js' { + declare module.exports: $Exports<'codemirror/mode/commonlisp/commonlisp'>; +} +declare module 'codemirror/mode/crystal/crystal.js' { + declare module.exports: $Exports<'codemirror/mode/crystal/crystal'>; +} +declare module 'codemirror/mode/css/css.js' { + declare module.exports: $Exports<'codemirror/mode/css/css'>; +} +declare module 'codemirror/mode/cypher/cypher.js' { + declare module.exports: $Exports<'codemirror/mode/cypher/cypher'>; +} +declare module 'codemirror/mode/d/d.js' { + declare module.exports: $Exports<'codemirror/mode/d/d'>; +} +declare module 'codemirror/mode/dart/dart.js' { + declare module.exports: $Exports<'codemirror/mode/dart/dart'>; +} +declare module 'codemirror/mode/diff/diff.js' { + declare module.exports: $Exports<'codemirror/mode/diff/diff'>; +} +declare module 'codemirror/mode/django/django.js' { + declare module.exports: $Exports<'codemirror/mode/django/django'>; +} +declare module 'codemirror/mode/dockerfile/dockerfile.js' { + declare module.exports: $Exports<'codemirror/mode/dockerfile/dockerfile'>; +} +declare module 'codemirror/mode/dtd/dtd.js' { + declare module.exports: $Exports<'codemirror/mode/dtd/dtd'>; +} +declare module 'codemirror/mode/dylan/dylan.js' { + declare module.exports: $Exports<'codemirror/mode/dylan/dylan'>; +} +declare module 'codemirror/mode/ebnf/ebnf.js' { + declare module.exports: $Exports<'codemirror/mode/ebnf/ebnf'>; +} +declare module 'codemirror/mode/ecl/ecl.js' { + declare module.exports: $Exports<'codemirror/mode/ecl/ecl'>; +} +declare module 'codemirror/mode/eiffel/eiffel.js' { + declare module.exports: $Exports<'codemirror/mode/eiffel/eiffel'>; +} +declare module 'codemirror/mode/elm/elm.js' { + declare module.exports: $Exports<'codemirror/mode/elm/elm'>; +} +declare module 'codemirror/mode/erlang/erlang.js' { + declare module.exports: $Exports<'codemirror/mode/erlang/erlang'>; +} +declare module 'codemirror/mode/factor/factor.js' { + declare module.exports: $Exports<'codemirror/mode/factor/factor'>; +} +declare module 'codemirror/mode/fcl/fcl.js' { + declare module.exports: $Exports<'codemirror/mode/fcl/fcl'>; +} +declare module 'codemirror/mode/forth/forth.js' { + declare module.exports: $Exports<'codemirror/mode/forth/forth'>; +} +declare module 'codemirror/mode/fortran/fortran.js' { + declare module.exports: $Exports<'codemirror/mode/fortran/fortran'>; +} +declare module 'codemirror/mode/gas/gas.js' { + declare module.exports: $Exports<'codemirror/mode/gas/gas'>; +} +declare module 'codemirror/mode/gfm/gfm.js' { + declare module.exports: $Exports<'codemirror/mode/gfm/gfm'>; +} +declare module 'codemirror/mode/gherkin/gherkin.js' { + declare module.exports: $Exports<'codemirror/mode/gherkin/gherkin'>; +} +declare module 'codemirror/mode/go/go.js' { + declare module.exports: $Exports<'codemirror/mode/go/go'>; +} +declare module 'codemirror/mode/groovy/groovy.js' { + declare module.exports: $Exports<'codemirror/mode/groovy/groovy'>; +} +declare module 'codemirror/mode/haml/haml.js' { + declare module.exports: $Exports<'codemirror/mode/haml/haml'>; +} +declare module 'codemirror/mode/handlebars/handlebars.js' { + declare module.exports: $Exports<'codemirror/mode/handlebars/handlebars'>; +} +declare module 'codemirror/mode/haskell-literate/haskell-literate.js' { + declare module.exports: $Exports<'codemirror/mode/haskell-literate/haskell-literate'>; +} +declare module 'codemirror/mode/haskell/haskell.js' { + declare module.exports: $Exports<'codemirror/mode/haskell/haskell'>; +} +declare module 'codemirror/mode/haxe/haxe.js' { + declare module.exports: $Exports<'codemirror/mode/haxe/haxe'>; +} +declare module 'codemirror/mode/htmlembedded/htmlembedded.js' { + declare module.exports: $Exports<'codemirror/mode/htmlembedded/htmlembedded'>; +} +declare module 'codemirror/mode/htmlmixed/htmlmixed.js' { + declare module.exports: $Exports<'codemirror/mode/htmlmixed/htmlmixed'>; +} +declare module 'codemirror/mode/http/http.js' { + declare module.exports: $Exports<'codemirror/mode/http/http'>; +} +declare module 'codemirror/mode/idl/idl.js' { + declare module.exports: $Exports<'codemirror/mode/idl/idl'>; +} +declare module 'codemirror/mode/javascript/javascript.js' { + declare module.exports: $Exports<'codemirror/mode/javascript/javascript'>; +} +declare module 'codemirror/mode/jinja2/jinja2.js' { + declare module.exports: $Exports<'codemirror/mode/jinja2/jinja2'>; +} +declare module 'codemirror/mode/jsx/jsx.js' { + declare module.exports: $Exports<'codemirror/mode/jsx/jsx'>; +} +declare module 'codemirror/mode/julia/julia.js' { + declare module.exports: $Exports<'codemirror/mode/julia/julia'>; +} +declare module 'codemirror/mode/livescript/livescript.js' { + declare module.exports: $Exports<'codemirror/mode/livescript/livescript'>; +} +declare module 'codemirror/mode/lua/lua.js' { + declare module.exports: $Exports<'codemirror/mode/lua/lua'>; +} +declare module 'codemirror/mode/markdown/markdown.js' { + declare module.exports: $Exports<'codemirror/mode/markdown/markdown'>; +} +declare module 'codemirror/mode/mathematica/mathematica.js' { + declare module.exports: $Exports<'codemirror/mode/mathematica/mathematica'>; +} +declare module 'codemirror/mode/mbox/mbox.js' { + declare module.exports: $Exports<'codemirror/mode/mbox/mbox'>; +} +declare module 'codemirror/mode/meta.js' { + declare module.exports: $Exports<'codemirror/mode/meta'>; +} +declare module 'codemirror/mode/mirc/mirc.js' { + declare module.exports: $Exports<'codemirror/mode/mirc/mirc'>; +} +declare module 'codemirror/mode/mllike/mllike.js' { + declare module.exports: $Exports<'codemirror/mode/mllike/mllike'>; +} +declare module 'codemirror/mode/modelica/modelica.js' { + declare module.exports: $Exports<'codemirror/mode/modelica/modelica'>; +} +declare module 'codemirror/mode/mscgen/mscgen.js' { + declare module.exports: $Exports<'codemirror/mode/mscgen/mscgen'>; +} +declare module 'codemirror/mode/mumps/mumps.js' { + declare module.exports: $Exports<'codemirror/mode/mumps/mumps'>; +} +declare module 'codemirror/mode/nginx/nginx.js' { + declare module.exports: $Exports<'codemirror/mode/nginx/nginx'>; +} +declare module 'codemirror/mode/nsis/nsis.js' { + declare module.exports: $Exports<'codemirror/mode/nsis/nsis'>; +} +declare module 'codemirror/mode/ntriples/ntriples.js' { + declare module.exports: $Exports<'codemirror/mode/ntriples/ntriples'>; +} +declare module 'codemirror/mode/octave/octave.js' { + declare module.exports: $Exports<'codemirror/mode/octave/octave'>; +} +declare module 'codemirror/mode/oz/oz.js' { + declare module.exports: $Exports<'codemirror/mode/oz/oz'>; +} +declare module 'codemirror/mode/pascal/pascal.js' { + declare module.exports: $Exports<'codemirror/mode/pascal/pascal'>; +} +declare module 'codemirror/mode/pegjs/pegjs.js' { + declare module.exports: $Exports<'codemirror/mode/pegjs/pegjs'>; +} +declare module 'codemirror/mode/perl/perl.js' { + declare module.exports: $Exports<'codemirror/mode/perl/perl'>; +} +declare module 'codemirror/mode/php/php.js' { + declare module.exports: $Exports<'codemirror/mode/php/php'>; +} +declare module 'codemirror/mode/pig/pig.js' { + declare module.exports: $Exports<'codemirror/mode/pig/pig'>; +} +declare module 'codemirror/mode/powershell/powershell.js' { + declare module.exports: $Exports<'codemirror/mode/powershell/powershell'>; +} +declare module 'codemirror/mode/properties/properties.js' { + declare module.exports: $Exports<'codemirror/mode/properties/properties'>; +} +declare module 'codemirror/mode/protobuf/protobuf.js' { + declare module.exports: $Exports<'codemirror/mode/protobuf/protobuf'>; +} +declare module 'codemirror/mode/pug/pug.js' { + declare module.exports: $Exports<'codemirror/mode/pug/pug'>; +} +declare module 'codemirror/mode/puppet/puppet.js' { + declare module.exports: $Exports<'codemirror/mode/puppet/puppet'>; +} +declare module 'codemirror/mode/python/python.js' { + declare module.exports: $Exports<'codemirror/mode/python/python'>; +} +declare module 'codemirror/mode/q/q.js' { + declare module.exports: $Exports<'codemirror/mode/q/q'>; +} +declare module 'codemirror/mode/r/r.js' { + declare module.exports: $Exports<'codemirror/mode/r/r'>; +} +declare module 'codemirror/mode/rpm/rpm.js' { + declare module.exports: $Exports<'codemirror/mode/rpm/rpm'>; +} +declare module 'codemirror/mode/rst/rst.js' { + declare module.exports: $Exports<'codemirror/mode/rst/rst'>; +} +declare module 'codemirror/mode/ruby/ruby.js' { + declare module.exports: $Exports<'codemirror/mode/ruby/ruby'>; +} +declare module 'codemirror/mode/rust/rust.js' { + declare module.exports: $Exports<'codemirror/mode/rust/rust'>; +} +declare module 'codemirror/mode/sas/sas.js' { + declare module.exports: $Exports<'codemirror/mode/sas/sas'>; +} +declare module 'codemirror/mode/sass/sass.js' { + declare module.exports: $Exports<'codemirror/mode/sass/sass'>; +} +declare module 'codemirror/mode/scheme/scheme.js' { + declare module.exports: $Exports<'codemirror/mode/scheme/scheme'>; +} +declare module 'codemirror/mode/shell/shell.js' { + declare module.exports: $Exports<'codemirror/mode/shell/shell'>; +} +declare module 'codemirror/mode/sieve/sieve.js' { + declare module.exports: $Exports<'codemirror/mode/sieve/sieve'>; +} +declare module 'codemirror/mode/slim/slim.js' { + declare module.exports: $Exports<'codemirror/mode/slim/slim'>; +} +declare module 'codemirror/mode/smalltalk/smalltalk.js' { + declare module.exports: $Exports<'codemirror/mode/smalltalk/smalltalk'>; +} +declare module 'codemirror/mode/smarty/smarty.js' { + declare module.exports: $Exports<'codemirror/mode/smarty/smarty'>; +} +declare module 'codemirror/mode/solr/solr.js' { + declare module.exports: $Exports<'codemirror/mode/solr/solr'>; +} +declare module 'codemirror/mode/soy/soy.js' { + declare module.exports: $Exports<'codemirror/mode/soy/soy'>; +} +declare module 'codemirror/mode/sparql/sparql.js' { + declare module.exports: $Exports<'codemirror/mode/sparql/sparql'>; +} +declare module 'codemirror/mode/spreadsheet/spreadsheet.js' { + declare module.exports: $Exports<'codemirror/mode/spreadsheet/spreadsheet'>; +} +declare module 'codemirror/mode/sql/sql.js' { + declare module.exports: $Exports<'codemirror/mode/sql/sql'>; +} +declare module 'codemirror/mode/stex/stex.js' { + declare module.exports: $Exports<'codemirror/mode/stex/stex'>; +} +declare module 'codemirror/mode/stylus/stylus.js' { + declare module.exports: $Exports<'codemirror/mode/stylus/stylus'>; +} +declare module 'codemirror/mode/swift/swift.js' { + declare module.exports: $Exports<'codemirror/mode/swift/swift'>; +} +declare module 'codemirror/mode/tcl/tcl.js' { + declare module.exports: $Exports<'codemirror/mode/tcl/tcl'>; +} +declare module 'codemirror/mode/textile/textile.js' { + declare module.exports: $Exports<'codemirror/mode/textile/textile'>; +} +declare module 'codemirror/mode/tiddlywiki/tiddlywiki.js' { + declare module.exports: $Exports<'codemirror/mode/tiddlywiki/tiddlywiki'>; +} +declare module 'codemirror/mode/tiki/tiki.js' { + declare module.exports: $Exports<'codemirror/mode/tiki/tiki'>; +} +declare module 'codemirror/mode/toml/toml.js' { + declare module.exports: $Exports<'codemirror/mode/toml/toml'>; +} +declare module 'codemirror/mode/tornado/tornado.js' { + declare module.exports: $Exports<'codemirror/mode/tornado/tornado'>; +} +declare module 'codemirror/mode/troff/troff.js' { + declare module.exports: $Exports<'codemirror/mode/troff/troff'>; +} +declare module 'codemirror/mode/ttcn-cfg/ttcn-cfg.js' { + declare module.exports: $Exports<'codemirror/mode/ttcn-cfg/ttcn-cfg'>; +} +declare module 'codemirror/mode/ttcn/ttcn.js' { + declare module.exports: $Exports<'codemirror/mode/ttcn/ttcn'>; +} +declare module 'codemirror/mode/turtle/turtle.js' { + declare module.exports: $Exports<'codemirror/mode/turtle/turtle'>; +} +declare module 'codemirror/mode/twig/twig.js' { + declare module.exports: $Exports<'codemirror/mode/twig/twig'>; +} +declare module 'codemirror/mode/vb/vb.js' { + declare module.exports: $Exports<'codemirror/mode/vb/vb'>; +} +declare module 'codemirror/mode/vbscript/vbscript.js' { + declare module.exports: $Exports<'codemirror/mode/vbscript/vbscript'>; +} +declare module 'codemirror/mode/velocity/velocity.js' { + declare module.exports: $Exports<'codemirror/mode/velocity/velocity'>; +} +declare module 'codemirror/mode/verilog/verilog.js' { + declare module.exports: $Exports<'codemirror/mode/verilog/verilog'>; +} +declare module 'codemirror/mode/vhdl/vhdl.js' { + declare module.exports: $Exports<'codemirror/mode/vhdl/vhdl'>; +} +declare module 'codemirror/mode/vue/vue.js' { + declare module.exports: $Exports<'codemirror/mode/vue/vue'>; +} +declare module 'codemirror/mode/webidl/webidl.js' { + declare module.exports: $Exports<'codemirror/mode/webidl/webidl'>; +} +declare module 'codemirror/mode/xml/xml.js' { + declare module.exports: $Exports<'codemirror/mode/xml/xml'>; +} +declare module 'codemirror/mode/xquery/xquery.js' { + declare module.exports: $Exports<'codemirror/mode/xquery/xquery'>; +} +declare module 'codemirror/mode/yacas/yacas.js' { + declare module.exports: $Exports<'codemirror/mode/yacas/yacas'>; +} +declare module 'codemirror/mode/yaml-frontmatter/yaml-frontmatter.js' { + declare module.exports: $Exports<'codemirror/mode/yaml-frontmatter/yaml-frontmatter'>; +} +declare module 'codemirror/mode/yaml/yaml.js' { + declare module.exports: $Exports<'codemirror/mode/yaml/yaml'>; +} +declare module 'codemirror/mode/z80/z80.js' { + declare module.exports: $Exports<'codemirror/mode/z80/z80'>; +} +declare module 'codemirror/rollup.config.js' { + declare module.exports: $Exports<'codemirror/rollup.config'>; +} +declare module 'codemirror/src/codemirror.js' { + declare module.exports: $Exports<'codemirror/src/codemirror'>; +} +declare module 'codemirror/src/display/Display.js' { + declare module.exports: $Exports<'codemirror/src/display/Display'>; +} +declare module 'codemirror/src/display/focus.js' { + declare module.exports: $Exports<'codemirror/src/display/focus'>; +} +declare module 'codemirror/src/display/gutters.js' { + declare module.exports: $Exports<'codemirror/src/display/gutters'>; +} +declare module 'codemirror/src/display/highlight_worker.js' { + declare module.exports: $Exports<'codemirror/src/display/highlight_worker'>; +} +declare module 'codemirror/src/display/line_numbers.js' { + declare module.exports: $Exports<'codemirror/src/display/line_numbers'>; +} +declare module 'codemirror/src/display/mode_state.js' { + declare module.exports: $Exports<'codemirror/src/display/mode_state'>; +} +declare module 'codemirror/src/display/operations.js' { + declare module.exports: $Exports<'codemirror/src/display/operations'>; +} +declare module 'codemirror/src/display/scroll_events.js' { + declare module.exports: $Exports<'codemirror/src/display/scroll_events'>; +} +declare module 'codemirror/src/display/scrollbars.js' { + declare module.exports: $Exports<'codemirror/src/display/scrollbars'>; +} +declare module 'codemirror/src/display/scrolling.js' { + declare module.exports: $Exports<'codemirror/src/display/scrolling'>; +} +declare module 'codemirror/src/display/selection.js' { + declare module.exports: $Exports<'codemirror/src/display/selection'>; +} +declare module 'codemirror/src/display/update_display.js' { + declare module.exports: $Exports<'codemirror/src/display/update_display'>; +} +declare module 'codemirror/src/display/update_line.js' { + declare module.exports: $Exports<'codemirror/src/display/update_line'>; +} +declare module 'codemirror/src/display/update_lines.js' { + declare module.exports: $Exports<'codemirror/src/display/update_lines'>; +} +declare module 'codemirror/src/display/view_tracking.js' { + declare module.exports: $Exports<'codemirror/src/display/view_tracking'>; +} +declare module 'codemirror/src/edit/CodeMirror.js' { + declare module.exports: $Exports<'codemirror/src/edit/CodeMirror'>; +} +declare module 'codemirror/src/edit/commands.js' { + declare module.exports: $Exports<'codemirror/src/edit/commands'>; +} +declare module 'codemirror/src/edit/deleteNearSelection.js' { + declare module.exports: $Exports<'codemirror/src/edit/deleteNearSelection'>; +} +declare module 'codemirror/src/edit/drop_events.js' { + declare module.exports: $Exports<'codemirror/src/edit/drop_events'>; +} +declare module 'codemirror/src/edit/fromTextArea.js' { + declare module.exports: $Exports<'codemirror/src/edit/fromTextArea'>; +} +declare module 'codemirror/src/edit/global_events.js' { + declare module.exports: $Exports<'codemirror/src/edit/global_events'>; +} +declare module 'codemirror/src/edit/key_events.js' { + declare module.exports: $Exports<'codemirror/src/edit/key_events'>; +} +declare module 'codemirror/src/edit/legacy.js' { + declare module.exports: $Exports<'codemirror/src/edit/legacy'>; +} +declare module 'codemirror/src/edit/main.js' { + declare module.exports: $Exports<'codemirror/src/edit/main'>; +} +declare module 'codemirror/src/edit/methods.js' { + declare module.exports: $Exports<'codemirror/src/edit/methods'>; +} +declare module 'codemirror/src/edit/mouse_events.js' { + declare module.exports: $Exports<'codemirror/src/edit/mouse_events'>; +} +declare module 'codemirror/src/edit/options.js' { + declare module.exports: $Exports<'codemirror/src/edit/options'>; +} +declare module 'codemirror/src/edit/utils.js' { + declare module.exports: $Exports<'codemirror/src/edit/utils'>; +} +declare module 'codemirror/src/input/ContentEditableInput.js' { + declare module.exports: $Exports<'codemirror/src/input/ContentEditableInput'>; +} +declare module 'codemirror/src/input/indent.js' { + declare module.exports: $Exports<'codemirror/src/input/indent'>; +} +declare module 'codemirror/src/input/input.js' { + declare module.exports: $Exports<'codemirror/src/input/input'>; +} +declare module 'codemirror/src/input/keymap.js' { + declare module.exports: $Exports<'codemirror/src/input/keymap'>; +} +declare module 'codemirror/src/input/keynames.js' { + declare module.exports: $Exports<'codemirror/src/input/keynames'>; +} +declare module 'codemirror/src/input/movement.js' { + declare module.exports: $Exports<'codemirror/src/input/movement'>; +} +declare module 'codemirror/src/input/TextareaInput.js' { + declare module.exports: $Exports<'codemirror/src/input/TextareaInput'>; +} +declare module 'codemirror/src/line/highlight.js' { + declare module.exports: $Exports<'codemirror/src/line/highlight'>; +} +declare module 'codemirror/src/line/line_data.js' { + declare module.exports: $Exports<'codemirror/src/line/line_data'>; +} +declare module 'codemirror/src/line/pos.js' { + declare module.exports: $Exports<'codemirror/src/line/pos'>; +} +declare module 'codemirror/src/line/saw_special_spans.js' { + declare module.exports: $Exports<'codemirror/src/line/saw_special_spans'>; +} +declare module 'codemirror/src/line/spans.js' { + declare module.exports: $Exports<'codemirror/src/line/spans'>; +} +declare module 'codemirror/src/line/utils_line.js' { + declare module.exports: $Exports<'codemirror/src/line/utils_line'>; +} +declare module 'codemirror/src/measurement/position_measurement.js' { + declare module.exports: $Exports<'codemirror/src/measurement/position_measurement'>; +} +declare module 'codemirror/src/measurement/widgets.js' { + declare module.exports: $Exports<'codemirror/src/measurement/widgets'>; +} +declare module 'codemirror/src/model/change_measurement.js' { + declare module.exports: $Exports<'codemirror/src/model/change_measurement'>; +} +declare module 'codemirror/src/model/changes.js' { + declare module.exports: $Exports<'codemirror/src/model/changes'>; +} +declare module 'codemirror/src/model/chunk.js' { + declare module.exports: $Exports<'codemirror/src/model/chunk'>; +} +declare module 'codemirror/src/model/Doc.js' { + declare module.exports: $Exports<'codemirror/src/model/Doc'>; +} +declare module 'codemirror/src/model/document_data.js' { + declare module.exports: $Exports<'codemirror/src/model/document_data'>; +} +declare module 'codemirror/src/model/history.js' { + declare module.exports: $Exports<'codemirror/src/model/history'>; +} +declare module 'codemirror/src/model/line_widget.js' { + declare module.exports: $Exports<'codemirror/src/model/line_widget'>; +} +declare module 'codemirror/src/model/mark_text.js' { + declare module.exports: $Exports<'codemirror/src/model/mark_text'>; +} +declare module 'codemirror/src/model/selection_updates.js' { + declare module.exports: $Exports<'codemirror/src/model/selection_updates'>; +} +declare module 'codemirror/src/model/selection.js' { + declare module.exports: $Exports<'codemirror/src/model/selection'>; +} +declare module 'codemirror/src/modes.js' { + declare module.exports: $Exports<'codemirror/src/modes'>; +} +declare module 'codemirror/src/util/bidi.js' { + declare module.exports: $Exports<'codemirror/src/util/bidi'>; +} +declare module 'codemirror/src/util/browser.js' { + declare module.exports: $Exports<'codemirror/src/util/browser'>; +} +declare module 'codemirror/src/util/dom.js' { + declare module.exports: $Exports<'codemirror/src/util/dom'>; +} +declare module 'codemirror/src/util/event.js' { + declare module.exports: $Exports<'codemirror/src/util/event'>; +} +declare module 'codemirror/src/util/feature_detection.js' { + declare module.exports: $Exports<'codemirror/src/util/feature_detection'>; +} +declare module 'codemirror/src/util/misc.js' { + declare module.exports: $Exports<'codemirror/src/util/misc'>; +} +declare module 'codemirror/src/util/operation_group.js' { + declare module.exports: $Exports<'codemirror/src/util/operation_group'>; +} +declare module 'codemirror/src/util/StringStream.js' { + declare module.exports: $Exports<'codemirror/src/util/StringStream'>; +} diff --git a/flow-typed/npm/dashify_vx.x.x.js b/flow-typed/npm/dashify_vx.x.x.js new file mode 100644 index 000000000..4222c2939 --- /dev/null +++ b/flow-typed/npm/dashify_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: b26465a763805506e2f1120d04ab58eb +// flow-typed version: <>/dashify_v^0.2.2/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'dashify' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'dashify' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'dashify/index' { + declare module.exports: $Exports<'dashify'>; +} +declare module 'dashify/index.js' { + declare module.exports: $Exports<'dashify'>; +} diff --git a/flow-typed/npm/deep-equal_vx.x.x.js b/flow-typed/npm/deep-equal_vx.x.x.js new file mode 100644 index 000000000..970019151 --- /dev/null +++ b/flow-typed/npm/deep-equal_vx.x.x.js @@ -0,0 +1,59 @@ +// flow-typed signature: ac858859a846498c06179ada0c8b78a1 +// flow-typed version: <>/deep-equal_v^1.0.1/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'deep-equal' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'deep-equal' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'deep-equal/example/cmp' { + declare module.exports: any; +} + +declare module 'deep-equal/lib/is_arguments' { + declare module.exports: any; +} + +declare module 'deep-equal/lib/keys' { + declare module.exports: any; +} + +declare module 'deep-equal/test/cmp' { + declare module.exports: any; +} + +// Filename aliases +declare module 'deep-equal/example/cmp.js' { + declare module.exports: $Exports<'deep-equal/example/cmp'>; +} +declare module 'deep-equal/index' { + declare module.exports: $Exports<'deep-equal'>; +} +declare module 'deep-equal/index.js' { + declare module.exports: $Exports<'deep-equal'>; +} +declare module 'deep-equal/lib/is_arguments.js' { + declare module.exports: $Exports<'deep-equal/lib/is_arguments'>; +} +declare module 'deep-equal/lib/keys.js' { + declare module.exports: $Exports<'deep-equal/lib/keys'>; +} +declare module 'deep-equal/test/cmp.js' { + declare module.exports: $Exports<'deep-equal/test/cmp'>; +} diff --git a/flow-typed/npm/detect-port_vx.x.x.js b/flow-typed/npm/detect-port_vx.x.x.js new file mode 100644 index 000000000..aaea225a1 --- /dev/null +++ b/flow-typed/npm/detect-port_vx.x.x.js @@ -0,0 +1,38 @@ +// flow-typed signature: ca3761607eebb8eefa0b0b2cd828537f +// flow-typed version: <>/detect-port_v^1.1.1/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'detect-port' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'detect-port' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'detect-port/lib/detect-port' { + declare module.exports: any; +} + +// Filename aliases +declare module 'detect-port/index' { + declare module.exports: $Exports<'detect-port'>; +} +declare module 'detect-port/index.js' { + declare module.exports: $Exports<'detect-port'>; +} +declare module 'detect-port/lib/detect-port.js' { + declare module.exports: $Exports<'detect-port/lib/detect-port'>; +} diff --git a/flow-typed/npm/electron-builder_vx.x.x.js b/flow-typed/npm/electron-builder_vx.x.x.js new file mode 100644 index 000000000..39edd17fd --- /dev/null +++ b/flow-typed/npm/electron-builder_vx.x.x.js @@ -0,0 +1,347 @@ +// flow-typed signature: 0782c9570706be2268d543150a4f38dc +// flow-typed version: <>/electron-builder_v^16.8.3/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'electron-builder' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'electron-builder' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'electron-builder/out/appInfo' { + declare module.exports: any; +} + +declare module 'electron-builder/out/asar' { + declare module.exports: any; +} + +declare module 'electron-builder/out/asarUtil' { + declare module.exports: any; +} + +declare module 'electron-builder/out/builder' { + declare module.exports: any; +} + +declare module 'electron-builder/out/cli/build-cli' { + declare module.exports: any; +} + +declare module 'electron-builder/out/cli/cliOptions' { + declare module.exports: any; +} + +declare module 'electron-builder/out/cli/create-self-signed-cert' { + declare module.exports: any; +} + +declare module 'electron-builder/out/cli/install-app-deps' { + declare module.exports: any; +} + +declare module 'electron-builder/out/cli/node-gyp-rebuild' { + declare module.exports: any; +} + +declare module 'electron-builder/out/codeSign' { + declare module.exports: any; +} + +declare module 'electron-builder/out/errorMessages' { + declare module.exports: any; +} + +declare module 'electron-builder/out/fileMatcher' { + declare module.exports: any; +} + +declare module 'electron-builder/out/fileTransformer' { + declare module.exports: any; +} + +declare module 'electron-builder/out/forge/forge-maker' { + declare module.exports: any; +} + +declare module 'electron-builder/out/index' { + declare module.exports: any; +} + +declare module 'electron-builder/out/linuxPackager' { + declare module.exports: any; +} + +declare module 'electron-builder/out/macPackager' { + declare module.exports: any; +} + +declare module 'electron-builder/out/metadata' { + declare module.exports: any; +} + +declare module 'electron-builder/out/options/linuxOptions' { + declare module.exports: any; +} + +declare module 'electron-builder/out/options/macOptions' { + declare module.exports: any; +} + +declare module 'electron-builder/out/options/winOptions' { + declare module.exports: any; +} + +declare module 'electron-builder/out/packager' { + declare module.exports: any; +} + +declare module 'electron-builder/out/packager/dirPackager' { + declare module.exports: any; +} + +declare module 'electron-builder/out/packager/mac' { + declare module.exports: any; +} + +declare module 'electron-builder/out/packagerApi' { + declare module.exports: any; +} + +declare module 'electron-builder/out/platformPackager' { + declare module.exports: any; +} + +declare module 'electron-builder/out/publish/PublishManager' { + declare module.exports: any; +} + +declare module 'electron-builder/out/readInstalled' { + declare module.exports: any; +} + +declare module 'electron-builder/out/repositoryInfo' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/appImage' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/appx' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/archive' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/ArchiveTarget' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/dmg' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/fpm' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/LinuxTargetHelper' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/nsis' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/pkg' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/snap' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/targetFactory' { + declare module.exports: any; +} + +declare module 'electron-builder/out/targets/WebInstallerTarget' { + declare module.exports: any; +} + +declare module 'electron-builder/out/util/filter' { + declare module.exports: any; +} + +declare module 'electron-builder/out/util/readPackageJson' { + declare module.exports: any; +} + +declare module 'electron-builder/out/windowsCodeSign' { + declare module.exports: any; +} + +declare module 'electron-builder/out/winPackager' { + declare module.exports: any; +} + +declare module 'electron-builder/out/yarn' { + declare module.exports: any; +} + +// Filename aliases +declare module 'electron-builder/out/appInfo.js' { + declare module.exports: $Exports<'electron-builder/out/appInfo'>; +} +declare module 'electron-builder/out/asar.js' { + declare module.exports: $Exports<'electron-builder/out/asar'>; +} +declare module 'electron-builder/out/asarUtil.js' { + declare module.exports: $Exports<'electron-builder/out/asarUtil'>; +} +declare module 'electron-builder/out/builder.js' { + declare module.exports: $Exports<'electron-builder/out/builder'>; +} +declare module 'electron-builder/out/cli/build-cli.js' { + declare module.exports: $Exports<'electron-builder/out/cli/build-cli'>; +} +declare module 'electron-builder/out/cli/cliOptions.js' { + declare module.exports: $Exports<'electron-builder/out/cli/cliOptions'>; +} +declare module 'electron-builder/out/cli/create-self-signed-cert.js' { + declare module.exports: $Exports<'electron-builder/out/cli/create-self-signed-cert'>; +} +declare module 'electron-builder/out/cli/install-app-deps.js' { + declare module.exports: $Exports<'electron-builder/out/cli/install-app-deps'>; +} +declare module 'electron-builder/out/cli/node-gyp-rebuild.js' { + declare module.exports: $Exports<'electron-builder/out/cli/node-gyp-rebuild'>; +} +declare module 'electron-builder/out/codeSign.js' { + declare module.exports: $Exports<'electron-builder/out/codeSign'>; +} +declare module 'electron-builder/out/errorMessages.js' { + declare module.exports: $Exports<'electron-builder/out/errorMessages'>; +} +declare module 'electron-builder/out/fileMatcher.js' { + declare module.exports: $Exports<'electron-builder/out/fileMatcher'>; +} +declare module 'electron-builder/out/fileTransformer.js' { + declare module.exports: $Exports<'electron-builder/out/fileTransformer'>; +} +declare module 'electron-builder/out/forge/forge-maker.js' { + declare module.exports: $Exports<'electron-builder/out/forge/forge-maker'>; +} +declare module 'electron-builder/out/index.js' { + declare module.exports: $Exports<'electron-builder/out/index'>; +} +declare module 'electron-builder/out/linuxPackager.js' { + declare module.exports: $Exports<'electron-builder/out/linuxPackager'>; +} +declare module 'electron-builder/out/macPackager.js' { + declare module.exports: $Exports<'electron-builder/out/macPackager'>; +} +declare module 'electron-builder/out/metadata.js' { + declare module.exports: $Exports<'electron-builder/out/metadata'>; +} +declare module 'electron-builder/out/options/linuxOptions.js' { + declare module.exports: $Exports<'electron-builder/out/options/linuxOptions'>; +} +declare module 'electron-builder/out/options/macOptions.js' { + declare module.exports: $Exports<'electron-builder/out/options/macOptions'>; +} +declare module 'electron-builder/out/options/winOptions.js' { + declare module.exports: $Exports<'electron-builder/out/options/winOptions'>; +} +declare module 'electron-builder/out/packager.js' { + declare module.exports: $Exports<'electron-builder/out/packager'>; +} +declare module 'electron-builder/out/packager/dirPackager.js' { + declare module.exports: $Exports<'electron-builder/out/packager/dirPackager'>; +} +declare module 'electron-builder/out/packager/mac.js' { + declare module.exports: $Exports<'electron-builder/out/packager/mac'>; +} +declare module 'electron-builder/out/packagerApi.js' { + declare module.exports: $Exports<'electron-builder/out/packagerApi'>; +} +declare module 'electron-builder/out/platformPackager.js' { + declare module.exports: $Exports<'electron-builder/out/platformPackager'>; +} +declare module 'electron-builder/out/publish/PublishManager.js' { + declare module.exports: $Exports<'electron-builder/out/publish/PublishManager'>; +} +declare module 'electron-builder/out/readInstalled.js' { + declare module.exports: $Exports<'electron-builder/out/readInstalled'>; +} +declare module 'electron-builder/out/repositoryInfo.js' { + declare module.exports: $Exports<'electron-builder/out/repositoryInfo'>; +} +declare module 'electron-builder/out/targets/appImage.js' { + declare module.exports: $Exports<'electron-builder/out/targets/appImage'>; +} +declare module 'electron-builder/out/targets/appx.js' { + declare module.exports: $Exports<'electron-builder/out/targets/appx'>; +} +declare module 'electron-builder/out/targets/archive.js' { + declare module.exports: $Exports<'electron-builder/out/targets/archive'>; +} +declare module 'electron-builder/out/targets/ArchiveTarget.js' { + declare module.exports: $Exports<'electron-builder/out/targets/ArchiveTarget'>; +} +declare module 'electron-builder/out/targets/dmg.js' { + declare module.exports: $Exports<'electron-builder/out/targets/dmg'>; +} +declare module 'electron-builder/out/targets/fpm.js' { + declare module.exports: $Exports<'electron-builder/out/targets/fpm'>; +} +declare module 'electron-builder/out/targets/LinuxTargetHelper.js' { + declare module.exports: $Exports<'electron-builder/out/targets/LinuxTargetHelper'>; +} +declare module 'electron-builder/out/targets/nsis.js' { + declare module.exports: $Exports<'electron-builder/out/targets/nsis'>; +} +declare module 'electron-builder/out/targets/pkg.js' { + declare module.exports: $Exports<'electron-builder/out/targets/pkg'>; +} +declare module 'electron-builder/out/targets/snap.js' { + declare module.exports: $Exports<'electron-builder/out/targets/snap'>; +} +declare module 'electron-builder/out/targets/targetFactory.js' { + declare module.exports: $Exports<'electron-builder/out/targets/targetFactory'>; +} +declare module 'electron-builder/out/targets/WebInstallerTarget.js' { + declare module.exports: $Exports<'electron-builder/out/targets/WebInstallerTarget'>; +} +declare module 'electron-builder/out/util/filter.js' { + declare module.exports: $Exports<'electron-builder/out/util/filter'>; +} +declare module 'electron-builder/out/util/readPackageJson.js' { + declare module.exports: $Exports<'electron-builder/out/util/readPackageJson'>; +} +declare module 'electron-builder/out/windowsCodeSign.js' { + declare module.exports: $Exports<'electron-builder/out/windowsCodeSign'>; +} +declare module 'electron-builder/out/winPackager.js' { + declare module.exports: $Exports<'electron-builder/out/winPackager'>; +} +declare module 'electron-builder/out/yarn.js' { + declare module.exports: $Exports<'electron-builder/out/yarn'>; +} diff --git a/flow-typed/npm/electron-devtools-installer_vx.x.x.js b/flow-typed/npm/electron-devtools-installer_vx.x.x.js new file mode 100644 index 000000000..6bab03346 --- /dev/null +++ b/flow-typed/npm/electron-devtools-installer_vx.x.x.js @@ -0,0 +1,67 @@ +// flow-typed signature: fce58527cae3f8649ab3ffb72e58090c +// flow-typed version: <>/electron-devtools-installer_v^2.2.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'electron-devtools-installer' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'electron-devtools-installer' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'electron-devtools-installer/dist/downloadChromeExtension' { + declare module.exports: any; +} + +declare module 'electron-devtools-installer/dist/index' { + declare module.exports: any; +} + +declare module 'electron-devtools-installer/dist/utils' { + declare module.exports: any; +} + +declare module 'electron-devtools-installer/src/downloadChromeExtension' { + declare module.exports: any; +} + +declare module 'electron-devtools-installer/src/index' { + declare module.exports: any; +} + +declare module 'electron-devtools-installer/src/utils' { + declare module.exports: any; +} + +// Filename aliases +declare module 'electron-devtools-installer/dist/downloadChromeExtension.js' { + declare module.exports: $Exports<'electron-devtools-installer/dist/downloadChromeExtension'>; +} +declare module 'electron-devtools-installer/dist/index.js' { + declare module.exports: $Exports<'electron-devtools-installer/dist/index'>; +} +declare module 'electron-devtools-installer/dist/utils.js' { + declare module.exports: $Exports<'electron-devtools-installer/dist/utils'>; +} +declare module 'electron-devtools-installer/src/downloadChromeExtension.js' { + declare module.exports: $Exports<'electron-devtools-installer/src/downloadChromeExtension'>; +} +declare module 'electron-devtools-installer/src/index.js' { + declare module.exports: $Exports<'electron-devtools-installer/src/index'>; +} +declare module 'electron-devtools-installer/src/utils.js' { + declare module.exports: $Exports<'electron-devtools-installer/src/utils'>; +} diff --git a/flow-typed/npm/electron_vx.x.x.js b/flow-typed/npm/electron_vx.x.x.js new file mode 100644 index 000000000..cbb81fb59 --- /dev/null +++ b/flow-typed/npm/electron_vx.x.x.js @@ -0,0 +1,59 @@ +// flow-typed signature: 451c88326bb7cc6aa79491d34e1c60b0 +// flow-typed version: <>/electron_v1.7.3/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'electron' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'electron' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'electron/cli' { + declare module.exports: any; +} + +declare module 'electron/install' { + declare module.exports: any; +} + +declare module 'electron/test/errors' { + declare module.exports: any; +} + +declare module 'electron/test/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'electron/cli.js' { + declare module.exports: $Exports<'electron/cli'>; +} +declare module 'electron/index' { + declare module.exports: $Exports<'electron'>; +} +declare module 'electron/index.js' { + declare module.exports: $Exports<'electron'>; +} +declare module 'electron/install.js' { + declare module.exports: $Exports<'electron/install'>; +} +declare module 'electron/test/errors.js' { + declare module.exports: $Exports<'electron/test/errors'>; +} +declare module 'electron/test/index.js' { + declare module.exports: $Exports<'electron/test/index'>; +} diff --git a/flow-typed/npm/eslint-config-react-app_vx.x.x.js b/flow-typed/npm/eslint-config-react-app_vx.x.x.js new file mode 100644 index 000000000..10d3abccf --- /dev/null +++ b/flow-typed/npm/eslint-config-react-app_vx.x.x.js @@ -0,0 +1,33 @@ +// flow-typed signature: 44d1e6a2fb005812ddc1cec21c490caf +// flow-typed version: <>/eslint-config-react-app_v^0.6.2/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'eslint-config-react-app' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'eslint-config-react-app' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ + + +// Filename aliases +declare module 'eslint-config-react-app/index' { + declare module.exports: $Exports<'eslint-config-react-app'>; +} +declare module 'eslint-config-react-app/index.js' { + declare module.exports: $Exports<'eslint-config-react-app'>; +} diff --git a/flow-typed/npm/eslint-plugin-flowtype_vx.x.x.js b/flow-typed/npm/eslint-plugin-flowtype_vx.x.x.js new file mode 100644 index 000000000..f58acf6ce --- /dev/null +++ b/flow-typed/npm/eslint-plugin-flowtype_vx.x.x.js @@ -0,0 +1,340 @@ +// flow-typed signature: 01f86fa0dc19e1126195f0bded4a75de +// flow-typed version: <>/eslint-plugin-flowtype_v^2.30.4/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'eslint-plugin-flowtype' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'eslint-plugin-flowtype' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'eslint-plugin-flowtype/bin/readmeAssertions' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/index' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/booleanStyle' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/defineFlowType' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/delimiterDangle' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/genericSpacing' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/noDupeKeys' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/noMutableArray' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/noPrimitiveConstructorTypes' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/noTypesMissingFileAnnotation' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/noUnusedExpressions' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/noWeakTypes' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/objectTypeDelimiter' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/requireParameterType' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/requireReturnType' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/requireValidFileAnnotation' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/requireVariableType' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/semi' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/sortKeys' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/spaceAfterTypeColon' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/spaceBeforeGenericBracket' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/spaceBeforeTypeColon' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateFunctions' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateObjectTypeIndexer' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateObjectTypeProperty' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateReturnType' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateTypeCastExpression' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateTypical' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/index' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/reporter' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/typeIdMatch' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/unionIntersectionSpacing' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/useFlowType' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/rules/validSyntax' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/checkFlowFileAnnotation' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/fuzzyStringMatch' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/getParameterName' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/getTokenAfterParens' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/getTokenBeforeParens' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/index' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/isFlowFile' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/isFlowFileAnnotation' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/iterateFunctionNodes' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/quoteName' { + declare module.exports: any; +} + +declare module 'eslint-plugin-flowtype/dist/utilities/spacingFixers' { + declare module.exports: any; +} + +// Filename aliases +declare module 'eslint-plugin-flowtype/bin/readmeAssertions.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/bin/readmeAssertions'>; +} +declare module 'eslint-plugin-flowtype/dist/index.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/index'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/booleanStyle.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/booleanStyle'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/defineFlowType.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/defineFlowType'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/delimiterDangle.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/delimiterDangle'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/genericSpacing.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/genericSpacing'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/noDupeKeys.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/noDupeKeys'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/noMutableArray.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/noMutableArray'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/noPrimitiveConstructorTypes.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/noPrimitiveConstructorTypes'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/noTypesMissingFileAnnotation.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/noTypesMissingFileAnnotation'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/noUnusedExpressions.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/noUnusedExpressions'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/noWeakTypes.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/noWeakTypes'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/objectTypeDelimiter.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/objectTypeDelimiter'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/requireParameterType.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/requireParameterType'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/requireReturnType.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/requireReturnType'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/requireValidFileAnnotation.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/requireValidFileAnnotation'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/requireVariableType.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/requireVariableType'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/semi.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/semi'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/sortKeys.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/sortKeys'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/spaceAfterTypeColon.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/spaceAfterTypeColon'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/spaceBeforeGenericBracket.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/spaceBeforeGenericBracket'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/spaceBeforeTypeColon.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/spaceBeforeTypeColon'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateFunctions.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateFunctions'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateObjectTypeIndexer.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateObjectTypeIndexer'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateObjectTypeProperty.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateObjectTypeProperty'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateReturnType.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateReturnType'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateTypeCastExpression.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateTypeCastExpression'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateTypical.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/typeColonSpacing/evaluateTypical'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/index.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/typeColonSpacing/index'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/typeColonSpacing/reporter.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/typeColonSpacing/reporter'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/typeIdMatch.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/typeIdMatch'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/unionIntersectionSpacing.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/unionIntersectionSpacing'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/useFlowType.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/useFlowType'>; +} +declare module 'eslint-plugin-flowtype/dist/rules/validSyntax.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/rules/validSyntax'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/checkFlowFileAnnotation.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/checkFlowFileAnnotation'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/fuzzyStringMatch.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/fuzzyStringMatch'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/getParameterName.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/getParameterName'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/getTokenAfterParens.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/getTokenAfterParens'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/getTokenBeforeParens.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/getTokenBeforeParens'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/index.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/index'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/isFlowFile.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/isFlowFile'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/isFlowFileAnnotation.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/isFlowFileAnnotation'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/iterateFunctionNodes.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/iterateFunctionNodes'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/quoteName.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/quoteName'>; +} +declare module 'eslint-plugin-flowtype/dist/utilities/spacingFixers.js' { + declare module.exports: $Exports<'eslint-plugin-flowtype/dist/utilities/spacingFixers'>; +} diff --git a/flow-typed/npm/eslint-plugin-import_vx.x.x.js b/flow-typed/npm/eslint-plugin-import_vx.x.x.js new file mode 100644 index 000000000..328442c4f --- /dev/null +++ b/flow-typed/npm/eslint-plugin-import_vx.x.x.js @@ -0,0 +1,340 @@ +// flow-typed signature: e6182e793944459234f882bb47d2ba01 +// flow-typed version: <>/eslint-plugin-import_v^2.2.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'eslint-plugin-import' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'eslint-plugin-import' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'eslint-plugin-import/config/electron' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/config/errors' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/config/react-native' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/config/react' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/config/recommended' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/config/stage-0' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/config/warnings' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/core/importType' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/core/staticRequire' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/ExportMap' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/importDeclaration' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/index' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/default' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/export' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/exports-last' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/extensions' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/first' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/imports-first' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/max-dependencies' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/named' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/namespace' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/newline-after-import' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-absolute-path' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-amd' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-anonymous-default-export' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-commonjs' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-deprecated' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-duplicates' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-dynamic-require' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-extraneous-dependencies' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-internal-modules' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-mutable-exports' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-named-as-default-member' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-named-as-default' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-named-default' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-namespace' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-nodejs-modules' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-restricted-paths' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-unassigned-import' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-unresolved' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/no-webpack-loader-syntax' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/order' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/prefer-default-export' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/lib/rules/unambiguous' { + declare module.exports: any; +} + +declare module 'eslint-plugin-import/memo-parser/index' { + declare module.exports: any; +} + +// Filename aliases +declare module 'eslint-plugin-import/config/electron.js' { + declare module.exports: $Exports<'eslint-plugin-import/config/electron'>; +} +declare module 'eslint-plugin-import/config/errors.js' { + declare module.exports: $Exports<'eslint-plugin-import/config/errors'>; +} +declare module 'eslint-plugin-import/config/react-native.js' { + declare module.exports: $Exports<'eslint-plugin-import/config/react-native'>; +} +declare module 'eslint-plugin-import/config/react.js' { + declare module.exports: $Exports<'eslint-plugin-import/config/react'>; +} +declare module 'eslint-plugin-import/config/recommended.js' { + declare module.exports: $Exports<'eslint-plugin-import/config/recommended'>; +} +declare module 'eslint-plugin-import/config/stage-0.js' { + declare module.exports: $Exports<'eslint-plugin-import/config/stage-0'>; +} +declare module 'eslint-plugin-import/config/warnings.js' { + declare module.exports: $Exports<'eslint-plugin-import/config/warnings'>; +} +declare module 'eslint-plugin-import/lib/core/importType.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/core/importType'>; +} +declare module 'eslint-plugin-import/lib/core/staticRequire.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/core/staticRequire'>; +} +declare module 'eslint-plugin-import/lib/ExportMap.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/ExportMap'>; +} +declare module 'eslint-plugin-import/lib/importDeclaration.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/importDeclaration'>; +} +declare module 'eslint-plugin-import/lib/index.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/index'>; +} +declare module 'eslint-plugin-import/lib/rules/default.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/default'>; +} +declare module 'eslint-plugin-import/lib/rules/export.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/export'>; +} +declare module 'eslint-plugin-import/lib/rules/exports-last.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/exports-last'>; +} +declare module 'eslint-plugin-import/lib/rules/extensions.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/extensions'>; +} +declare module 'eslint-plugin-import/lib/rules/first.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/first'>; +} +declare module 'eslint-plugin-import/lib/rules/imports-first.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/imports-first'>; +} +declare module 'eslint-plugin-import/lib/rules/max-dependencies.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/max-dependencies'>; +} +declare module 'eslint-plugin-import/lib/rules/named.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/named'>; +} +declare module 'eslint-plugin-import/lib/rules/namespace.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/namespace'>; +} +declare module 'eslint-plugin-import/lib/rules/newline-after-import.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/newline-after-import'>; +} +declare module 'eslint-plugin-import/lib/rules/no-absolute-path.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-absolute-path'>; +} +declare module 'eslint-plugin-import/lib/rules/no-amd.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-amd'>; +} +declare module 'eslint-plugin-import/lib/rules/no-anonymous-default-export.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-anonymous-default-export'>; +} +declare module 'eslint-plugin-import/lib/rules/no-commonjs.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-commonjs'>; +} +declare module 'eslint-plugin-import/lib/rules/no-deprecated.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-deprecated'>; +} +declare module 'eslint-plugin-import/lib/rules/no-duplicates.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-duplicates'>; +} +declare module 'eslint-plugin-import/lib/rules/no-dynamic-require.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-dynamic-require'>; +} +declare module 'eslint-plugin-import/lib/rules/no-extraneous-dependencies.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-extraneous-dependencies'>; +} +declare module 'eslint-plugin-import/lib/rules/no-internal-modules.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-internal-modules'>; +} +declare module 'eslint-plugin-import/lib/rules/no-mutable-exports.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-mutable-exports'>; +} +declare module 'eslint-plugin-import/lib/rules/no-named-as-default-member.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-named-as-default-member'>; +} +declare module 'eslint-plugin-import/lib/rules/no-named-as-default.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-named-as-default'>; +} +declare module 'eslint-plugin-import/lib/rules/no-named-default.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-named-default'>; +} +declare module 'eslint-plugin-import/lib/rules/no-namespace.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-namespace'>; +} +declare module 'eslint-plugin-import/lib/rules/no-nodejs-modules.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-nodejs-modules'>; +} +declare module 'eslint-plugin-import/lib/rules/no-restricted-paths.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-restricted-paths'>; +} +declare module 'eslint-plugin-import/lib/rules/no-unassigned-import.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-unassigned-import'>; +} +declare module 'eslint-plugin-import/lib/rules/no-unresolved.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-unresolved'>; +} +declare module 'eslint-plugin-import/lib/rules/no-webpack-loader-syntax.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/no-webpack-loader-syntax'>; +} +declare module 'eslint-plugin-import/lib/rules/order.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/order'>; +} +declare module 'eslint-plugin-import/lib/rules/prefer-default-export.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/prefer-default-export'>; +} +declare module 'eslint-plugin-import/lib/rules/unambiguous.js' { + declare module.exports: $Exports<'eslint-plugin-import/lib/rules/unambiguous'>; +} +declare module 'eslint-plugin-import/memo-parser/index.js' { + declare module.exports: $Exports<'eslint-plugin-import/memo-parser/index'>; +} diff --git a/flow-typed/npm/eslint-plugin-jest_vx.x.x.js b/flow-typed/npm/eslint-plugin-jest_vx.x.x.js new file mode 100644 index 000000000..691b5fc55 --- /dev/null +++ b/flow-typed/npm/eslint-plugin-jest_vx.x.x.js @@ -0,0 +1,67 @@ +// flow-typed signature: 8a40d0f5dc4e849acdc08684ab7c532f +// flow-typed version: <>/eslint-plugin-jest_v^20.0.1/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'eslint-plugin-jest' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'eslint-plugin-jest' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'eslint-plugin-jest/build/index' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jest/build/rules/no-disabled-tests' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jest/build/rules/no-focused-tests' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jest/build/rules/no-identical-title' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jest/build/rules/types' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jest/build/rules/valid-expect' { + declare module.exports: any; +} + +// Filename aliases +declare module 'eslint-plugin-jest/build/index.js' { + declare module.exports: $Exports<'eslint-plugin-jest/build/index'>; +} +declare module 'eslint-plugin-jest/build/rules/no-disabled-tests.js' { + declare module.exports: $Exports<'eslint-plugin-jest/build/rules/no-disabled-tests'>; +} +declare module 'eslint-plugin-jest/build/rules/no-focused-tests.js' { + declare module.exports: $Exports<'eslint-plugin-jest/build/rules/no-focused-tests'>; +} +declare module 'eslint-plugin-jest/build/rules/no-identical-title.js' { + declare module.exports: $Exports<'eslint-plugin-jest/build/rules/no-identical-title'>; +} +declare module 'eslint-plugin-jest/build/rules/types.js' { + declare module.exports: $Exports<'eslint-plugin-jest/build/rules/types'>; +} +declare module 'eslint-plugin-jest/build/rules/valid-expect.js' { + declare module.exports: $Exports<'eslint-plugin-jest/build/rules/valid-expect'>; +} diff --git a/flow-typed/npm/eslint-plugin-jsx-a11y_vx.x.x.js b/flow-typed/npm/eslint-plugin-jsx-a11y_vx.x.x.js new file mode 100644 index 000000000..fbaf71646 --- /dev/null +++ b/flow-typed/npm/eslint-plugin-jsx-a11y_vx.x.x.js @@ -0,0 +1,1411 @@ +// flow-typed signature: 6b61f7970cc96dba334588a5e4980afa +// flow-typed version: <>/eslint-plugin-jsx-a11y_v^4.0.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'eslint-plugin-jsx-a11y' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'eslint-plugin-jsx-a11y' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'eslint-plugin-jsx-a11y/__mocks__/genInteractives' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__mocks__/IdentifierMock' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__mocks__/JSXAttributeMock' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__mocks__/JSXElementMock' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__mocks__/JSXExpressionContainerMock' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/__util__/parserOptionsMapper' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/index-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/accessible-emoji-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/anchor-has-content-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-activedescendant-has-tabindex-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-props-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-proptypes-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-role-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-unsupported-elements-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/click-events-have-key-events-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/heading-has-content-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/href-no-hash-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/html-has-lang-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/iframe-has-title-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/img-has-alt-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/img-redundant-alt-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/label-has-for-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/lang-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/mouse-events-have-key-events-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-access-key-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-autofocus-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-distracting-elements-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-onchange-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-redundant-roles-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-static-element-interactions-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/onclick-has-focus-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/onclick-has-role-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/role-has-required-aria-props-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/role-supports-aria-props-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/scope-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/tabindex-no-positive-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/getSuggestion-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/getTabIndex-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/isInteractiveElement-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/isInteractiveRole-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/parserOptionsMapper-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/schemas-test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/flow/eslint' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/index' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/accessible-emoji' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/anchor-has-content' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-activedescendant-has-tabindex' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-proptypes' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-role' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-unsupported-elements' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/click-events-have-key-events' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/heading-has-content' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/href-no-hash' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/html-has-lang' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/iframe-has-title' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/img-has-alt' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/img-redundant-alt' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/label-has-for' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/lang' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/mouse-events-have-key-events' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-access-key' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-autofocus' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-distracting-elements' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-onchange' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-redundant-roles' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-static-element-interactions' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/onclick-has-focus' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/onclick-has-role' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/role-has-required-aria-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/role-supports-aria-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/scope' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/rules/tabindex-no-positive' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/getImplicitRole' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/getSuggestion' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/getTabIndex' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/a' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/area' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/article' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/aside' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/body' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/button' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/datalist' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/details' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/dialog' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/dl' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/form' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h1' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h2' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h3' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h4' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h5' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h6' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/hr' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/img' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/index' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/input' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/li' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/link' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/menu' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/menuitem' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/meter' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/nav' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/ol' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/option' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/output' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/progress' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/section' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/select' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/tbody' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/textarea' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/tfoot' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/thead' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/ul' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/isHiddenFromScreenReader' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/isInteractiveElement' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/isInteractiveRole' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/lib/util/schemas' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/scripts/addRuleToIndex' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/scripts/boilerplate/doc' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/scripts/boilerplate/rule' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/scripts/boilerplate/test' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/scripts/create-rule' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/index' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/accessible-emoji' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/anchor-has-content' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-activedescendant-has-tabindex' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-proptypes' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-role' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-unsupported-elements' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/click-events-have-key-events' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/heading-has-content' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/href-no-hash' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/html-has-lang' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/iframe-has-title' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/img-has-alt' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/img-redundant-alt' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/label-has-for' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/lang' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/mouse-events-have-key-events' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/no-access-key' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/no-autofocus' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/no-distracting-elements' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/no-onchange' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/no-redundant-roles' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/no-static-element-interactions' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/onclick-has-focus' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/onclick-has-role' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/role-has-required-aria-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/role-supports-aria-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/scope' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/rules/tabindex-no-positive' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/getImplicitRole' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/getSuggestion' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/getTabIndex' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/a' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/area' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/article' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/aside' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/body' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/button' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/datalist' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/details' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/dialog' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/dl' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/form' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h1' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h2' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h3' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h4' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h5' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h6' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/hr' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/img' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/index' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/input' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/li' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/link' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/menu' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/menuitem' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/meter' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/nav' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/ol' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/option' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/output' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/progress' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/section' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/select' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/tbody' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/textarea' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/tfoot' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/thead' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/ul' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/isHiddenFromScreenReader' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/isInteractiveElement' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/isInteractiveRole' { + declare module.exports: any; +} + +declare module 'eslint-plugin-jsx-a11y/src/util/schemas' { + declare module.exports: any; +} + +// Filename aliases +declare module 'eslint-plugin-jsx-a11y/__mocks__/genInteractives.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__mocks__/genInteractives'>; +} +declare module 'eslint-plugin-jsx-a11y/__mocks__/IdentifierMock.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__mocks__/IdentifierMock'>; +} +declare module 'eslint-plugin-jsx-a11y/__mocks__/JSXAttributeMock.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__mocks__/JSXAttributeMock'>; +} +declare module 'eslint-plugin-jsx-a11y/__mocks__/JSXElementMock.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__mocks__/JSXElementMock'>; +} +declare module 'eslint-plugin-jsx-a11y/__mocks__/JSXExpressionContainerMock.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__mocks__/JSXExpressionContainerMock'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/__util__/parserOptionsMapper.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/__util__/parserOptionsMapper'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/index-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/index-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/accessible-emoji-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/accessible-emoji-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/anchor-has-content-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/anchor-has-content-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-activedescendant-has-tabindex-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-props-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-props-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-proptypes-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-proptypes-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-role-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-role-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-unsupported-elements-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/aria-unsupported-elements-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/click-events-have-key-events-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/click-events-have-key-events-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/heading-has-content-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/heading-has-content-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/href-no-hash-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/href-no-hash-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/html-has-lang-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/html-has-lang-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/iframe-has-title-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/iframe-has-title-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/img-has-alt-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/img-has-alt-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/img-redundant-alt-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/img-redundant-alt-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/label-has-for-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/label-has-for-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/lang-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/lang-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/mouse-events-have-key-events-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/mouse-events-have-key-events-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-access-key-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/no-access-key-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-autofocus-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/no-autofocus-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-distracting-elements-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/no-distracting-elements-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-onchange-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/no-onchange-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-redundant-roles-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/no-redundant-roles-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/no-static-element-interactions-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/no-static-element-interactions-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/onclick-has-focus-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/onclick-has-focus-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/onclick-has-role-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/onclick-has-role-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/role-has-required-aria-props-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/role-has-required-aria-props-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/role-supports-aria-props-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/role-supports-aria-props-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/scope-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/scope-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/rules/tabindex-no-positive-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/rules/tabindex-no-positive-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/getSuggestion-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/util/getSuggestion-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/getTabIndex-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/util/getTabIndex-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/isInteractiveElement-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/util/isInteractiveElement-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/isInteractiveRole-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/util/isInteractiveRole-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/parserOptionsMapper-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/util/parserOptionsMapper-test'>; +} +declare module 'eslint-plugin-jsx-a11y/__tests__/src/util/schemas-test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/__tests__/src/util/schemas-test'>; +} +declare module 'eslint-plugin-jsx-a11y/flow/eslint.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/flow/eslint'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/index.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/index'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/accessible-emoji.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/accessible-emoji'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/anchor-has-content.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/anchor-has-content'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-activedescendant-has-tabindex.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/aria-activedescendant-has-tabindex'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-props.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/aria-props'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-proptypes.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/aria-proptypes'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-role.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/aria-role'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/aria-unsupported-elements.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/aria-unsupported-elements'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/click-events-have-key-events.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/click-events-have-key-events'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/heading-has-content.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/heading-has-content'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/href-no-hash.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/href-no-hash'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/html-has-lang.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/html-has-lang'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/iframe-has-title.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/iframe-has-title'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/img-has-alt.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/img-has-alt'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/img-redundant-alt.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/img-redundant-alt'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/label-has-for.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/label-has-for'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/lang.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/lang'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/mouse-events-have-key-events.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/mouse-events-have-key-events'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-access-key.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/no-access-key'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-autofocus.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/no-autofocus'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-distracting-elements.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/no-distracting-elements'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-onchange.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/no-onchange'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-redundant-roles.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/no-redundant-roles'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/no-static-element-interactions.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/no-static-element-interactions'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/onclick-has-focus.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/onclick-has-focus'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/onclick-has-role.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/onclick-has-role'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/role-has-required-aria-props.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/role-has-required-aria-props'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/role-supports-aria-props.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/role-supports-aria-props'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/scope.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/scope'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/rules/tabindex-no-positive.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/rules/tabindex-no-positive'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/getImplicitRole.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/getImplicitRole'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/getSuggestion.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/getSuggestion'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/getTabIndex.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/getTabIndex'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/a.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/a'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/area.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/area'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/article.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/article'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/aside.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/aside'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/body.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/body'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/button.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/button'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/datalist.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/datalist'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/details.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/details'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/dialog.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/dialog'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/dl.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/dl'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/form.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/form'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h1.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h1'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h2.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h2'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h3.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h3'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h4.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h4'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h5.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h5'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h6.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/h6'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/hr.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/hr'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/img.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/img'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/index.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/index'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/input.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/input'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/li.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/li'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/link.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/link'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/menu.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/menu'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/menuitem.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/menuitem'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/meter.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/meter'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/nav.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/nav'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/ol.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/ol'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/option.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/option'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/output.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/output'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/progress.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/progress'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/section.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/section'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/select.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/select'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/tbody.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/tbody'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/textarea.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/textarea'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/tfoot.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/tfoot'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/thead.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/thead'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/implicitRoles/ul.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/implicitRoles/ul'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/isHiddenFromScreenReader.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/isHiddenFromScreenReader'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/isInteractiveElement.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/isInteractiveElement'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/isInteractiveRole.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/isInteractiveRole'>; +} +declare module 'eslint-plugin-jsx-a11y/lib/util/schemas.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/lib/util/schemas'>; +} +declare module 'eslint-plugin-jsx-a11y/scripts/addRuleToIndex.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/scripts/addRuleToIndex'>; +} +declare module 'eslint-plugin-jsx-a11y/scripts/boilerplate/doc.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/scripts/boilerplate/doc'>; +} +declare module 'eslint-plugin-jsx-a11y/scripts/boilerplate/rule.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/scripts/boilerplate/rule'>; +} +declare module 'eslint-plugin-jsx-a11y/scripts/boilerplate/test.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/scripts/boilerplate/test'>; +} +declare module 'eslint-plugin-jsx-a11y/scripts/create-rule.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/scripts/create-rule'>; +} +declare module 'eslint-plugin-jsx-a11y/src/index.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/index'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/accessible-emoji.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/accessible-emoji'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/anchor-has-content.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/anchor-has-content'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-activedescendant-has-tabindex.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/aria-activedescendant-has-tabindex'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-props.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/aria-props'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-proptypes.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/aria-proptypes'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-role.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/aria-role'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/aria-unsupported-elements.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/aria-unsupported-elements'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/click-events-have-key-events.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/click-events-have-key-events'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/heading-has-content.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/heading-has-content'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/href-no-hash.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/href-no-hash'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/html-has-lang.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/html-has-lang'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/iframe-has-title.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/iframe-has-title'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/img-has-alt.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/img-has-alt'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/img-redundant-alt.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/img-redundant-alt'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/label-has-for.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/label-has-for'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/lang.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/lang'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/mouse-events-have-key-events.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/mouse-events-have-key-events'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/no-access-key.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/no-access-key'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/no-autofocus.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/no-autofocus'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/no-distracting-elements.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/no-distracting-elements'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/no-onchange.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/no-onchange'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/no-redundant-roles.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/no-redundant-roles'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/no-static-element-interactions.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/no-static-element-interactions'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/onclick-has-focus.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/onclick-has-focus'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/onclick-has-role.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/onclick-has-role'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/role-has-required-aria-props.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/role-has-required-aria-props'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/role-supports-aria-props.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/role-supports-aria-props'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/scope.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/scope'>; +} +declare module 'eslint-plugin-jsx-a11y/src/rules/tabindex-no-positive.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/rules/tabindex-no-positive'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/getImplicitRole.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/getImplicitRole'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/getSuggestion.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/getSuggestion'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/getTabIndex.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/getTabIndex'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/a.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/a'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/area.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/area'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/article.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/article'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/aside.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/aside'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/body.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/body'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/button.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/button'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/datalist.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/datalist'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/details.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/details'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/dialog.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/dialog'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/dl.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/dl'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/form.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/form'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h1.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/h1'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h2.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/h2'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h3.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/h3'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h4.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/h4'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h5.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/h5'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/h6.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/h6'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/hr.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/hr'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/img.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/img'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/index.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/index'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/input.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/input'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/li.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/li'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/link.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/link'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/menu.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/menu'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/menuitem.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/menuitem'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/meter.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/meter'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/nav.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/nav'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/ol.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/ol'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/option.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/option'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/output.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/output'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/progress.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/progress'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/section.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/section'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/select.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/select'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/tbody.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/tbody'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/textarea.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/textarea'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/tfoot.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/tfoot'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/thead.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/thead'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/implicitRoles/ul.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/implicitRoles/ul'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/isHiddenFromScreenReader.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/isHiddenFromScreenReader'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/isInteractiveElement.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/isInteractiveElement'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/isInteractiveRole.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/isInteractiveRole'>; +} +declare module 'eslint-plugin-jsx-a11y/src/util/schemas.js' { + declare module.exports: $Exports<'eslint-plugin-jsx-a11y/src/util/schemas'>; +} diff --git a/flow-typed/npm/eslint-plugin-prettier_vx.x.x.js b/flow-typed/npm/eslint-plugin-prettier_vx.x.x.js new file mode 100644 index 000000000..8b345c4ad --- /dev/null +++ b/flow-typed/npm/eslint-plugin-prettier_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: 409c518b379d758c595068e9eefec26b +// flow-typed version: <>/eslint-plugin-prettier_v^2.3.1/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'eslint-plugin-prettier' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'eslint-plugin-prettier' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'eslint-plugin-prettier/eslint-plugin-prettier' { + declare module.exports: any; +} + +// Filename aliases +declare module 'eslint-plugin-prettier/eslint-plugin-prettier.js' { + declare module.exports: $Exports<'eslint-plugin-prettier/eslint-plugin-prettier'>; +} diff --git a/flow-typed/npm/eslint-plugin-prettify_vx.x.x.js b/flow-typed/npm/eslint-plugin-prettify_vx.x.x.js new file mode 100644 index 000000000..d99f8cc70 --- /dev/null +++ b/flow-typed/npm/eslint-plugin-prettify_vx.x.x.js @@ -0,0 +1,45 @@ +// flow-typed signature: bcecc715564737f1856994b80c3fff42 +// flow-typed version: <>/eslint-plugin-prettify_v^1.0.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'eslint-plugin-prettify' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'eslint-plugin-prettify' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'eslint-plugin-prettify/no-styles' { + declare module.exports: any; +} + +declare module 'eslint-plugin-prettify/rule' { + declare module.exports: any; +} + +// Filename aliases +declare module 'eslint-plugin-prettify/index' { + declare module.exports: $Exports<'eslint-plugin-prettify'>; +} +declare module 'eslint-plugin-prettify/index.js' { + declare module.exports: $Exports<'eslint-plugin-prettify'>; +} +declare module 'eslint-plugin-prettify/no-styles.js' { + declare module.exports: $Exports<'eslint-plugin-prettify/no-styles'>; +} +declare module 'eslint-plugin-prettify/rule.js' { + declare module.exports: $Exports<'eslint-plugin-prettify/rule'>; +} diff --git a/flow-typed/npm/eslint-plugin-react_vx.x.x.js b/flow-typed/npm/eslint-plugin-react_vx.x.x.js new file mode 100644 index 000000000..fc36e09c4 --- /dev/null +++ b/flow-typed/npm/eslint-plugin-react_vx.x.x.js @@ -0,0 +1,500 @@ +// flow-typed signature: db9fe4d9399695da59af119fe46c104a +// flow-typed version: <>/eslint-plugin-react_v^6.10.3/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'eslint-plugin-react' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'eslint-plugin-react' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'eslint-plugin-react/lib/rules/display-name' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/forbid-component-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/forbid-elements' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/forbid-foreign-prop-types' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/forbid-prop-types' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-boolean-value' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-closing-bracket-location' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-curly-spacing' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-equals-spacing' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-filename-extension' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-first-prop-new-line' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-handler-names' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-indent-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-indent' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-key' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-max-props-per-line' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-no-bind' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-no-comment-textnodes' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-no-duplicate-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-no-literals' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-no-target-blank' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-no-undef' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-pascal-case' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-sort-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-space-before-closing' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-tag-spacing' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-uses-react' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-uses-vars' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/jsx-wrap-multilines' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-array-index-key' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-children-prop' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-comment-textnodes' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-danger-with-children' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-danger' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-deprecated' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-did-mount-set-state' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-did-update-set-state' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-direct-mutation-state' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-find-dom-node' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-is-mounted' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-multi-comp' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-render-return-value' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-set-state' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-string-refs' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-unescaped-entities' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-unknown-property' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/no-unused-prop-types' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/prefer-es6-class' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/prefer-stateless-function' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/prop-types' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/react-in-jsx-scope' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/require-default-props' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/require-extension' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/require-optimization' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/require-render-return' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/self-closing-comp' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/sort-comp' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/sort-prop-types' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/style-prop-object' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/void-dom-elements-no-children' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/rules/wrap-multilines' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/util/annotations' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/util/Components' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/util/getTokenBeforeClosingBracket' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/util/pragma' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/util/variable' { + declare module.exports: any; +} + +declare module 'eslint-plugin-react/lib/util/version' { + declare module.exports: any; +} + +// Filename aliases +declare module 'eslint-plugin-react/index' { + declare module.exports: $Exports<'eslint-plugin-react'>; +} +declare module 'eslint-plugin-react/index.js' { + declare module.exports: $Exports<'eslint-plugin-react'>; +} +declare module 'eslint-plugin-react/lib/rules/display-name.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/display-name'>; +} +declare module 'eslint-plugin-react/lib/rules/forbid-component-props.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/forbid-component-props'>; +} +declare module 'eslint-plugin-react/lib/rules/forbid-elements.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/forbid-elements'>; +} +declare module 'eslint-plugin-react/lib/rules/forbid-foreign-prop-types.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/forbid-foreign-prop-types'>; +} +declare module 'eslint-plugin-react/lib/rules/forbid-prop-types.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/forbid-prop-types'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-boolean-value.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-boolean-value'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-closing-bracket-location.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-closing-bracket-location'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-curly-spacing.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-curly-spacing'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-equals-spacing.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-equals-spacing'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-filename-extension.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-filename-extension'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-first-prop-new-line.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-first-prop-new-line'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-handler-names.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-handler-names'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-indent-props.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-indent-props'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-indent.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-indent'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-key.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-key'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-max-props-per-line.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-max-props-per-line'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-no-bind.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-no-bind'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-no-comment-textnodes.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-no-comment-textnodes'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-no-duplicate-props.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-no-duplicate-props'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-no-literals.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-no-literals'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-no-target-blank.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-no-target-blank'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-no-undef.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-no-undef'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-pascal-case.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-pascal-case'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-sort-props.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-sort-props'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-space-before-closing.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-space-before-closing'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-tag-spacing.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-tag-spacing'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-uses-react.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-uses-react'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-uses-vars.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-uses-vars'>; +} +declare module 'eslint-plugin-react/lib/rules/jsx-wrap-multilines.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/jsx-wrap-multilines'>; +} +declare module 'eslint-plugin-react/lib/rules/no-array-index-key.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-array-index-key'>; +} +declare module 'eslint-plugin-react/lib/rules/no-children-prop.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-children-prop'>; +} +declare module 'eslint-plugin-react/lib/rules/no-comment-textnodes.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-comment-textnodes'>; +} +declare module 'eslint-plugin-react/lib/rules/no-danger-with-children.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-danger-with-children'>; +} +declare module 'eslint-plugin-react/lib/rules/no-danger.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-danger'>; +} +declare module 'eslint-plugin-react/lib/rules/no-deprecated.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-deprecated'>; +} +declare module 'eslint-plugin-react/lib/rules/no-did-mount-set-state.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-did-mount-set-state'>; +} +declare module 'eslint-plugin-react/lib/rules/no-did-update-set-state.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-did-update-set-state'>; +} +declare module 'eslint-plugin-react/lib/rules/no-direct-mutation-state.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-direct-mutation-state'>; +} +declare module 'eslint-plugin-react/lib/rules/no-find-dom-node.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-find-dom-node'>; +} +declare module 'eslint-plugin-react/lib/rules/no-is-mounted.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-is-mounted'>; +} +declare module 'eslint-plugin-react/lib/rules/no-multi-comp.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-multi-comp'>; +} +declare module 'eslint-plugin-react/lib/rules/no-render-return-value.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-render-return-value'>; +} +declare module 'eslint-plugin-react/lib/rules/no-set-state.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-set-state'>; +} +declare module 'eslint-plugin-react/lib/rules/no-string-refs.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-string-refs'>; +} +declare module 'eslint-plugin-react/lib/rules/no-unescaped-entities.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-unescaped-entities'>; +} +declare module 'eslint-plugin-react/lib/rules/no-unknown-property.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-unknown-property'>; +} +declare module 'eslint-plugin-react/lib/rules/no-unused-prop-types.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/no-unused-prop-types'>; +} +declare module 'eslint-plugin-react/lib/rules/prefer-es6-class.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/prefer-es6-class'>; +} +declare module 'eslint-plugin-react/lib/rules/prefer-stateless-function.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/prefer-stateless-function'>; +} +declare module 'eslint-plugin-react/lib/rules/prop-types.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/prop-types'>; +} +declare module 'eslint-plugin-react/lib/rules/react-in-jsx-scope.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/react-in-jsx-scope'>; +} +declare module 'eslint-plugin-react/lib/rules/require-default-props.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/require-default-props'>; +} +declare module 'eslint-plugin-react/lib/rules/require-extension.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/require-extension'>; +} +declare module 'eslint-plugin-react/lib/rules/require-optimization.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/require-optimization'>; +} +declare module 'eslint-plugin-react/lib/rules/require-render-return.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/require-render-return'>; +} +declare module 'eslint-plugin-react/lib/rules/self-closing-comp.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/self-closing-comp'>; +} +declare module 'eslint-plugin-react/lib/rules/sort-comp.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/sort-comp'>; +} +declare module 'eslint-plugin-react/lib/rules/sort-prop-types.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/sort-prop-types'>; +} +declare module 'eslint-plugin-react/lib/rules/style-prop-object.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/style-prop-object'>; +} +declare module 'eslint-plugin-react/lib/rules/void-dom-elements-no-children.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/void-dom-elements-no-children'>; +} +declare module 'eslint-plugin-react/lib/rules/wrap-multilines.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/rules/wrap-multilines'>; +} +declare module 'eslint-plugin-react/lib/util/annotations.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/util/annotations'>; +} +declare module 'eslint-plugin-react/lib/util/Components.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/util/Components'>; +} +declare module 'eslint-plugin-react/lib/util/getTokenBeforeClosingBracket.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/util/getTokenBeforeClosingBracket'>; +} +declare module 'eslint-plugin-react/lib/util/pragma.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/util/pragma'>; +} +declare module 'eslint-plugin-react/lib/util/variable.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/util/variable'>; +} +declare module 'eslint-plugin-react/lib/util/version.js' { + declare module.exports: $Exports<'eslint-plugin-react/lib/util/version'>; +} diff --git a/flow-typed/npm/eslint_vx.x.x.js b/flow-typed/npm/eslint_vx.x.x.js new file mode 100644 index 000000000..d05b36b11 --- /dev/null +++ b/flow-typed/npm/eslint_vx.x.x.js @@ -0,0 +1,2293 @@ +// flow-typed signature: 21bb1519e9485a20d45c150ed4f678de +// flow-typed version: <>/eslint_v^3.19.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'eslint' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'eslint' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'eslint/bin/eslint' { + declare module.exports: any; +} + +declare module 'eslint/conf/cli-options' { + declare module.exports: any; +} + +declare module 'eslint/conf/environments' { + declare module.exports: any; +} + +declare module 'eslint/conf/eslint-all' { + declare module.exports: any; +} + +declare module 'eslint/conf/eslint-recommended' { + declare module.exports: any; +} + +declare module 'eslint/lib/api' { + declare module.exports: any; +} + +declare module 'eslint/lib/ast-utils' { + declare module.exports: any; +} + +declare module 'eslint/lib/cli-engine' { + declare module.exports: any; +} + +declare module 'eslint/lib/cli' { + declare module.exports: any; +} + +declare module 'eslint/lib/code-path-analysis/code-path-analyzer' { + declare module.exports: any; +} + +declare module 'eslint/lib/code-path-analysis/code-path-segment' { + declare module.exports: any; +} + +declare module 'eslint/lib/code-path-analysis/code-path-state' { + declare module.exports: any; +} + +declare module 'eslint/lib/code-path-analysis/code-path' { + declare module.exports: any; +} + +declare module 'eslint/lib/code-path-analysis/debug-helpers' { + declare module.exports: any; +} + +declare module 'eslint/lib/code-path-analysis/fork-context' { + declare module.exports: any; +} + +declare module 'eslint/lib/code-path-analysis/id-generator' { + declare module.exports: any; +} + +declare module 'eslint/lib/config' { + declare module.exports: any; +} + +declare module 'eslint/lib/config/autoconfig' { + declare module.exports: any; +} + +declare module 'eslint/lib/config/config-file' { + declare module.exports: any; +} + +declare module 'eslint/lib/config/config-initializer' { + declare module.exports: any; +} + +declare module 'eslint/lib/config/config-ops' { + declare module.exports: any; +} + +declare module 'eslint/lib/config/config-rule' { + declare module.exports: any; +} + +declare module 'eslint/lib/config/config-validator' { + declare module.exports: any; +} + +declare module 'eslint/lib/config/environments' { + declare module.exports: any; +} + +declare module 'eslint/lib/config/plugins' { + declare module.exports: any; +} + +declare module 'eslint/lib/eslint' { + declare module.exports: any; +} + +declare module 'eslint/lib/file-finder' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/checkstyle' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/codeframe' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/compact' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/html' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/jslint-xml' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/json' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/junit' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/stylish' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/table' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/tap' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/unix' { + declare module.exports: any; +} + +declare module 'eslint/lib/formatters/visualstudio' { + declare module.exports: any; +} + +declare module 'eslint/lib/ignored-paths' { + declare module.exports: any; +} + +declare module 'eslint/lib/internal-rules/internal-consistent-docs-description' { + declare module.exports: any; +} + +declare module 'eslint/lib/internal-rules/internal-no-invalid-meta' { + declare module.exports: any; +} + +declare module 'eslint/lib/load-rules' { + declare module.exports: any; +} + +declare module 'eslint/lib/logging' { + declare module.exports: any; +} + +declare module 'eslint/lib/options' { + declare module.exports: any; +} + +declare module 'eslint/lib/rule-context' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/accessor-pairs' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/array-bracket-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/array-callback-return' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/arrow-body-style' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/arrow-parens' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/arrow-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/block-scoped-var' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/block-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/brace-style' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/callback-return' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/camelcase' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/capitalized-comments' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/class-methods-use-this' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/comma-dangle' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/comma-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/comma-style' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/complexity' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/computed-property-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/consistent-return' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/consistent-this' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/constructor-super' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/curly' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/default-case' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/dot-location' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/dot-notation' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/eol-last' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/eqeqeq' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/func-call-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/func-name-matching' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/func-names' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/func-style' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/generator-star-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/global-require' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/guard-for-in' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/handle-callback-err' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/id-blacklist' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/id-length' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/id-match' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/indent' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/init-declarations' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/jsx-quotes' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/key-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/keyword-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/line-comment-position' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/linebreak-style' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/lines-around-comment' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/lines-around-directive' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/max-depth' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/max-len' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/max-lines' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/max-nested-callbacks' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/max-params' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/max-statements-per-line' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/max-statements' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/multiline-ternary' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/new-cap' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/new-parens' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/newline-after-var' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/newline-before-return' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/newline-per-chained-call' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-alert' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-array-constructor' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-await-in-loop' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-bitwise' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-caller' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-case-declarations' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-catch-shadow' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-class-assign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-compare-neg-zero' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-cond-assign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-confusing-arrow' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-console' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-const-assign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-constant-condition' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-continue' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-control-regex' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-debugger' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-delete-var' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-div-regex' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-dupe-args' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-dupe-class-members' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-dupe-keys' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-duplicate-case' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-duplicate-imports' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-else-return' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-empty-character-class' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-empty-function' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-empty-pattern' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-empty' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-eq-null' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-eval' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-ex-assign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-extend-native' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-extra-bind' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-extra-boolean-cast' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-extra-label' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-extra-parens' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-extra-semi' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-fallthrough' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-floating-decimal' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-func-assign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-global-assign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-implicit-coercion' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-implicit-globals' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-implied-eval' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-inline-comments' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-inner-declarations' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-invalid-regexp' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-invalid-this' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-irregular-whitespace' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-iterator' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-label-var' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-labels' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-lone-blocks' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-lonely-if' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-loop-func' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-magic-numbers' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-mixed-operators' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-mixed-requires' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-mixed-spaces-and-tabs' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-multi-assign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-multi-spaces' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-multi-str' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-multiple-empty-lines' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-native-reassign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-negated-condition' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-negated-in-lhs' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-nested-ternary' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-new-func' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-new-object' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-new-require' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-new-symbol' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-new-wrappers' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-new' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-obj-calls' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-octal-escape' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-octal' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-param-reassign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-path-concat' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-plusplus' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-process-env' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-process-exit' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-proto' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-prototype-builtins' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-redeclare' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-regex-spaces' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-restricted-globals' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-restricted-imports' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-restricted-modules' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-restricted-properties' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-restricted-syntax' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-return-assign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-return-await' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-script-url' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-self-assign' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-self-compare' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-sequences' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-shadow-restricted-names' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-shadow' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-spaced-func' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-sparse-arrays' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-sync' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-tabs' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-template-curly-in-string' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-ternary' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-this-before-super' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-throw-literal' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-trailing-spaces' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-undef-init' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-undef' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-undefined' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-underscore-dangle' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-unexpected-multiline' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-unmodified-loop-condition' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-unneeded-ternary' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-unreachable' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-unsafe-finally' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-unsafe-negation' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-unused-expressions' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-unused-labels' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-unused-vars' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-use-before-define' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-useless-call' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-useless-computed-key' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-useless-concat' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-useless-constructor' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-useless-escape' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-useless-rename' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-useless-return' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-var' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-void' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-warning-comments' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-whitespace-before-property' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/no-with' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/nonblock-statement-body-position' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/object-curly-newline' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/object-curly-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/object-property-newline' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/object-shorthand' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/one-var-declaration-per-line' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/one-var' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/operator-assignment' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/operator-linebreak' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/padded-blocks' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/prefer-arrow-callback' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/prefer-const' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/prefer-destructuring' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/prefer-numeric-literals' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/prefer-promise-reject-errors' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/prefer-reflect' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/prefer-rest-params' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/prefer-spread' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/prefer-template' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/quote-props' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/quotes' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/radix' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/require-await' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/require-jsdoc' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/require-yield' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/rest-spread-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/semi-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/semi' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/sort-imports' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/sort-keys' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/sort-vars' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/space-before-blocks' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/space-before-function-paren' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/space-in-parens' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/space-infix-ops' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/space-unary-ops' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/spaced-comment' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/strict' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/symbol-description' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/template-curly-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/template-tag-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/unicode-bom' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/use-isnan' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/valid-jsdoc' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/valid-typeof' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/vars-on-top' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/wrap-iife' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/wrap-regex' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/yield-star-spacing' { + declare module.exports: any; +} + +declare module 'eslint/lib/rules/yoda' { + declare module.exports: any; +} + +declare module 'eslint/lib/testers/event-generator-tester' { + declare module.exports: any; +} + +declare module 'eslint/lib/testers/rule-tester' { + declare module.exports: any; +} + +declare module 'eslint/lib/timing' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/backward-token-comment-cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/backward-token-cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/cursors' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/decorative-cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/filter-cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/forward-token-comment-cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/forward-token-cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/index' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/limit-cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/padded-token-cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/skip-cursor' { + declare module.exports: any; +} + +declare module 'eslint/lib/token-store/utils' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/comment-event-generator' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/fix-tracker' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/glob-util' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/glob' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/hash' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/keywords' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/module-resolver' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/node-event-generator' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/npm-util' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/path-util' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/patterns/letters' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/rule-fixer' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/source-code-fixer' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/source-code-util' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/source-code' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/traverser' { + declare module.exports: any; +} + +declare module 'eslint/lib/util/xml-escape' { + declare module.exports: any; +} + +// Filename aliases +declare module 'eslint/bin/eslint.js' { + declare module.exports: $Exports<'eslint/bin/eslint'>; +} +declare module 'eslint/conf/cli-options.js' { + declare module.exports: $Exports<'eslint/conf/cli-options'>; +} +declare module 'eslint/conf/environments.js' { + declare module.exports: $Exports<'eslint/conf/environments'>; +} +declare module 'eslint/conf/eslint-all.js' { + declare module.exports: $Exports<'eslint/conf/eslint-all'>; +} +declare module 'eslint/conf/eslint-recommended.js' { + declare module.exports: $Exports<'eslint/conf/eslint-recommended'>; +} +declare module 'eslint/lib/api.js' { + declare module.exports: $Exports<'eslint/lib/api'>; +} +declare module 'eslint/lib/ast-utils.js' { + declare module.exports: $Exports<'eslint/lib/ast-utils'>; +} +declare module 'eslint/lib/cli-engine.js' { + declare module.exports: $Exports<'eslint/lib/cli-engine'>; +} +declare module 'eslint/lib/cli.js' { + declare module.exports: $Exports<'eslint/lib/cli'>; +} +declare module 'eslint/lib/code-path-analysis/code-path-analyzer.js' { + declare module.exports: $Exports<'eslint/lib/code-path-analysis/code-path-analyzer'>; +} +declare module 'eslint/lib/code-path-analysis/code-path-segment.js' { + declare module.exports: $Exports<'eslint/lib/code-path-analysis/code-path-segment'>; +} +declare module 'eslint/lib/code-path-analysis/code-path-state.js' { + declare module.exports: $Exports<'eslint/lib/code-path-analysis/code-path-state'>; +} +declare module 'eslint/lib/code-path-analysis/code-path.js' { + declare module.exports: $Exports<'eslint/lib/code-path-analysis/code-path'>; +} +declare module 'eslint/lib/code-path-analysis/debug-helpers.js' { + declare module.exports: $Exports<'eslint/lib/code-path-analysis/debug-helpers'>; +} +declare module 'eslint/lib/code-path-analysis/fork-context.js' { + declare module.exports: $Exports<'eslint/lib/code-path-analysis/fork-context'>; +} +declare module 'eslint/lib/code-path-analysis/id-generator.js' { + declare module.exports: $Exports<'eslint/lib/code-path-analysis/id-generator'>; +} +declare module 'eslint/lib/config.js' { + declare module.exports: $Exports<'eslint/lib/config'>; +} +declare module 'eslint/lib/config/autoconfig.js' { + declare module.exports: $Exports<'eslint/lib/config/autoconfig'>; +} +declare module 'eslint/lib/config/config-file.js' { + declare module.exports: $Exports<'eslint/lib/config/config-file'>; +} +declare module 'eslint/lib/config/config-initializer.js' { + declare module.exports: $Exports<'eslint/lib/config/config-initializer'>; +} +declare module 'eslint/lib/config/config-ops.js' { + declare module.exports: $Exports<'eslint/lib/config/config-ops'>; +} +declare module 'eslint/lib/config/config-rule.js' { + declare module.exports: $Exports<'eslint/lib/config/config-rule'>; +} +declare module 'eslint/lib/config/config-validator.js' { + declare module.exports: $Exports<'eslint/lib/config/config-validator'>; +} +declare module 'eslint/lib/config/environments.js' { + declare module.exports: $Exports<'eslint/lib/config/environments'>; +} +declare module 'eslint/lib/config/plugins.js' { + declare module.exports: $Exports<'eslint/lib/config/plugins'>; +} +declare module 'eslint/lib/eslint.js' { + declare module.exports: $Exports<'eslint/lib/eslint'>; +} +declare module 'eslint/lib/file-finder.js' { + declare module.exports: $Exports<'eslint/lib/file-finder'>; +} +declare module 'eslint/lib/formatters/checkstyle.js' { + declare module.exports: $Exports<'eslint/lib/formatters/checkstyle'>; +} +declare module 'eslint/lib/formatters/codeframe.js' { + declare module.exports: $Exports<'eslint/lib/formatters/codeframe'>; +} +declare module 'eslint/lib/formatters/compact.js' { + declare module.exports: $Exports<'eslint/lib/formatters/compact'>; +} +declare module 'eslint/lib/formatters/html.js' { + declare module.exports: $Exports<'eslint/lib/formatters/html'>; +} +declare module 'eslint/lib/formatters/jslint-xml.js' { + declare module.exports: $Exports<'eslint/lib/formatters/jslint-xml'>; +} +declare module 'eslint/lib/formatters/json.js' { + declare module.exports: $Exports<'eslint/lib/formatters/json'>; +} +declare module 'eslint/lib/formatters/junit.js' { + declare module.exports: $Exports<'eslint/lib/formatters/junit'>; +} +declare module 'eslint/lib/formatters/stylish.js' { + declare module.exports: $Exports<'eslint/lib/formatters/stylish'>; +} +declare module 'eslint/lib/formatters/table.js' { + declare module.exports: $Exports<'eslint/lib/formatters/table'>; +} +declare module 'eslint/lib/formatters/tap.js' { + declare module.exports: $Exports<'eslint/lib/formatters/tap'>; +} +declare module 'eslint/lib/formatters/unix.js' { + declare module.exports: $Exports<'eslint/lib/formatters/unix'>; +} +declare module 'eslint/lib/formatters/visualstudio.js' { + declare module.exports: $Exports<'eslint/lib/formatters/visualstudio'>; +} +declare module 'eslint/lib/ignored-paths.js' { + declare module.exports: $Exports<'eslint/lib/ignored-paths'>; +} +declare module 'eslint/lib/internal-rules/internal-consistent-docs-description.js' { + declare module.exports: $Exports<'eslint/lib/internal-rules/internal-consistent-docs-description'>; +} +declare module 'eslint/lib/internal-rules/internal-no-invalid-meta.js' { + declare module.exports: $Exports<'eslint/lib/internal-rules/internal-no-invalid-meta'>; +} +declare module 'eslint/lib/load-rules.js' { + declare module.exports: $Exports<'eslint/lib/load-rules'>; +} +declare module 'eslint/lib/logging.js' { + declare module.exports: $Exports<'eslint/lib/logging'>; +} +declare module 'eslint/lib/options.js' { + declare module.exports: $Exports<'eslint/lib/options'>; +} +declare module 'eslint/lib/rule-context.js' { + declare module.exports: $Exports<'eslint/lib/rule-context'>; +} +declare module 'eslint/lib/rules.js' { + declare module.exports: $Exports<'eslint/lib/rules'>; +} +declare module 'eslint/lib/rules/accessor-pairs.js' { + declare module.exports: $Exports<'eslint/lib/rules/accessor-pairs'>; +} +declare module 'eslint/lib/rules/array-bracket-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/array-bracket-spacing'>; +} +declare module 'eslint/lib/rules/array-callback-return.js' { + declare module.exports: $Exports<'eslint/lib/rules/array-callback-return'>; +} +declare module 'eslint/lib/rules/arrow-body-style.js' { + declare module.exports: $Exports<'eslint/lib/rules/arrow-body-style'>; +} +declare module 'eslint/lib/rules/arrow-parens.js' { + declare module.exports: $Exports<'eslint/lib/rules/arrow-parens'>; +} +declare module 'eslint/lib/rules/arrow-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/arrow-spacing'>; +} +declare module 'eslint/lib/rules/block-scoped-var.js' { + declare module.exports: $Exports<'eslint/lib/rules/block-scoped-var'>; +} +declare module 'eslint/lib/rules/block-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/block-spacing'>; +} +declare module 'eslint/lib/rules/brace-style.js' { + declare module.exports: $Exports<'eslint/lib/rules/brace-style'>; +} +declare module 'eslint/lib/rules/callback-return.js' { + declare module.exports: $Exports<'eslint/lib/rules/callback-return'>; +} +declare module 'eslint/lib/rules/camelcase.js' { + declare module.exports: $Exports<'eslint/lib/rules/camelcase'>; +} +declare module 'eslint/lib/rules/capitalized-comments.js' { + declare module.exports: $Exports<'eslint/lib/rules/capitalized-comments'>; +} +declare module 'eslint/lib/rules/class-methods-use-this.js' { + declare module.exports: $Exports<'eslint/lib/rules/class-methods-use-this'>; +} +declare module 'eslint/lib/rules/comma-dangle.js' { + declare module.exports: $Exports<'eslint/lib/rules/comma-dangle'>; +} +declare module 'eslint/lib/rules/comma-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/comma-spacing'>; +} +declare module 'eslint/lib/rules/comma-style.js' { + declare module.exports: $Exports<'eslint/lib/rules/comma-style'>; +} +declare module 'eslint/lib/rules/complexity.js' { + declare module.exports: $Exports<'eslint/lib/rules/complexity'>; +} +declare module 'eslint/lib/rules/computed-property-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/computed-property-spacing'>; +} +declare module 'eslint/lib/rules/consistent-return.js' { + declare module.exports: $Exports<'eslint/lib/rules/consistent-return'>; +} +declare module 'eslint/lib/rules/consistent-this.js' { + declare module.exports: $Exports<'eslint/lib/rules/consistent-this'>; +} +declare module 'eslint/lib/rules/constructor-super.js' { + declare module.exports: $Exports<'eslint/lib/rules/constructor-super'>; +} +declare module 'eslint/lib/rules/curly.js' { + declare module.exports: $Exports<'eslint/lib/rules/curly'>; +} +declare module 'eslint/lib/rules/default-case.js' { + declare module.exports: $Exports<'eslint/lib/rules/default-case'>; +} +declare module 'eslint/lib/rules/dot-location.js' { + declare module.exports: $Exports<'eslint/lib/rules/dot-location'>; +} +declare module 'eslint/lib/rules/dot-notation.js' { + declare module.exports: $Exports<'eslint/lib/rules/dot-notation'>; +} +declare module 'eslint/lib/rules/eol-last.js' { + declare module.exports: $Exports<'eslint/lib/rules/eol-last'>; +} +declare module 'eslint/lib/rules/eqeqeq.js' { + declare module.exports: $Exports<'eslint/lib/rules/eqeqeq'>; +} +declare module 'eslint/lib/rules/func-call-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/func-call-spacing'>; +} +declare module 'eslint/lib/rules/func-name-matching.js' { + declare module.exports: $Exports<'eslint/lib/rules/func-name-matching'>; +} +declare module 'eslint/lib/rules/func-names.js' { + declare module.exports: $Exports<'eslint/lib/rules/func-names'>; +} +declare module 'eslint/lib/rules/func-style.js' { + declare module.exports: $Exports<'eslint/lib/rules/func-style'>; +} +declare module 'eslint/lib/rules/generator-star-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/generator-star-spacing'>; +} +declare module 'eslint/lib/rules/global-require.js' { + declare module.exports: $Exports<'eslint/lib/rules/global-require'>; +} +declare module 'eslint/lib/rules/guard-for-in.js' { + declare module.exports: $Exports<'eslint/lib/rules/guard-for-in'>; +} +declare module 'eslint/lib/rules/handle-callback-err.js' { + declare module.exports: $Exports<'eslint/lib/rules/handle-callback-err'>; +} +declare module 'eslint/lib/rules/id-blacklist.js' { + declare module.exports: $Exports<'eslint/lib/rules/id-blacklist'>; +} +declare module 'eslint/lib/rules/id-length.js' { + declare module.exports: $Exports<'eslint/lib/rules/id-length'>; +} +declare module 'eslint/lib/rules/id-match.js' { + declare module.exports: $Exports<'eslint/lib/rules/id-match'>; +} +declare module 'eslint/lib/rules/indent.js' { + declare module.exports: $Exports<'eslint/lib/rules/indent'>; +} +declare module 'eslint/lib/rules/init-declarations.js' { + declare module.exports: $Exports<'eslint/lib/rules/init-declarations'>; +} +declare module 'eslint/lib/rules/jsx-quotes.js' { + declare module.exports: $Exports<'eslint/lib/rules/jsx-quotes'>; +} +declare module 'eslint/lib/rules/key-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/key-spacing'>; +} +declare module 'eslint/lib/rules/keyword-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/keyword-spacing'>; +} +declare module 'eslint/lib/rules/line-comment-position.js' { + declare module.exports: $Exports<'eslint/lib/rules/line-comment-position'>; +} +declare module 'eslint/lib/rules/linebreak-style.js' { + declare module.exports: $Exports<'eslint/lib/rules/linebreak-style'>; +} +declare module 'eslint/lib/rules/lines-around-comment.js' { + declare module.exports: $Exports<'eslint/lib/rules/lines-around-comment'>; +} +declare module 'eslint/lib/rules/lines-around-directive.js' { + declare module.exports: $Exports<'eslint/lib/rules/lines-around-directive'>; +} +declare module 'eslint/lib/rules/max-depth.js' { + declare module.exports: $Exports<'eslint/lib/rules/max-depth'>; +} +declare module 'eslint/lib/rules/max-len.js' { + declare module.exports: $Exports<'eslint/lib/rules/max-len'>; +} +declare module 'eslint/lib/rules/max-lines.js' { + declare module.exports: $Exports<'eslint/lib/rules/max-lines'>; +} +declare module 'eslint/lib/rules/max-nested-callbacks.js' { + declare module.exports: $Exports<'eslint/lib/rules/max-nested-callbacks'>; +} +declare module 'eslint/lib/rules/max-params.js' { + declare module.exports: $Exports<'eslint/lib/rules/max-params'>; +} +declare module 'eslint/lib/rules/max-statements-per-line.js' { + declare module.exports: $Exports<'eslint/lib/rules/max-statements-per-line'>; +} +declare module 'eslint/lib/rules/max-statements.js' { + declare module.exports: $Exports<'eslint/lib/rules/max-statements'>; +} +declare module 'eslint/lib/rules/multiline-ternary.js' { + declare module.exports: $Exports<'eslint/lib/rules/multiline-ternary'>; +} +declare module 'eslint/lib/rules/new-cap.js' { + declare module.exports: $Exports<'eslint/lib/rules/new-cap'>; +} +declare module 'eslint/lib/rules/new-parens.js' { + declare module.exports: $Exports<'eslint/lib/rules/new-parens'>; +} +declare module 'eslint/lib/rules/newline-after-var.js' { + declare module.exports: $Exports<'eslint/lib/rules/newline-after-var'>; +} +declare module 'eslint/lib/rules/newline-before-return.js' { + declare module.exports: $Exports<'eslint/lib/rules/newline-before-return'>; +} +declare module 'eslint/lib/rules/newline-per-chained-call.js' { + declare module.exports: $Exports<'eslint/lib/rules/newline-per-chained-call'>; +} +declare module 'eslint/lib/rules/no-alert.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-alert'>; +} +declare module 'eslint/lib/rules/no-array-constructor.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-array-constructor'>; +} +declare module 'eslint/lib/rules/no-await-in-loop.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-await-in-loop'>; +} +declare module 'eslint/lib/rules/no-bitwise.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-bitwise'>; +} +declare module 'eslint/lib/rules/no-caller.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-caller'>; +} +declare module 'eslint/lib/rules/no-case-declarations.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-case-declarations'>; +} +declare module 'eslint/lib/rules/no-catch-shadow.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-catch-shadow'>; +} +declare module 'eslint/lib/rules/no-class-assign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-class-assign'>; +} +declare module 'eslint/lib/rules/no-compare-neg-zero.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-compare-neg-zero'>; +} +declare module 'eslint/lib/rules/no-cond-assign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-cond-assign'>; +} +declare module 'eslint/lib/rules/no-confusing-arrow.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-confusing-arrow'>; +} +declare module 'eslint/lib/rules/no-console.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-console'>; +} +declare module 'eslint/lib/rules/no-const-assign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-const-assign'>; +} +declare module 'eslint/lib/rules/no-constant-condition.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-constant-condition'>; +} +declare module 'eslint/lib/rules/no-continue.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-continue'>; +} +declare module 'eslint/lib/rules/no-control-regex.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-control-regex'>; +} +declare module 'eslint/lib/rules/no-debugger.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-debugger'>; +} +declare module 'eslint/lib/rules/no-delete-var.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-delete-var'>; +} +declare module 'eslint/lib/rules/no-div-regex.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-div-regex'>; +} +declare module 'eslint/lib/rules/no-dupe-args.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-dupe-args'>; +} +declare module 'eslint/lib/rules/no-dupe-class-members.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-dupe-class-members'>; +} +declare module 'eslint/lib/rules/no-dupe-keys.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-dupe-keys'>; +} +declare module 'eslint/lib/rules/no-duplicate-case.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-duplicate-case'>; +} +declare module 'eslint/lib/rules/no-duplicate-imports.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-duplicate-imports'>; +} +declare module 'eslint/lib/rules/no-else-return.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-else-return'>; +} +declare module 'eslint/lib/rules/no-empty-character-class.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-empty-character-class'>; +} +declare module 'eslint/lib/rules/no-empty-function.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-empty-function'>; +} +declare module 'eslint/lib/rules/no-empty-pattern.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-empty-pattern'>; +} +declare module 'eslint/lib/rules/no-empty.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-empty'>; +} +declare module 'eslint/lib/rules/no-eq-null.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-eq-null'>; +} +declare module 'eslint/lib/rules/no-eval.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-eval'>; +} +declare module 'eslint/lib/rules/no-ex-assign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-ex-assign'>; +} +declare module 'eslint/lib/rules/no-extend-native.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-extend-native'>; +} +declare module 'eslint/lib/rules/no-extra-bind.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-extra-bind'>; +} +declare module 'eslint/lib/rules/no-extra-boolean-cast.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-extra-boolean-cast'>; +} +declare module 'eslint/lib/rules/no-extra-label.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-extra-label'>; +} +declare module 'eslint/lib/rules/no-extra-parens.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-extra-parens'>; +} +declare module 'eslint/lib/rules/no-extra-semi.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-extra-semi'>; +} +declare module 'eslint/lib/rules/no-fallthrough.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-fallthrough'>; +} +declare module 'eslint/lib/rules/no-floating-decimal.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-floating-decimal'>; +} +declare module 'eslint/lib/rules/no-func-assign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-func-assign'>; +} +declare module 'eslint/lib/rules/no-global-assign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-global-assign'>; +} +declare module 'eslint/lib/rules/no-implicit-coercion.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-implicit-coercion'>; +} +declare module 'eslint/lib/rules/no-implicit-globals.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-implicit-globals'>; +} +declare module 'eslint/lib/rules/no-implied-eval.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-implied-eval'>; +} +declare module 'eslint/lib/rules/no-inline-comments.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-inline-comments'>; +} +declare module 'eslint/lib/rules/no-inner-declarations.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-inner-declarations'>; +} +declare module 'eslint/lib/rules/no-invalid-regexp.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-invalid-regexp'>; +} +declare module 'eslint/lib/rules/no-invalid-this.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-invalid-this'>; +} +declare module 'eslint/lib/rules/no-irregular-whitespace.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-irregular-whitespace'>; +} +declare module 'eslint/lib/rules/no-iterator.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-iterator'>; +} +declare module 'eslint/lib/rules/no-label-var.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-label-var'>; +} +declare module 'eslint/lib/rules/no-labels.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-labels'>; +} +declare module 'eslint/lib/rules/no-lone-blocks.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-lone-blocks'>; +} +declare module 'eslint/lib/rules/no-lonely-if.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-lonely-if'>; +} +declare module 'eslint/lib/rules/no-loop-func.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-loop-func'>; +} +declare module 'eslint/lib/rules/no-magic-numbers.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-magic-numbers'>; +} +declare module 'eslint/lib/rules/no-mixed-operators.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-mixed-operators'>; +} +declare module 'eslint/lib/rules/no-mixed-requires.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-mixed-requires'>; +} +declare module 'eslint/lib/rules/no-mixed-spaces-and-tabs.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-mixed-spaces-and-tabs'>; +} +declare module 'eslint/lib/rules/no-multi-assign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-multi-assign'>; +} +declare module 'eslint/lib/rules/no-multi-spaces.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-multi-spaces'>; +} +declare module 'eslint/lib/rules/no-multi-str.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-multi-str'>; +} +declare module 'eslint/lib/rules/no-multiple-empty-lines.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-multiple-empty-lines'>; +} +declare module 'eslint/lib/rules/no-native-reassign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-native-reassign'>; +} +declare module 'eslint/lib/rules/no-negated-condition.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-negated-condition'>; +} +declare module 'eslint/lib/rules/no-negated-in-lhs.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-negated-in-lhs'>; +} +declare module 'eslint/lib/rules/no-nested-ternary.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-nested-ternary'>; +} +declare module 'eslint/lib/rules/no-new-func.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-new-func'>; +} +declare module 'eslint/lib/rules/no-new-object.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-new-object'>; +} +declare module 'eslint/lib/rules/no-new-require.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-new-require'>; +} +declare module 'eslint/lib/rules/no-new-symbol.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-new-symbol'>; +} +declare module 'eslint/lib/rules/no-new-wrappers.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-new-wrappers'>; +} +declare module 'eslint/lib/rules/no-new.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-new'>; +} +declare module 'eslint/lib/rules/no-obj-calls.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-obj-calls'>; +} +declare module 'eslint/lib/rules/no-octal-escape.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-octal-escape'>; +} +declare module 'eslint/lib/rules/no-octal.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-octal'>; +} +declare module 'eslint/lib/rules/no-param-reassign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-param-reassign'>; +} +declare module 'eslint/lib/rules/no-path-concat.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-path-concat'>; +} +declare module 'eslint/lib/rules/no-plusplus.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-plusplus'>; +} +declare module 'eslint/lib/rules/no-process-env.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-process-env'>; +} +declare module 'eslint/lib/rules/no-process-exit.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-process-exit'>; +} +declare module 'eslint/lib/rules/no-proto.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-proto'>; +} +declare module 'eslint/lib/rules/no-prototype-builtins.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-prototype-builtins'>; +} +declare module 'eslint/lib/rules/no-redeclare.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-redeclare'>; +} +declare module 'eslint/lib/rules/no-regex-spaces.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-regex-spaces'>; +} +declare module 'eslint/lib/rules/no-restricted-globals.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-restricted-globals'>; +} +declare module 'eslint/lib/rules/no-restricted-imports.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-restricted-imports'>; +} +declare module 'eslint/lib/rules/no-restricted-modules.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-restricted-modules'>; +} +declare module 'eslint/lib/rules/no-restricted-properties.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-restricted-properties'>; +} +declare module 'eslint/lib/rules/no-restricted-syntax.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-restricted-syntax'>; +} +declare module 'eslint/lib/rules/no-return-assign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-return-assign'>; +} +declare module 'eslint/lib/rules/no-return-await.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-return-await'>; +} +declare module 'eslint/lib/rules/no-script-url.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-script-url'>; +} +declare module 'eslint/lib/rules/no-self-assign.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-self-assign'>; +} +declare module 'eslint/lib/rules/no-self-compare.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-self-compare'>; +} +declare module 'eslint/lib/rules/no-sequences.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-sequences'>; +} +declare module 'eslint/lib/rules/no-shadow-restricted-names.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-shadow-restricted-names'>; +} +declare module 'eslint/lib/rules/no-shadow.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-shadow'>; +} +declare module 'eslint/lib/rules/no-spaced-func.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-spaced-func'>; +} +declare module 'eslint/lib/rules/no-sparse-arrays.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-sparse-arrays'>; +} +declare module 'eslint/lib/rules/no-sync.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-sync'>; +} +declare module 'eslint/lib/rules/no-tabs.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-tabs'>; +} +declare module 'eslint/lib/rules/no-template-curly-in-string.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-template-curly-in-string'>; +} +declare module 'eslint/lib/rules/no-ternary.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-ternary'>; +} +declare module 'eslint/lib/rules/no-this-before-super.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-this-before-super'>; +} +declare module 'eslint/lib/rules/no-throw-literal.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-throw-literal'>; +} +declare module 'eslint/lib/rules/no-trailing-spaces.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-trailing-spaces'>; +} +declare module 'eslint/lib/rules/no-undef-init.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-undef-init'>; +} +declare module 'eslint/lib/rules/no-undef.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-undef'>; +} +declare module 'eslint/lib/rules/no-undefined.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-undefined'>; +} +declare module 'eslint/lib/rules/no-underscore-dangle.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-underscore-dangle'>; +} +declare module 'eslint/lib/rules/no-unexpected-multiline.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-unexpected-multiline'>; +} +declare module 'eslint/lib/rules/no-unmodified-loop-condition.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-unmodified-loop-condition'>; +} +declare module 'eslint/lib/rules/no-unneeded-ternary.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-unneeded-ternary'>; +} +declare module 'eslint/lib/rules/no-unreachable.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-unreachable'>; +} +declare module 'eslint/lib/rules/no-unsafe-finally.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-unsafe-finally'>; +} +declare module 'eslint/lib/rules/no-unsafe-negation.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-unsafe-negation'>; +} +declare module 'eslint/lib/rules/no-unused-expressions.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-unused-expressions'>; +} +declare module 'eslint/lib/rules/no-unused-labels.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-unused-labels'>; +} +declare module 'eslint/lib/rules/no-unused-vars.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-unused-vars'>; +} +declare module 'eslint/lib/rules/no-use-before-define.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-use-before-define'>; +} +declare module 'eslint/lib/rules/no-useless-call.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-useless-call'>; +} +declare module 'eslint/lib/rules/no-useless-computed-key.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-useless-computed-key'>; +} +declare module 'eslint/lib/rules/no-useless-concat.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-useless-concat'>; +} +declare module 'eslint/lib/rules/no-useless-constructor.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-useless-constructor'>; +} +declare module 'eslint/lib/rules/no-useless-escape.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-useless-escape'>; +} +declare module 'eslint/lib/rules/no-useless-rename.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-useless-rename'>; +} +declare module 'eslint/lib/rules/no-useless-return.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-useless-return'>; +} +declare module 'eslint/lib/rules/no-var.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-var'>; +} +declare module 'eslint/lib/rules/no-void.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-void'>; +} +declare module 'eslint/lib/rules/no-warning-comments.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-warning-comments'>; +} +declare module 'eslint/lib/rules/no-whitespace-before-property.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-whitespace-before-property'>; +} +declare module 'eslint/lib/rules/no-with.js' { + declare module.exports: $Exports<'eslint/lib/rules/no-with'>; +} +declare module 'eslint/lib/rules/nonblock-statement-body-position.js' { + declare module.exports: $Exports<'eslint/lib/rules/nonblock-statement-body-position'>; +} +declare module 'eslint/lib/rules/object-curly-newline.js' { + declare module.exports: $Exports<'eslint/lib/rules/object-curly-newline'>; +} +declare module 'eslint/lib/rules/object-curly-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/object-curly-spacing'>; +} +declare module 'eslint/lib/rules/object-property-newline.js' { + declare module.exports: $Exports<'eslint/lib/rules/object-property-newline'>; +} +declare module 'eslint/lib/rules/object-shorthand.js' { + declare module.exports: $Exports<'eslint/lib/rules/object-shorthand'>; +} +declare module 'eslint/lib/rules/one-var-declaration-per-line.js' { + declare module.exports: $Exports<'eslint/lib/rules/one-var-declaration-per-line'>; +} +declare module 'eslint/lib/rules/one-var.js' { + declare module.exports: $Exports<'eslint/lib/rules/one-var'>; +} +declare module 'eslint/lib/rules/operator-assignment.js' { + declare module.exports: $Exports<'eslint/lib/rules/operator-assignment'>; +} +declare module 'eslint/lib/rules/operator-linebreak.js' { + declare module.exports: $Exports<'eslint/lib/rules/operator-linebreak'>; +} +declare module 'eslint/lib/rules/padded-blocks.js' { + declare module.exports: $Exports<'eslint/lib/rules/padded-blocks'>; +} +declare module 'eslint/lib/rules/prefer-arrow-callback.js' { + declare module.exports: $Exports<'eslint/lib/rules/prefer-arrow-callback'>; +} +declare module 'eslint/lib/rules/prefer-const.js' { + declare module.exports: $Exports<'eslint/lib/rules/prefer-const'>; +} +declare module 'eslint/lib/rules/prefer-destructuring.js' { + declare module.exports: $Exports<'eslint/lib/rules/prefer-destructuring'>; +} +declare module 'eslint/lib/rules/prefer-numeric-literals.js' { + declare module.exports: $Exports<'eslint/lib/rules/prefer-numeric-literals'>; +} +declare module 'eslint/lib/rules/prefer-promise-reject-errors.js' { + declare module.exports: $Exports<'eslint/lib/rules/prefer-promise-reject-errors'>; +} +declare module 'eslint/lib/rules/prefer-reflect.js' { + declare module.exports: $Exports<'eslint/lib/rules/prefer-reflect'>; +} +declare module 'eslint/lib/rules/prefer-rest-params.js' { + declare module.exports: $Exports<'eslint/lib/rules/prefer-rest-params'>; +} +declare module 'eslint/lib/rules/prefer-spread.js' { + declare module.exports: $Exports<'eslint/lib/rules/prefer-spread'>; +} +declare module 'eslint/lib/rules/prefer-template.js' { + declare module.exports: $Exports<'eslint/lib/rules/prefer-template'>; +} +declare module 'eslint/lib/rules/quote-props.js' { + declare module.exports: $Exports<'eslint/lib/rules/quote-props'>; +} +declare module 'eslint/lib/rules/quotes.js' { + declare module.exports: $Exports<'eslint/lib/rules/quotes'>; +} +declare module 'eslint/lib/rules/radix.js' { + declare module.exports: $Exports<'eslint/lib/rules/radix'>; +} +declare module 'eslint/lib/rules/require-await.js' { + declare module.exports: $Exports<'eslint/lib/rules/require-await'>; +} +declare module 'eslint/lib/rules/require-jsdoc.js' { + declare module.exports: $Exports<'eslint/lib/rules/require-jsdoc'>; +} +declare module 'eslint/lib/rules/require-yield.js' { + declare module.exports: $Exports<'eslint/lib/rules/require-yield'>; +} +declare module 'eslint/lib/rules/rest-spread-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/rest-spread-spacing'>; +} +declare module 'eslint/lib/rules/semi-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/semi-spacing'>; +} +declare module 'eslint/lib/rules/semi.js' { + declare module.exports: $Exports<'eslint/lib/rules/semi'>; +} +declare module 'eslint/lib/rules/sort-imports.js' { + declare module.exports: $Exports<'eslint/lib/rules/sort-imports'>; +} +declare module 'eslint/lib/rules/sort-keys.js' { + declare module.exports: $Exports<'eslint/lib/rules/sort-keys'>; +} +declare module 'eslint/lib/rules/sort-vars.js' { + declare module.exports: $Exports<'eslint/lib/rules/sort-vars'>; +} +declare module 'eslint/lib/rules/space-before-blocks.js' { + declare module.exports: $Exports<'eslint/lib/rules/space-before-blocks'>; +} +declare module 'eslint/lib/rules/space-before-function-paren.js' { + declare module.exports: $Exports<'eslint/lib/rules/space-before-function-paren'>; +} +declare module 'eslint/lib/rules/space-in-parens.js' { + declare module.exports: $Exports<'eslint/lib/rules/space-in-parens'>; +} +declare module 'eslint/lib/rules/space-infix-ops.js' { + declare module.exports: $Exports<'eslint/lib/rules/space-infix-ops'>; +} +declare module 'eslint/lib/rules/space-unary-ops.js' { + declare module.exports: $Exports<'eslint/lib/rules/space-unary-ops'>; +} +declare module 'eslint/lib/rules/spaced-comment.js' { + declare module.exports: $Exports<'eslint/lib/rules/spaced-comment'>; +} +declare module 'eslint/lib/rules/strict.js' { + declare module.exports: $Exports<'eslint/lib/rules/strict'>; +} +declare module 'eslint/lib/rules/symbol-description.js' { + declare module.exports: $Exports<'eslint/lib/rules/symbol-description'>; +} +declare module 'eslint/lib/rules/template-curly-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/template-curly-spacing'>; +} +declare module 'eslint/lib/rules/template-tag-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/template-tag-spacing'>; +} +declare module 'eslint/lib/rules/unicode-bom.js' { + declare module.exports: $Exports<'eslint/lib/rules/unicode-bom'>; +} +declare module 'eslint/lib/rules/use-isnan.js' { + declare module.exports: $Exports<'eslint/lib/rules/use-isnan'>; +} +declare module 'eslint/lib/rules/valid-jsdoc.js' { + declare module.exports: $Exports<'eslint/lib/rules/valid-jsdoc'>; +} +declare module 'eslint/lib/rules/valid-typeof.js' { + declare module.exports: $Exports<'eslint/lib/rules/valid-typeof'>; +} +declare module 'eslint/lib/rules/vars-on-top.js' { + declare module.exports: $Exports<'eslint/lib/rules/vars-on-top'>; +} +declare module 'eslint/lib/rules/wrap-iife.js' { + declare module.exports: $Exports<'eslint/lib/rules/wrap-iife'>; +} +declare module 'eslint/lib/rules/wrap-regex.js' { + declare module.exports: $Exports<'eslint/lib/rules/wrap-regex'>; +} +declare module 'eslint/lib/rules/yield-star-spacing.js' { + declare module.exports: $Exports<'eslint/lib/rules/yield-star-spacing'>; +} +declare module 'eslint/lib/rules/yoda.js' { + declare module.exports: $Exports<'eslint/lib/rules/yoda'>; +} +declare module 'eslint/lib/testers/event-generator-tester.js' { + declare module.exports: $Exports<'eslint/lib/testers/event-generator-tester'>; +} +declare module 'eslint/lib/testers/rule-tester.js' { + declare module.exports: $Exports<'eslint/lib/testers/rule-tester'>; +} +declare module 'eslint/lib/timing.js' { + declare module.exports: $Exports<'eslint/lib/timing'>; +} +declare module 'eslint/lib/token-store/backward-token-comment-cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/backward-token-comment-cursor'>; +} +declare module 'eslint/lib/token-store/backward-token-cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/backward-token-cursor'>; +} +declare module 'eslint/lib/token-store/cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/cursor'>; +} +declare module 'eslint/lib/token-store/cursors.js' { + declare module.exports: $Exports<'eslint/lib/token-store/cursors'>; +} +declare module 'eslint/lib/token-store/decorative-cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/decorative-cursor'>; +} +declare module 'eslint/lib/token-store/filter-cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/filter-cursor'>; +} +declare module 'eslint/lib/token-store/forward-token-comment-cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/forward-token-comment-cursor'>; +} +declare module 'eslint/lib/token-store/forward-token-cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/forward-token-cursor'>; +} +declare module 'eslint/lib/token-store/index.js' { + declare module.exports: $Exports<'eslint/lib/token-store/index'>; +} +declare module 'eslint/lib/token-store/limit-cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/limit-cursor'>; +} +declare module 'eslint/lib/token-store/padded-token-cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/padded-token-cursor'>; +} +declare module 'eslint/lib/token-store/skip-cursor.js' { + declare module.exports: $Exports<'eslint/lib/token-store/skip-cursor'>; +} +declare module 'eslint/lib/token-store/utils.js' { + declare module.exports: $Exports<'eslint/lib/token-store/utils'>; +} +declare module 'eslint/lib/util/comment-event-generator.js' { + declare module.exports: $Exports<'eslint/lib/util/comment-event-generator'>; +} +declare module 'eslint/lib/util/fix-tracker.js' { + declare module.exports: $Exports<'eslint/lib/util/fix-tracker'>; +} +declare module 'eslint/lib/util/glob-util.js' { + declare module.exports: $Exports<'eslint/lib/util/glob-util'>; +} +declare module 'eslint/lib/util/glob.js' { + declare module.exports: $Exports<'eslint/lib/util/glob'>; +} +declare module 'eslint/lib/util/hash.js' { + declare module.exports: $Exports<'eslint/lib/util/hash'>; +} +declare module 'eslint/lib/util/keywords.js' { + declare module.exports: $Exports<'eslint/lib/util/keywords'>; +} +declare module 'eslint/lib/util/module-resolver.js' { + declare module.exports: $Exports<'eslint/lib/util/module-resolver'>; +} +declare module 'eslint/lib/util/node-event-generator.js' { + declare module.exports: $Exports<'eslint/lib/util/node-event-generator'>; +} +declare module 'eslint/lib/util/npm-util.js' { + declare module.exports: $Exports<'eslint/lib/util/npm-util'>; +} +declare module 'eslint/lib/util/path-util.js' { + declare module.exports: $Exports<'eslint/lib/util/path-util'>; +} +declare module 'eslint/lib/util/patterns/letters.js' { + declare module.exports: $Exports<'eslint/lib/util/patterns/letters'>; +} +declare module 'eslint/lib/util/rule-fixer.js' { + declare module.exports: $Exports<'eslint/lib/util/rule-fixer'>; +} +declare module 'eslint/lib/util/source-code-fixer.js' { + declare module.exports: $Exports<'eslint/lib/util/source-code-fixer'>; +} +declare module 'eslint/lib/util/source-code-util.js' { + declare module.exports: $Exports<'eslint/lib/util/source-code-util'>; +} +declare module 'eslint/lib/util/source-code.js' { + declare module.exports: $Exports<'eslint/lib/util/source-code'>; +} +declare module 'eslint/lib/util/traverser.js' { + declare module.exports: $Exports<'eslint/lib/util/traverser'>; +} +declare module 'eslint/lib/util/xml-escape.js' { + declare module.exports: $Exports<'eslint/lib/util/xml-escape'>; +} diff --git a/flow-typed/npm/express_v4.x.x.js b/flow-typed/npm/express_v4.x.x.js new file mode 100644 index 000000000..82e05800e --- /dev/null +++ b/flow-typed/npm/express_v4.x.x.js @@ -0,0 +1,207 @@ +// flow-typed signature: f0e399a136d6e8dc8b1fbdc078e2850c +// flow-typed version: ed397013d1/express_v4.x.x/flow_>=v0.32.x + +import type { Server } from 'http'; +import type { Socket } from 'net'; + +declare type express$RouterOptions = { + caseSensitive?: boolean, + mergeParams?: boolean, + strict?: boolean +}; + +declare class express$RequestResponseBase { + app: express$Application; + get(field: string): string | void; +} + +declare type express$RequestParams = { + [param: string]: string +} + +declare class express$Request extends http$IncomingMessage mixins express$RequestResponseBase { + baseUrl: string; + body: any; + cookies: {[cookie: string]: string}; + connection: Socket; + fresh: boolean; + hostname: string; + ip: string; + ips: Array; + method: string; + originalUrl: string; + params: express$RequestParams; + path: string; + protocol: 'https' | 'http'; + query: {[name: string]: string | Array}; + route: string; + secure: boolean; + signedCookies: {[signedCookie: string]: string}; + stale: boolean; + subdomains: Array; + xhr: boolean; + accepts(types: string): string | false; + accepts(types: Array): string | false; + acceptsCharsets(...charsets: Array): string | false; + acceptsEncodings(...encoding: Array): string | false; + acceptsLanguages(...lang: Array): string | false; + header(field: string): string | void; + is(type: string): boolean; + param(name: string, defaultValue?: string): string | void; +} + +declare type express$CookieOptions = { + domain?: string, + encode?: (value: string) => string, + expires?: Date, + httpOnly?: boolean, + maxAge?: number, + path?: string, + secure?: boolean, + signed?: boolean +}; + +declare type express$Path = string | RegExp; + +declare type express$RenderCallback = (err: Error | null, html?: string) => mixed; + +declare type express$SendFileOptions = { + maxAge?: number, + root?: string, + lastModified?: boolean, + headers?: {[name: string]: string}, + dotfiles?: 'allow' | 'deny' | 'ignore' +}; + +declare class express$Response extends http$ServerResponse mixins express$RequestResponseBase { + headersSent: boolean; + locals: {[name: string]: mixed}; + append(field: string, value?: string): this; + attachment(filename?: string): this; + cookie(name: string, value: string, options?: express$CookieOptions): this; + clearCookie(name: string, options?: express$CookieOptions): this; + download(path: string, filename?: string, callback?: (err?: ?Error) => void): this; + format(typesObject: {[type: string]: Function}): this; + json(body?: mixed): this; + jsonp(body?: mixed): this; + links(links: {[name: string]: string}): this; + location(path: string): this; + redirect(url: string, ...args: Array): this; + redirect(status: number, url: string, ...args: Array): this; + render(view: string, locals?: {[name: string]: mixed}, callback?: express$RenderCallback): this; + send(body?: mixed): this; + sendFile(path: string, options?: express$SendFileOptions, callback?: (err?: ?Error) => mixed): this; + sendStatus(statusCode: number): this; + header(field: string, value?: string): this; + header(headers: {[name: string]: string}): this; + set(field: string, value?: string|string[]): this; + set(headers: {[name: string]: string}): this; + status(statusCode: number): this; + type(type: string): this; + vary(field: string): this; + req: express$Request; +} + +declare type express$NextFunction = (err?: ?Error | 'route') => mixed; +declare type express$Middleware = + ((req: $Subtype, res: express$Response, next: express$NextFunction) => mixed) | + ((error: Error, req: $Subtype, res: express$Response, next: express$NextFunction) => mixed); +declare interface express$RouteMethodType { + (middleware: express$Middleware): T; + (...middleware: Array): T; + (path: express$Path|express$Path[], ...middleware: Array): T; +} +declare class express$Route { + all: express$RouteMethodType; + get: express$RouteMethodType; + post: express$RouteMethodType; + put: express$RouteMethodType; + head: express$RouteMethodType; + delete: express$RouteMethodType; + options: express$RouteMethodType; + trace: express$RouteMethodType; + copy: express$RouteMethodType; + lock: express$RouteMethodType; + mkcol: express$RouteMethodType; + move: express$RouteMethodType; + purge: express$RouteMethodType; + propfind: express$RouteMethodType; + proppatch: express$RouteMethodType; + unlock: express$RouteMethodType; + report: express$RouteMethodType; + mkactivity: express$RouteMethodType; + checkout: express$RouteMethodType; + merge: express$RouteMethodType; + + // @TODO Missing 'm-search' but get flow illegal name error. + + notify: express$RouteMethodType; + subscribe: express$RouteMethodType; + unsubscribe: express$RouteMethodType; + patch: express$RouteMethodType; + search: express$RouteMethodType; + connect: express$RouteMethodType; +} + +declare class express$Router extends express$Route { + constructor(options?: express$RouterOptions): void; + route(path: string): express$Route; + static (options?: express$RouterOptions): express$Router; + use(middleware: express$Middleware): this; + use(...middleware: Array): this; + use(path: express$Path|express$Path[], ...middleware: Array): this; + use(path: string, router: express$Router): this; + handle(req: http$IncomingMessage, res: http$ServerResponse, next: express$NextFunction): void; + param( + param: string, + callback: ( + req: $Subtype, + res: express$Response, + next: express$NextFunction, + id: string + ) => mixed + ): void; + + // Can't use regular callable signature syntax due to https://github.com/facebook/flow/issues/3084 + $call: (req: http$IncomingMessage, res: http$ServerResponse, next?: ?express$NextFunction) => void; +} + +declare class express$Application extends express$Router mixins events$EventEmitter { + constructor(): void; + locals: {[name: string]: mixed}; + mountpath: string; + listen(port: number, hostname?: string, backlog?: number, callback?: (err?: ?Error) => mixed): Server; + listen(port: number, hostname?: string, callback?: (err?: ?Error) => mixed): Server; + listen(port: number, callback?: (err?: ?Error) => mixed): Server; + listen(path: string, callback?: (err?: ?Error) => mixed): Server; + listen(handle: Object, callback?: (err?: ?Error) => mixed): Server; + disable(name: string): void; + disabled(name: string): boolean; + enable(name: string): express$Application; + enabled(name: string): boolean; + engine(name: string, callback: Function): void; + /** + * Mixed will not be taken as a value option. Issue around using the GET http method name and the get for settings. + */ + // get(name: string): mixed; + set(name: string, value: mixed): mixed; + render(name: string, optionsOrFunction: {[name: string]: mixed}, callback: express$RenderCallback): void; + handle(req: http$IncomingMessage, res: http$ServerResponse, next?: ?express$NextFunction): void; +} + +declare module 'express' { + declare export type RouterOptions = express$RouterOptions; + declare export type CookieOptions = express$CookieOptions; + declare export type Middleware = express$Middleware; + declare export type NextFunction = express$NextFunction; + declare export type RequestParams = express$RequestParams; + declare export type $Response = express$Response; + declare export type $Request = express$Request; + declare export type $Application = express$Application; + + declare module.exports: { + (): express$Application, // If you try to call like a function, it will use this signature + static: (root: string, options?: Object) => express$Middleware, // `static` property on the function + Router: typeof express$Router, // `Router` property on the function + }; +} diff --git a/flow-typed/npm/flow-bin_v0.x.x.js b/flow-typed/npm/flow-bin_v0.x.x.js new file mode 100644 index 000000000..c538e2086 --- /dev/null +++ b/flow-typed/npm/flow-bin_v0.x.x.js @@ -0,0 +1,6 @@ +// flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583 +// flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x + +declare module "flow-bin" { + declare module.exports: string; +} diff --git a/flow-typed/npm/fs-extra_vx.x.x.js b/flow-typed/npm/fs-extra_vx.x.x.js new file mode 100644 index 000000000..5606e57dd --- /dev/null +++ b/flow-typed/npm/fs-extra_vx.x.x.js @@ -0,0 +1,235 @@ +// flow-typed signature: 334664ea61c80495e1e70186a29a5fa7 +// flow-typed version: <>/fs-extra_v5.x.x/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'fs-extra' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'fs-extra' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'fs-extra/lib/copy-sync/copy-sync' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/copy-sync/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/copy/copy' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/copy/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/empty/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/ensure/file' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/ensure/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/ensure/link' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/ensure/symlink-paths' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/ensure/symlink-type' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/ensure/symlink' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/fs/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/json/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/json/jsonfile' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/json/output-json-sync' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/json/output-json' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/mkdirs/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/mkdirs/mkdirs-sync' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/mkdirs/mkdirs' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/mkdirs/win32' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/move-sync/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/move/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/output/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/path-exists/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/remove/index' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/remove/rimraf' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/util/assign' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/util/buffer' { + declare module.exports: any; +} + +declare module 'fs-extra/lib/util/utimes' { + declare module.exports: any; +} + +// Filename aliases +declare module 'fs-extra/lib/copy-sync/copy-sync.js' { + declare module.exports: $Exports<'fs-extra/lib/copy-sync/copy-sync'>; +} +declare module 'fs-extra/lib/copy-sync/index.js' { + declare module.exports: $Exports<'fs-extra/lib/copy-sync/index'>; +} +declare module 'fs-extra/lib/copy/copy.js' { + declare module.exports: $Exports<'fs-extra/lib/copy/copy'>; +} +declare module 'fs-extra/lib/copy/index.js' { + declare module.exports: $Exports<'fs-extra/lib/copy/index'>; +} +declare module 'fs-extra/lib/empty/index.js' { + declare module.exports: $Exports<'fs-extra/lib/empty/index'>; +} +declare module 'fs-extra/lib/ensure/file.js' { + declare module.exports: $Exports<'fs-extra/lib/ensure/file'>; +} +declare module 'fs-extra/lib/ensure/index.js' { + declare module.exports: $Exports<'fs-extra/lib/ensure/index'>; +} +declare module 'fs-extra/lib/ensure/link.js' { + declare module.exports: $Exports<'fs-extra/lib/ensure/link'>; +} +declare module 'fs-extra/lib/ensure/symlink-paths.js' { + declare module.exports: $Exports<'fs-extra/lib/ensure/symlink-paths'>; +} +declare module 'fs-extra/lib/ensure/symlink-type.js' { + declare module.exports: $Exports<'fs-extra/lib/ensure/symlink-type'>; +} +declare module 'fs-extra/lib/ensure/symlink.js' { + declare module.exports: $Exports<'fs-extra/lib/ensure/symlink'>; +} +declare module 'fs-extra/lib/fs/index.js' { + declare module.exports: $Exports<'fs-extra/lib/fs/index'>; +} +declare module 'fs-extra/lib/index.js' { + declare module.exports: $Exports<'fs-extra/lib/index'>; +} +declare module 'fs-extra/lib/json/index.js' { + declare module.exports: $Exports<'fs-extra/lib/json/index'>; +} +declare module 'fs-extra/lib/json/jsonfile.js' { + declare module.exports: $Exports<'fs-extra/lib/json/jsonfile'>; +} +declare module 'fs-extra/lib/json/output-json-sync.js' { + declare module.exports: $Exports<'fs-extra/lib/json/output-json-sync'>; +} +declare module 'fs-extra/lib/json/output-json.js' { + declare module.exports: $Exports<'fs-extra/lib/json/output-json'>; +} +declare module 'fs-extra/lib/mkdirs/index.js' { + declare module.exports: $Exports<'fs-extra/lib/mkdirs/index'>; +} +declare module 'fs-extra/lib/mkdirs/mkdirs-sync.js' { + declare module.exports: $Exports<'fs-extra/lib/mkdirs/mkdirs-sync'>; +} +declare module 'fs-extra/lib/mkdirs/mkdirs.js' { + declare module.exports: $Exports<'fs-extra/lib/mkdirs/mkdirs'>; +} +declare module 'fs-extra/lib/mkdirs/win32.js' { + declare module.exports: $Exports<'fs-extra/lib/mkdirs/win32'>; +} +declare module 'fs-extra/lib/move-sync/index.js' { + declare module.exports: $Exports<'fs-extra/lib/move-sync/index'>; +} +declare module 'fs-extra/lib/move/index.js' { + declare module.exports: $Exports<'fs-extra/lib/move/index'>; +} +declare module 'fs-extra/lib/output/index.js' { + declare module.exports: $Exports<'fs-extra/lib/output/index'>; +} +declare module 'fs-extra/lib/path-exists/index.js' { + declare module.exports: $Exports<'fs-extra/lib/path-exists/index'>; +} +declare module 'fs-extra/lib/remove/index.js' { + declare module.exports: $Exports<'fs-extra/lib/remove/index'>; +} +declare module 'fs-extra/lib/remove/rimraf.js' { + declare module.exports: $Exports<'fs-extra/lib/remove/rimraf'>; +} +declare module 'fs-extra/lib/util/assign.js' { + declare module.exports: $Exports<'fs-extra/lib/util/assign'>; +} +declare module 'fs-extra/lib/util/buffer.js' { + declare module.exports: $Exports<'fs-extra/lib/util/buffer'>; +} +declare module 'fs-extra/lib/util/utimes.js' { + declare module.exports: $Exports<'fs-extra/lib/util/utimes'>; +} diff --git a/flow-typed/npm/graphiql_vx.x.x.js b/flow-typed/npm/graphiql_vx.x.x.js new file mode 100644 index 000000000..cdcee38bd --- /dev/null +++ b/flow-typed/npm/graphiql_vx.x.x.js @@ -0,0 +1,277 @@ +// flow-typed signature: 69ddad32e70eb9e1eb79e7ce2017117b +// flow-typed version: <>/graphiql_v0.11.10/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'graphiql' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'graphiql' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'graphiql/dist/components/DocExplorer' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/DocExplorer/Argument' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/DocExplorer/DefaultValue' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/DocExplorer/FieldDoc' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/DocExplorer/MarkdownContent' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/DocExplorer/SchemaDoc' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/DocExplorer/SearchBox' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/DocExplorer/SearchResults' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/DocExplorer/TypeDoc' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/DocExplorer/TypeLink' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/ExecuteButton' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/GraphiQL' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/HistoryQuery' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/QueryEditor' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/QueryHistory' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/ResultViewer' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/ToolbarButton' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/ToolbarGroup' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/ToolbarMenu' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/ToolbarSelect' { + declare module.exports: any; +} + +declare module 'graphiql/dist/components/VariableEditor' { + declare module.exports: any; +} + +declare module 'graphiql/dist/index' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/CodeMirrorSizer' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/debounce' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/elementPosition' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/fillLeafs' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/find' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/getQueryFacts' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/getSelectedOperationName' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/introspectionQueries' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/normalizeWhitespace' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/onHasCompletion' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/QueryStore' { + declare module.exports: any; +} + +declare module 'graphiql/dist/utility/StorageAPI' { + declare module.exports: any; +} + +declare module 'graphiql/graphiql' { + declare module.exports: any; +} + +declare module 'graphiql/graphiql.min' { + declare module.exports: any; +} + +// Filename aliases +declare module 'graphiql/dist/components/DocExplorer.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer'>; +} +declare module 'graphiql/dist/components/DocExplorer/Argument.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer/Argument'>; +} +declare module 'graphiql/dist/components/DocExplorer/DefaultValue.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer/DefaultValue'>; +} +declare module 'graphiql/dist/components/DocExplorer/FieldDoc.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer/FieldDoc'>; +} +declare module 'graphiql/dist/components/DocExplorer/MarkdownContent.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer/MarkdownContent'>; +} +declare module 'graphiql/dist/components/DocExplorer/SchemaDoc.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer/SchemaDoc'>; +} +declare module 'graphiql/dist/components/DocExplorer/SearchBox.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer/SearchBox'>; +} +declare module 'graphiql/dist/components/DocExplorer/SearchResults.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer/SearchResults'>; +} +declare module 'graphiql/dist/components/DocExplorer/TypeDoc.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer/TypeDoc'>; +} +declare module 'graphiql/dist/components/DocExplorer/TypeLink.js' { + declare module.exports: $Exports<'graphiql/dist/components/DocExplorer/TypeLink'>; +} +declare module 'graphiql/dist/components/ExecuteButton.js' { + declare module.exports: $Exports<'graphiql/dist/components/ExecuteButton'>; +} +declare module 'graphiql/dist/components/GraphiQL.js' { + declare module.exports: $Exports<'graphiql/dist/components/GraphiQL'>; +} +declare module 'graphiql/dist/components/HistoryQuery.js' { + declare module.exports: $Exports<'graphiql/dist/components/HistoryQuery'>; +} +declare module 'graphiql/dist/components/QueryEditor.js' { + declare module.exports: $Exports<'graphiql/dist/components/QueryEditor'>; +} +declare module 'graphiql/dist/components/QueryHistory.js' { + declare module.exports: $Exports<'graphiql/dist/components/QueryHistory'>; +} +declare module 'graphiql/dist/components/ResultViewer.js' { + declare module.exports: $Exports<'graphiql/dist/components/ResultViewer'>; +} +declare module 'graphiql/dist/components/ToolbarButton.js' { + declare module.exports: $Exports<'graphiql/dist/components/ToolbarButton'>; +} +declare module 'graphiql/dist/components/ToolbarGroup.js' { + declare module.exports: $Exports<'graphiql/dist/components/ToolbarGroup'>; +} +declare module 'graphiql/dist/components/ToolbarMenu.js' { + declare module.exports: $Exports<'graphiql/dist/components/ToolbarMenu'>; +} +declare module 'graphiql/dist/components/ToolbarSelect.js' { + declare module.exports: $Exports<'graphiql/dist/components/ToolbarSelect'>; +} +declare module 'graphiql/dist/components/VariableEditor.js' { + declare module.exports: $Exports<'graphiql/dist/components/VariableEditor'>; +} +declare module 'graphiql/dist/index.js' { + declare module.exports: $Exports<'graphiql/dist/index'>; +} +declare module 'graphiql/dist/utility/CodeMirrorSizer.js' { + declare module.exports: $Exports<'graphiql/dist/utility/CodeMirrorSizer'>; +} +declare module 'graphiql/dist/utility/debounce.js' { + declare module.exports: $Exports<'graphiql/dist/utility/debounce'>; +} +declare module 'graphiql/dist/utility/elementPosition.js' { + declare module.exports: $Exports<'graphiql/dist/utility/elementPosition'>; +} +declare module 'graphiql/dist/utility/fillLeafs.js' { + declare module.exports: $Exports<'graphiql/dist/utility/fillLeafs'>; +} +declare module 'graphiql/dist/utility/find.js' { + declare module.exports: $Exports<'graphiql/dist/utility/find'>; +} +declare module 'graphiql/dist/utility/getQueryFacts.js' { + declare module.exports: $Exports<'graphiql/dist/utility/getQueryFacts'>; +} +declare module 'graphiql/dist/utility/getSelectedOperationName.js' { + declare module.exports: $Exports<'graphiql/dist/utility/getSelectedOperationName'>; +} +declare module 'graphiql/dist/utility/introspectionQueries.js' { + declare module.exports: $Exports<'graphiql/dist/utility/introspectionQueries'>; +} +declare module 'graphiql/dist/utility/normalizeWhitespace.js' { + declare module.exports: $Exports<'graphiql/dist/utility/normalizeWhitespace'>; +} +declare module 'graphiql/dist/utility/onHasCompletion.js' { + declare module.exports: $Exports<'graphiql/dist/utility/onHasCompletion'>; +} +declare module 'graphiql/dist/utility/QueryStore.js' { + declare module.exports: $Exports<'graphiql/dist/utility/QueryStore'>; +} +declare module 'graphiql/dist/utility/StorageAPI.js' { + declare module.exports: $Exports<'graphiql/dist/utility/StorageAPI'>; +} +declare module 'graphiql/graphiql.js' { + declare module.exports: $Exports<'graphiql/graphiql'>; +} +declare module 'graphiql/graphiql.min.js' { + declare module.exports: $Exports<'graphiql/graphiql.min'>; +} diff --git a/flow-typed/npm/graphql_vx.x.x.js b/flow-typed/npm/graphql_vx.x.x.js new file mode 100644 index 000000000..3326b36cd --- /dev/null +++ b/flow-typed/npm/graphql_vx.x.x.js @@ -0,0 +1,633 @@ +// flow-typed signature: 78a3b0630930ae254dcef08544af115d +// flow-typed version: <>/graphql_v^0.11.7/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'graphql' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'graphql' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'graphql/error/formatError' { + declare module.exports: any; +} + +declare module 'graphql/error/GraphQLError' { + declare module.exports: any; +} + +declare module 'graphql/error/index' { + declare module.exports: any; +} + +declare module 'graphql/error/locatedError' { + declare module.exports: any; +} + +declare module 'graphql/error/syntaxError' { + declare module.exports: any; +} + +declare module 'graphql/execution/execute' { + declare module.exports: any; +} + +declare module 'graphql/execution/index' { + declare module.exports: any; +} + +declare module 'graphql/execution/values' { + declare module.exports: any; +} + +declare module 'graphql/graphql' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/dedent' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/find' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/invariant' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/isInvalid' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/isNullish' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/keyMap' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/keyValMap' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/ObjMap' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/quotedOrList' { + declare module.exports: any; +} + +declare module 'graphql/jsutils/suggestionList' { + declare module.exports: any; +} + +declare module 'graphql/language/ast' { + declare module.exports: any; +} + +declare module 'graphql/language/index' { + declare module.exports: any; +} + +declare module 'graphql/language/kinds' { + declare module.exports: any; +} + +declare module 'graphql/language/lexer' { + declare module.exports: any; +} + +declare module 'graphql/language/location' { + declare module.exports: any; +} + +declare module 'graphql/language/parser' { + declare module.exports: any; +} + +declare module 'graphql/language/printer' { + declare module.exports: any; +} + +declare module 'graphql/language/source' { + declare module.exports: any; +} + +declare module 'graphql/language/visitor' { + declare module.exports: any; +} + +declare module 'graphql/subscription/asyncIteratorReject' { + declare module.exports: any; +} + +declare module 'graphql/subscription/index' { + declare module.exports: any; +} + +declare module 'graphql/subscription/mapAsyncIterator' { + declare module.exports: any; +} + +declare module 'graphql/subscription/subscribe' { + declare module.exports: any; +} + +declare module 'graphql/type/definition' { + declare module.exports: any; +} + +declare module 'graphql/type/directives' { + declare module.exports: any; +} + +declare module 'graphql/type/index' { + declare module.exports: any; +} + +declare module 'graphql/type/introspection' { + declare module.exports: any; +} + +declare module 'graphql/type/scalars' { + declare module.exports: any; +} + +declare module 'graphql/type/schema' { + declare module.exports: any; +} + +declare module 'graphql/utilities/assertValidName' { + declare module.exports: any; +} + +declare module 'graphql/utilities/astFromValue' { + declare module.exports: any; +} + +declare module 'graphql/utilities/buildASTSchema' { + declare module.exports: any; +} + +declare module 'graphql/utilities/buildClientSchema' { + declare module.exports: any; +} + +declare module 'graphql/utilities/concatAST' { + declare module.exports: any; +} + +declare module 'graphql/utilities/extendSchema' { + declare module.exports: any; +} + +declare module 'graphql/utilities/findBreakingChanges' { + declare module.exports: any; +} + +declare module 'graphql/utilities/findDeprecatedUsages' { + declare module.exports: any; +} + +declare module 'graphql/utilities/getOperationAST' { + declare module.exports: any; +} + +declare module 'graphql/utilities/index' { + declare module.exports: any; +} + +declare module 'graphql/utilities/introspectionQuery' { + declare module.exports: any; +} + +declare module 'graphql/utilities/isValidJSValue' { + declare module.exports: any; +} + +declare module 'graphql/utilities/isValidLiteralValue' { + declare module.exports: any; +} + +declare module 'graphql/utilities/schemaPrinter' { + declare module.exports: any; +} + +declare module 'graphql/utilities/separateOperations' { + declare module.exports: any; +} + +declare module 'graphql/utilities/typeComparators' { + declare module.exports: any; +} + +declare module 'graphql/utilities/typeFromAST' { + declare module.exports: any; +} + +declare module 'graphql/utilities/TypeInfo' { + declare module.exports: any; +} + +declare module 'graphql/utilities/valueFromAST' { + declare module.exports: any; +} + +declare module 'graphql/validation/index' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/ArgumentsOfCorrectType' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/DefaultValuesOfCorrectType' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/FieldsOnCorrectType' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/FragmentsOnCompositeTypes' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/KnownArgumentNames' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/KnownDirectives' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/KnownFragmentNames' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/KnownTypeNames' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/LoneAnonymousOperation' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/NoFragmentCycles' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/NoUndefinedVariables' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/NoUnusedFragments' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/NoUnusedVariables' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/OverlappingFieldsCanBeMerged' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/PossibleFragmentSpreads' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/ProvidedNonNullArguments' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/ScalarLeafs' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/SingleFieldSubscriptions' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/UniqueArgumentNames' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/UniqueDirectivesPerLocation' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/UniqueFragmentNames' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/UniqueInputFieldNames' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/UniqueOperationNames' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/UniqueVariableNames' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/VariablesAreInputTypes' { + declare module.exports: any; +} + +declare module 'graphql/validation/rules/VariablesInAllowedPosition' { + declare module.exports: any; +} + +declare module 'graphql/validation/specifiedRules' { + declare module.exports: any; +} + +declare module 'graphql/validation/validate' { + declare module.exports: any; +} + +// Filename aliases +declare module 'graphql/error/formatError.js' { + declare module.exports: $Exports<'graphql/error/formatError'>; +} +declare module 'graphql/error/GraphQLError.js' { + declare module.exports: $Exports<'graphql/error/GraphQLError'>; +} +declare module 'graphql/error/index.js' { + declare module.exports: $Exports<'graphql/error/index'>; +} +declare module 'graphql/error/locatedError.js' { + declare module.exports: $Exports<'graphql/error/locatedError'>; +} +declare module 'graphql/error/syntaxError.js' { + declare module.exports: $Exports<'graphql/error/syntaxError'>; +} +declare module 'graphql/execution/execute.js' { + declare module.exports: $Exports<'graphql/execution/execute'>; +} +declare module 'graphql/execution/index.js' { + declare module.exports: $Exports<'graphql/execution/index'>; +} +declare module 'graphql/execution/values.js' { + declare module.exports: $Exports<'graphql/execution/values'>; +} +declare module 'graphql/graphql.js' { + declare module.exports: $Exports<'graphql/graphql'>; +} +declare module 'graphql/index' { + declare module.exports: $Exports<'graphql'>; +} +declare module 'graphql/index.js' { + declare module.exports: $Exports<'graphql'>; +} +declare module 'graphql/jsutils/dedent.js' { + declare module.exports: $Exports<'graphql/jsutils/dedent'>; +} +declare module 'graphql/jsutils/find.js' { + declare module.exports: $Exports<'graphql/jsutils/find'>; +} +declare module 'graphql/jsutils/invariant.js' { + declare module.exports: $Exports<'graphql/jsutils/invariant'>; +} +declare module 'graphql/jsutils/isInvalid.js' { + declare module.exports: $Exports<'graphql/jsutils/isInvalid'>; +} +declare module 'graphql/jsutils/isNullish.js' { + declare module.exports: $Exports<'graphql/jsutils/isNullish'>; +} +declare module 'graphql/jsutils/keyMap.js' { + declare module.exports: $Exports<'graphql/jsutils/keyMap'>; +} +declare module 'graphql/jsutils/keyValMap.js' { + declare module.exports: $Exports<'graphql/jsutils/keyValMap'>; +} +declare module 'graphql/jsutils/ObjMap.js' { + declare module.exports: $Exports<'graphql/jsutils/ObjMap'>; +} +declare module 'graphql/jsutils/quotedOrList.js' { + declare module.exports: $Exports<'graphql/jsutils/quotedOrList'>; +} +declare module 'graphql/jsutils/suggestionList.js' { + declare module.exports: $Exports<'graphql/jsutils/suggestionList'>; +} +declare module 'graphql/language/ast.js' { + declare module.exports: $Exports<'graphql/language/ast'>; +} +declare module 'graphql/language/index.js' { + declare module.exports: $Exports<'graphql/language/index'>; +} +declare module 'graphql/language/kinds.js' { + declare module.exports: $Exports<'graphql/language/kinds'>; +} +declare module 'graphql/language/lexer.js' { + declare module.exports: $Exports<'graphql/language/lexer'>; +} +declare module 'graphql/language/location.js' { + declare module.exports: $Exports<'graphql/language/location'>; +} +declare module 'graphql/language/parser.js' { + declare module.exports: $Exports<'graphql/language/parser'>; +} +declare module 'graphql/language/printer.js' { + declare module.exports: $Exports<'graphql/language/printer'>; +} +declare module 'graphql/language/source.js' { + declare module.exports: $Exports<'graphql/language/source'>; +} +declare module 'graphql/language/visitor.js' { + declare module.exports: $Exports<'graphql/language/visitor'>; +} +declare module 'graphql/subscription/asyncIteratorReject.js' { + declare module.exports: $Exports<'graphql/subscription/asyncIteratorReject'>; +} +declare module 'graphql/subscription/index.js' { + declare module.exports: $Exports<'graphql/subscription/index'>; +} +declare module 'graphql/subscription/mapAsyncIterator.js' { + declare module.exports: $Exports<'graphql/subscription/mapAsyncIterator'>; +} +declare module 'graphql/subscription/subscribe.js' { + declare module.exports: $Exports<'graphql/subscription/subscribe'>; +} +declare module 'graphql/type/definition.js' { + declare module.exports: $Exports<'graphql/type/definition'>; +} +declare module 'graphql/type/directives.js' { + declare module.exports: $Exports<'graphql/type/directives'>; +} +declare module 'graphql/type/index.js' { + declare module.exports: $Exports<'graphql/type/index'>; +} +declare module 'graphql/type/introspection.js' { + declare module.exports: $Exports<'graphql/type/introspection'>; +} +declare module 'graphql/type/scalars.js' { + declare module.exports: $Exports<'graphql/type/scalars'>; +} +declare module 'graphql/type/schema.js' { + declare module.exports: $Exports<'graphql/type/schema'>; +} +declare module 'graphql/utilities/assertValidName.js' { + declare module.exports: $Exports<'graphql/utilities/assertValidName'>; +} +declare module 'graphql/utilities/astFromValue.js' { + declare module.exports: $Exports<'graphql/utilities/astFromValue'>; +} +declare module 'graphql/utilities/buildASTSchema.js' { + declare module.exports: $Exports<'graphql/utilities/buildASTSchema'>; +} +declare module 'graphql/utilities/buildClientSchema.js' { + declare module.exports: $Exports<'graphql/utilities/buildClientSchema'>; +} +declare module 'graphql/utilities/concatAST.js' { + declare module.exports: $Exports<'graphql/utilities/concatAST'>; +} +declare module 'graphql/utilities/extendSchema.js' { + declare module.exports: $Exports<'graphql/utilities/extendSchema'>; +} +declare module 'graphql/utilities/findBreakingChanges.js' { + declare module.exports: $Exports<'graphql/utilities/findBreakingChanges'>; +} +declare module 'graphql/utilities/findDeprecatedUsages.js' { + declare module.exports: $Exports<'graphql/utilities/findDeprecatedUsages'>; +} +declare module 'graphql/utilities/getOperationAST.js' { + declare module.exports: $Exports<'graphql/utilities/getOperationAST'>; +} +declare module 'graphql/utilities/index.js' { + declare module.exports: $Exports<'graphql/utilities/index'>; +} +declare module 'graphql/utilities/introspectionQuery.js' { + declare module.exports: $Exports<'graphql/utilities/introspectionQuery'>; +} +declare module 'graphql/utilities/isValidJSValue.js' { + declare module.exports: $Exports<'graphql/utilities/isValidJSValue'>; +} +declare module 'graphql/utilities/isValidLiteralValue.js' { + declare module.exports: $Exports<'graphql/utilities/isValidLiteralValue'>; +} +declare module 'graphql/utilities/schemaPrinter.js' { + declare module.exports: $Exports<'graphql/utilities/schemaPrinter'>; +} +declare module 'graphql/utilities/separateOperations.js' { + declare module.exports: $Exports<'graphql/utilities/separateOperations'>; +} +declare module 'graphql/utilities/typeComparators.js' { + declare module.exports: $Exports<'graphql/utilities/typeComparators'>; +} +declare module 'graphql/utilities/typeFromAST.js' { + declare module.exports: $Exports<'graphql/utilities/typeFromAST'>; +} +declare module 'graphql/utilities/TypeInfo.js' { + declare module.exports: $Exports<'graphql/utilities/TypeInfo'>; +} +declare module 'graphql/utilities/valueFromAST.js' { + declare module.exports: $Exports<'graphql/utilities/valueFromAST'>; +} +declare module 'graphql/validation/index.js' { + declare module.exports: $Exports<'graphql/validation/index'>; +} +declare module 'graphql/validation/rules/ArgumentsOfCorrectType.js' { + declare module.exports: $Exports<'graphql/validation/rules/ArgumentsOfCorrectType'>; +} +declare module 'graphql/validation/rules/DefaultValuesOfCorrectType.js' { + declare module.exports: $Exports<'graphql/validation/rules/DefaultValuesOfCorrectType'>; +} +declare module 'graphql/validation/rules/FieldsOnCorrectType.js' { + declare module.exports: $Exports<'graphql/validation/rules/FieldsOnCorrectType'>; +} +declare module 'graphql/validation/rules/FragmentsOnCompositeTypes.js' { + declare module.exports: $Exports<'graphql/validation/rules/FragmentsOnCompositeTypes'>; +} +declare module 'graphql/validation/rules/KnownArgumentNames.js' { + declare module.exports: $Exports<'graphql/validation/rules/KnownArgumentNames'>; +} +declare module 'graphql/validation/rules/KnownDirectives.js' { + declare module.exports: $Exports<'graphql/validation/rules/KnownDirectives'>; +} +declare module 'graphql/validation/rules/KnownFragmentNames.js' { + declare module.exports: $Exports<'graphql/validation/rules/KnownFragmentNames'>; +} +declare module 'graphql/validation/rules/KnownTypeNames.js' { + declare module.exports: $Exports<'graphql/validation/rules/KnownTypeNames'>; +} +declare module 'graphql/validation/rules/LoneAnonymousOperation.js' { + declare module.exports: $Exports<'graphql/validation/rules/LoneAnonymousOperation'>; +} +declare module 'graphql/validation/rules/NoFragmentCycles.js' { + declare module.exports: $Exports<'graphql/validation/rules/NoFragmentCycles'>; +} +declare module 'graphql/validation/rules/NoUndefinedVariables.js' { + declare module.exports: $Exports<'graphql/validation/rules/NoUndefinedVariables'>; +} +declare module 'graphql/validation/rules/NoUnusedFragments.js' { + declare module.exports: $Exports<'graphql/validation/rules/NoUnusedFragments'>; +} +declare module 'graphql/validation/rules/NoUnusedVariables.js' { + declare module.exports: $Exports<'graphql/validation/rules/NoUnusedVariables'>; +} +declare module 'graphql/validation/rules/OverlappingFieldsCanBeMerged.js' { + declare module.exports: $Exports<'graphql/validation/rules/OverlappingFieldsCanBeMerged'>; +} +declare module 'graphql/validation/rules/PossibleFragmentSpreads.js' { + declare module.exports: $Exports<'graphql/validation/rules/PossibleFragmentSpreads'>; +} +declare module 'graphql/validation/rules/ProvidedNonNullArguments.js' { + declare module.exports: $Exports<'graphql/validation/rules/ProvidedNonNullArguments'>; +} +declare module 'graphql/validation/rules/ScalarLeafs.js' { + declare module.exports: $Exports<'graphql/validation/rules/ScalarLeafs'>; +} +declare module 'graphql/validation/rules/SingleFieldSubscriptions.js' { + declare module.exports: $Exports<'graphql/validation/rules/SingleFieldSubscriptions'>; +} +declare module 'graphql/validation/rules/UniqueArgumentNames.js' { + declare module.exports: $Exports<'graphql/validation/rules/UniqueArgumentNames'>; +} +declare module 'graphql/validation/rules/UniqueDirectivesPerLocation.js' { + declare module.exports: $Exports<'graphql/validation/rules/UniqueDirectivesPerLocation'>; +} +declare module 'graphql/validation/rules/UniqueFragmentNames.js' { + declare module.exports: $Exports<'graphql/validation/rules/UniqueFragmentNames'>; +} +declare module 'graphql/validation/rules/UniqueInputFieldNames.js' { + declare module.exports: $Exports<'graphql/validation/rules/UniqueInputFieldNames'>; +} +declare module 'graphql/validation/rules/UniqueOperationNames.js' { + declare module.exports: $Exports<'graphql/validation/rules/UniqueOperationNames'>; +} +declare module 'graphql/validation/rules/UniqueVariableNames.js' { + declare module.exports: $Exports<'graphql/validation/rules/UniqueVariableNames'>; +} +declare module 'graphql/validation/rules/VariablesAreInputTypes.js' { + declare module.exports: $Exports<'graphql/validation/rules/VariablesAreInputTypes'>; +} +declare module 'graphql/validation/rules/VariablesInAllowedPosition.js' { + declare module.exports: $Exports<'graphql/validation/rules/VariablesInAllowedPosition'>; +} +declare module 'graphql/validation/specifiedRules.js' { + declare module.exports: $Exports<'graphql/validation/specifiedRules'>; +} +declare module 'graphql/validation/validate.js' { + declare module.exports: $Exports<'graphql/validation/validate'>; +} diff --git a/flow-typed/npm/invariant_v2.x.x.js b/flow-typed/npm/invariant_v2.x.x.js new file mode 100644 index 000000000..2b5894bdd --- /dev/null +++ b/flow-typed/npm/invariant_v2.x.x.js @@ -0,0 +1,6 @@ +// flow-typed signature: 41a21b97ad4a7c01c4caf3a8b9382354 +// flow-typed version: b43dff3e0e/invariant_v2.x.x/flow_>=v0.33.x + +declare module invariant { + declare var exports: (condition: boolean, message: string) => void; +} diff --git a/flow-typed/npm/jest_v21.x.x.js b/flow-typed/npm/jest_v21.x.x.js new file mode 100644 index 000000000..daaeda70e --- /dev/null +++ b/flow-typed/npm/jest_v21.x.x.js @@ -0,0 +1,584 @@ +// flow-typed signature: 107cf7068b8835594e97f938e8848244 +// flow-typed version: 8b4dd96654/jest_v21.x.x/flow_>=v0.39.x + +type JestMockFn, TReturn> = { + (...args: TArguments): TReturn, + /** + * An object for introspecting mock calls + */ + mock: { + /** + * An array that represents all calls that have been made into this mock + * function. Each call is represented by an array of arguments that were + * passed during the call. + */ + calls: Array, + /** + * An array that contains all the object instances that have been + * instantiated from this mock function. + */ + instances: Array + }, + /** + * Resets all information stored in the mockFn.mock.calls and + * mockFn.mock.instances arrays. Often this is useful when you want to clean + * up a mock's usage data between two assertions. + */ + mockClear(): void, + /** + * Resets all information stored in the mock. This is useful when you want to + * completely restore a mock back to its initial state. + */ + mockReset(): void, + /** + * Removes the mock and restores the initial implementation. This is useful + * when you want to mock functions in certain test cases and restore the + * original implementation in others. Beware that mockFn.mockRestore only + * works when mock was created with jest.spyOn. Thus you have to take care of + * restoration yourself when manually assigning jest.fn(). + */ + mockRestore(): void, + /** + * Accepts a function that should be used as the implementation of the mock. + * The mock itself will still record all calls that go into and instances + * that come from itself -- the only difference is that the implementation + * will also be executed when the mock is called. + */ + mockImplementation( + fn: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Accepts a function that will be used as an implementation of the mock for + * one call to the mocked function. Can be chained so that multiple function + * calls produce different results. + */ + mockImplementationOnce( + fn: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Just a simple sugar function for returning `this` + */ + mockReturnThis(): void, + /** + * Deprecated: use jest.fn(() => value) instead + */ + mockReturnValue(value: TReturn): JestMockFn, + /** + * Sugar for only returning a value once inside your mock + */ + mockReturnValueOnce(value: TReturn): JestMockFn +}; + +type JestAsymmetricEqualityType = { + /** + * A custom Jasmine equality tester + */ + asymmetricMatch(value: mixed): boolean +}; + +type JestCallsType = { + allArgs(): mixed, + all(): mixed, + any(): boolean, + count(): number, + first(): mixed, + mostRecent(): mixed, + reset(): void +}; + +type JestClockType = { + install(): void, + mockDate(date: Date): void, + tick(milliseconds?: number): void, + uninstall(): void +}; + +type JestMatcherResult = { + message?: string | (() => string), + pass: boolean +}; + +type JestMatcher = (actual: any, expected: any) => JestMatcherResult; + +type JestPromiseType = { + /** + * Use rejects to unwrap the reason of a rejected promise so any other + * matcher can be chained. If the promise is fulfilled the assertion fails. + */ + rejects: JestExpectType, + /** + * Use resolves to unwrap the value of a fulfilled promise so any other + * matcher can be chained. If the promise is rejected the assertion fails. + */ + resolves: JestExpectType +}; + +/** + * Plugin: jest-enzyme + */ +type EnzymeMatchersType = { + toBeChecked(): void, + toBeDisabled(): void, + toBeEmpty(): void, + toBePresent(): void, + toContainReact(element: React$Element): void, + toHaveClassName(className: string): void, + toHaveHTML(html: string): void, + toHaveProp(propKey: string, propValue?: any): void, + toHaveRef(refName: string): void, + toHaveState(stateKey: string, stateValue?: any): void, + toHaveStyle(styleKey: string, styleValue?: any): void, + toHaveTagName(tagName: string): void, + toHaveText(text: string): void, + toIncludeText(text: string): void, + toHaveValue(value: any): void, + toMatchElement(element: React$Element): void, + toMatchSelector(selector: string): void +}; + +type JestExpectType = { + not: JestExpectType & EnzymeMatchersType, + /** + * If you have a mock function, you can use .lastCalledWith to test what + * arguments it was last called with. + */ + lastCalledWith(...args: Array): void, + /** + * toBe just checks that a value is what you expect. It uses === to check + * strict equality. + */ + toBe(value: any): void, + /** + * Use .toHaveBeenCalled to ensure that a mock function got called. + */ + toBeCalled(): void, + /** + * Use .toBeCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toBeCalledWith(...args: Array): void, + /** + * Using exact equality with floating point numbers is a bad idea. Rounding + * means that intuitive things fail. + */ + toBeCloseTo(num: number, delta: any): void, + /** + * Use .toBeDefined to check that a variable is not undefined. + */ + toBeDefined(): void, + /** + * Use .toBeFalsy when you don't care what a value is, you just want to + * ensure a value is false in a boolean context. + */ + toBeFalsy(): void, + /** + * To compare floating point numbers, you can use toBeGreaterThan. + */ + toBeGreaterThan(number: number): void, + /** + * To compare floating point numbers, you can use toBeGreaterThanOrEqual. + */ + toBeGreaterThanOrEqual(number: number): void, + /** + * To compare floating point numbers, you can use toBeLessThan. + */ + toBeLessThan(number: number): void, + /** + * To compare floating point numbers, you can use toBeLessThanOrEqual. + */ + toBeLessThanOrEqual(number: number): void, + /** + * Use .toBeInstanceOf(Class) to check that an object is an instance of a + * class. + */ + toBeInstanceOf(cls: Class<*>): void, + /** + * .toBeNull() is the same as .toBe(null) but the error messages are a bit + * nicer. + */ + toBeNull(): void, + /** + * Use .toBeTruthy when you don't care what a value is, you just want to + * ensure a value is true in a boolean context. + */ + toBeTruthy(): void, + /** + * Use .toBeUndefined to check that a variable is undefined. + */ + toBeUndefined(): void, + /** + * Use .toContain when you want to check that an item is in a list. For + * testing the items in the list, this uses ===, a strict equality check. + */ + toContain(item: any): void, + /** + * Use .toContainEqual when you want to check that an item is in a list. For + * testing the items in the list, this matcher recursively checks the + * equality of all fields, rather than checking for object identity. + */ + toContainEqual(item: any): void, + /** + * Use .toEqual when you want to check that two objects have the same value. + * This matcher recursively checks the equality of all fields, rather than + * checking for object identity. + */ + toEqual(value: any): void, + /** + * Use .toHaveBeenCalled to ensure that a mock function got called. + */ + toHaveBeenCalled(): void, + /** + * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact + * number of times. + */ + toHaveBeenCalledTimes(number: number): void, + /** + * Use .toHaveBeenCalledWith to ensure that a mock function was called with + * specific arguments. + */ + toHaveBeenCalledWith(...args: Array): void, + /** + * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called + * with specific arguments. + */ + toHaveBeenLastCalledWith(...args: Array): void, + /** + * Check that an object has a .length property and it is set to a certain + * numeric value. + */ + toHaveLength(number: number): void, + /** + * + */ + toHaveProperty(propPath: string, value?: any): void, + /** + * Use .toMatch to check that a string matches a regular expression or string. + */ + toMatch(regexpOrString: RegExp | string): void, + /** + * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. + */ + toMatchObject(object: Object | Array): void, + /** + * This ensures that a React component matches the most recent snapshot. + */ + toMatchSnapshot(name?: string): void, + /** + * Use .toThrow to test that a function throws when it is called. + * If you want to test that a specific error gets thrown, you can provide an + * argument to toThrow. The argument can be a string for the error message, + * a class for the error, or a regex that should match the error. + * + * Alias: .toThrowError + */ + toThrow(message?: string | Error | Class | RegExp): void, + toThrowError(message?: string | Error | Class | RegExp): void, + /** + * Use .toThrowErrorMatchingSnapshot to test that a function throws a error + * matching the most recent snapshot when it is called. + */ + toThrowErrorMatchingSnapshot(): void +}; + +type JestObjectType = { + /** + * Disables automatic mocking in the module loader. + * + * After this method is called, all `require()`s will return the real + * versions of each module (rather than a mocked version). + */ + disableAutomock(): JestObjectType, + /** + * An un-hoisted version of disableAutomock + */ + autoMockOff(): JestObjectType, + /** + * Enables automatic mocking in the module loader. + */ + enableAutomock(): JestObjectType, + /** + * An un-hoisted version of enableAutomock + */ + autoMockOn(): JestObjectType, + /** + * Clears the mock.calls and mock.instances properties of all mocks. + * Equivalent to calling .mockClear() on every mocked function. + */ + clearAllMocks(): JestObjectType, + /** + * Resets the state of all mocks. Equivalent to calling .mockReset() on every + * mocked function. + */ + resetAllMocks(): JestObjectType, + /** + * Removes any pending timers from the timer system. + */ + clearAllTimers(): void, + /** + * The same as `mock` but not moved to the top of the expectation by + * babel-jest. + */ + doMock(moduleName: string, moduleFactory?: any): JestObjectType, + /** + * The same as `unmock` but not moved to the top of the expectation by + * babel-jest. + */ + dontMock(moduleName: string): JestObjectType, + /** + * Returns a new, unused mock function. Optionally takes a mock + * implementation. + */ + fn, TReturn>( + implementation?: (...args: TArguments) => TReturn + ): JestMockFn, + /** + * Determines if the given function is a mocked function. + */ + isMockFunction(fn: Function): boolean, + /** + * Given the name of a module, use the automatic mocking system to generate a + * mocked version of the module for you. + */ + genMockFromModule(moduleName: string): any, + /** + * Mocks a module with an auto-mocked version when it is being required. + * + * The second argument can be used to specify an explicit module factory that + * is being run instead of using Jest's automocking feature. + * + * The third argument can be used to create virtual mocks -- mocks of modules + * that don't exist anywhere in the system. + */ + mock( + moduleName: string, + moduleFactory?: any, + options?: Object + ): JestObjectType, + /** + * Returns the actual module instead of a mock, bypassing all checks on + * whether the module should receive a mock implementation or not. + */ + requireActual(moduleName: string): any, + /** + * Returns a mock module instead of the actual module, bypassing all checks + * on whether the module should be required normally or not. + */ + requireMock(moduleName: string): any, + /** + * Resets the module registry - the cache of all required modules. This is + * useful to isolate modules where local state might conflict between tests. + */ + resetModules(): JestObjectType, + /** + * Exhausts the micro-task queue (usually interfaced in node via + * process.nextTick). + */ + runAllTicks(): void, + /** + * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), + * setInterval(), and setImmediate()). + */ + runAllTimers(): void, + /** + * Exhausts all tasks queued by setImmediate(). + */ + runAllImmediates(): void, + /** + * Executes only the macro task queue (i.e. all tasks queued by setTimeout() + * or setInterval() and setImmediate()). + */ + runTimersToTime(msToRun: number): void, + /** + * Executes only the macro-tasks that are currently pending (i.e., only the + * tasks that have been queued by setTimeout() or setInterval() up to this + * point) + */ + runOnlyPendingTimers(): void, + /** + * Explicitly supplies the mock object that the module system should return + * for the specified module. Note: It is recommended to use jest.mock() + * instead. + */ + setMock(moduleName: string, moduleExports: any): JestObjectType, + /** + * Indicates that the module system should never return a mocked version of + * the specified module from require() (e.g. that it should always return the + * real module). + */ + unmock(moduleName: string): JestObjectType, + /** + * Instructs Jest to use fake versions of the standard timer functions + * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, + * setImmediate and clearImmediate). + */ + useFakeTimers(): JestObjectType, + /** + * Instructs Jest to use the real versions of the standard timer functions. + */ + useRealTimers(): JestObjectType, + /** + * Creates a mock function similar to jest.fn but also tracks calls to + * object[methodName]. + */ + spyOn(object: Object, methodName: string): JestMockFn, + /** + * Set the default timeout interval for tests and before/after hooks in milliseconds. + * Note: The default timeout interval is 5 seconds if this method is not called. + */ + setTimeout(timeout: number): JestObjectType +}; + +type JestSpyType = { + calls: JestCallsType +}; + +/** Runs this function after every test inside this context */ +declare function afterEach( + fn: (done: () => void) => ?Promise, + timeout?: number +): void; +/** Runs this function before every test inside this context */ +declare function beforeEach( + fn: (done: () => void) => ?Promise, + timeout?: number +): void; +/** Runs this function after all tests have finished inside this context */ +declare function afterAll( + fn: (done: () => void) => ?Promise, + timeout?: number +): void; +/** Runs this function before any tests have started inside this context */ +declare function beforeAll( + fn: (done: () => void) => ?Promise, + timeout?: number +): void; + +/** A context for grouping tests together */ +declare var describe: { + /** + * Creates a block that groups together several related tests in one "test suite" + */ + (name: string, fn: () => void): void, + + /** + * Only run this describe block + */ + only(name: string, fn: () => void): void, + + /** + * Skip running this describe block + */ + skip(name: string, fn: () => void): void +}; + +/** An individual test unit */ +declare var it: { + /** + * An individual test unit + * + * @param {string} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + ( + name: string, + fn?: (done: () => void) => ?Promise, + timeout?: number + ): void, + /** + * Only run this test + * + * @param {string} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + only( + name: string, + fn?: (done: () => void) => ?Promise, + timeout?: number + ): void, + /** + * Skip running this test + * + * @param {string} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + skip( + name: string, + fn?: (done: () => void) => ?Promise, + timeout?: number + ): void, + /** + * Run the test concurrently + * + * @param {string} Name of Test + * @param {Function} Test + * @param {number} Timeout for the test, in milliseconds. + */ + concurrent( + name: string, + fn?: (done: () => void) => ?Promise, + timeout?: number + ): void +}; +declare function fit( + name: string, + fn: (done: () => void) => ?Promise, + timeout?: number +): void; +/** An individual test unit */ +declare var test: typeof it; +/** A disabled group of tests */ +declare var xdescribe: typeof describe; +/** A focused group of tests */ +declare var fdescribe: typeof describe; +/** A disabled individual test */ +declare var xit: typeof it; +/** A disabled individual test */ +declare var xtest: typeof it; + +/** The expect function is used every time you want to test a value */ +declare var expect: { + /** The object that you want to make assertions against */ + (value: any): JestExpectType & JestPromiseType & EnzymeMatchersType, + /** Add additional Jasmine matchers to Jest's roster */ + extend(matchers: { [name: string]: JestMatcher }): void, + /** Add a module that formats application-specific data structures. */ + addSnapshotSerializer(serializer: (input: Object) => string): void, + assertions(expectedAssertions: number): void, + hasAssertions(): void, + any(value: mixed): JestAsymmetricEqualityType, + anything(): void, + arrayContaining(value: Array): void, + objectContaining(value: Object): void, + /** Matches any received string that contains the exact expected string. */ + stringContaining(value: string): void, + stringMatching(value: string | RegExp): void +}; + +// TODO handle return type +// http://jasmine.github.io/2.4/introduction.html#section-Spies +declare function spyOn(value: mixed, method: string): Object; + +/** Holds all functions related to manipulating test runner */ +declare var jest: JestObjectType; + +/** + * The global Jasmine object, this is generally not exposed as the public API, + * using features inside here could break in later versions of Jest. + */ +declare var jasmine: { + DEFAULT_TIMEOUT_INTERVAL: number, + any(value: mixed): JestAsymmetricEqualityType, + anything(): void, + arrayContaining(value: Array): void, + clock(): JestClockType, + createSpy(name: string): JestSpyType, + createSpyObj( + baseName: string, + methodNames: Array + ): { [methodName: string]: JestSpyType }, + objectContaining(value: Object): void, + stringMatching(value: string): void +}; diff --git a/flow-typed/npm/lodash_v4.x.x.js b/flow-typed/npm/lodash_v4.x.x.js new file mode 100644 index 000000000..9bf5bdfef --- /dev/null +++ b/flow-typed/npm/lodash_v4.x.x.js @@ -0,0 +1,4207 @@ +// flow-typed signature: fcbc2c8209ca21df4e50468c1f1cf2cf +// flow-typed version: 9dc62314fe/lodash_v4.x.x/flow_>=v0.55.x + +declare module "lodash" { + declare type __CurriedFunction1 = (...r: [AA]) => R; + declare type CurriedFunction1 = __CurriedFunction1; + + declare type __CurriedFunction2 = (( + ...r: [AA] + ) => CurriedFunction1) & + ((...r: [AA, BB]) => R); + declare type CurriedFunction2 = __CurriedFunction2; + + declare type __CurriedFunction3 = (( + ...r: [AA] + ) => CurriedFunction2) & + ((...r: [AA, BB]) => CurriedFunction1) & + ((...r: [AA, BB, CC]) => R); + declare type CurriedFunction3 = __CurriedFunction3< + A, + B, + C, + R, + *, + *, + * + >; + + declare type __CurriedFunction4< + A, + B, + C, + D, + R, + AA: A, + BB: B, + CC: C, + DD: D + > = ((...r: [AA]) => CurriedFunction3) & + ((...r: [AA, BB]) => CurriedFunction2) & + ((...r: [AA, BB, CC]) => CurriedFunction1) & + ((...r: [AA, BB, CC, DD]) => R); + declare type CurriedFunction4 = __CurriedFunction4< + A, + B, + C, + D, + R, + *, + *, + *, + * + >; + + declare type __CurriedFunction5< + A, + B, + C, + D, + E, + R, + AA: A, + BB: B, + CC: C, + DD: D, + EE: E + > = ((...r: [AA]) => CurriedFunction4) & + ((...r: [AA, BB]) => CurriedFunction3) & + ((...r: [AA, BB, CC]) => CurriedFunction2) & + ((...r: [AA, BB, CC, DD]) => CurriedFunction1) & + ((...r: [AA, BB, CC, DD, EE]) => R); + declare type CurriedFunction5 = __CurriedFunction5< + A, + B, + C, + D, + E, + R, + *, + *, + *, + *, + * + >; + + declare type __CurriedFunction6< + A, + B, + C, + D, + E, + F, + R, + AA: A, + BB: B, + CC: C, + DD: D, + EE: E, + FF: F + > = ((...r: [AA]) => CurriedFunction5) & + ((...r: [AA, BB]) => CurriedFunction4) & + ((...r: [AA, BB, CC]) => CurriedFunction3) & + ((...r: [AA, BB, CC, DD]) => CurriedFunction2) & + ((...r: [AA, BB, CC, DD, EE]) => CurriedFunction1) & + ((...r: [AA, BB, CC, DD, EE, FF]) => R); + declare type CurriedFunction6 = __CurriedFunction6< + A, + B, + C, + D, + E, + F, + R, + *, + *, + *, + *, + *, + * + >; + + declare type Curry = (((...r: [A]) => R) => CurriedFunction1) & + (((...r: [A, B]) => R) => CurriedFunction2) & + (((...r: [A, B, C]) => R) => CurriedFunction3) & + (( + (...r: [A, B, C, D]) => R + ) => CurriedFunction4) & + (( + (...r: [A, B, C, D, E]) => R + ) => CurriedFunction5) & + (( + (...r: [A, B, C, D, E, F]) => R + ) => CurriedFunction6); + + declare type UnaryFn = (a: A) => R; + + declare type TemplateSettings = { + escape?: RegExp, + evaluate?: RegExp, + imports?: Object, + interpolate?: RegExp, + variable?: string + }; + + declare type TruncateOptions = { + length?: number, + omission?: string, + separator?: RegExp | string + }; + + declare type DebounceOptions = { + leading?: boolean, + maxWait?: number, + trailing?: boolean + }; + + declare type ThrottleOptions = { + leading?: boolean, + trailing?: boolean + }; + + declare type NestedArray = Array>; + + declare type matchesIterateeShorthand = Object; + declare type matchesPropertyIterateeShorthand = [string, any]; + declare type propertyIterateeShorthand = string; + + declare type OPredicate = + | ((value: A, key: string, object: O) => any) + | matchesIterateeShorthand + | matchesPropertyIterateeShorthand + | propertyIterateeShorthand; + + declare type OIterateeWithResult = + | Object + | string + | ((value: V, key: string, object: O) => R); + declare type OIteratee = OIterateeWithResult; + declare type OFlatMapIteratee = OIterateeWithResult>; + + declare type Predicate = + | ((value: T, index: number, array: Array) => any) + | matchesIterateeShorthand + | matchesPropertyIterateeShorthand + | propertyIterateeShorthand; + + declare type _ValueOnlyIteratee = (value: T) => mixed; + declare type ValueOnlyIteratee = _ValueOnlyIteratee | string; + declare type _Iteratee = ( + item: T, + index: number, + array: ?Array + ) => mixed; + declare type Iteratee = _Iteratee | Object | string; + declare type FlatMapIteratee = + | ((item: T, index: number, array: ?Array) => Array) + | Object + | string; + declare type Comparator = (item: T, item2: T) => boolean; + + declare type MapIterator = + | ((item: T, index: number, array: Array) => U) + | propertyIterateeShorthand; + + declare type OMapIterator = + | ((item: T, key: string, object: O) => U) + | propertyIterateeShorthand; + + declare class Lodash { + // Array + chunk(array: ?Array, size?: number): Array>; + compact(array: Array): Array; + concat(base: Array, ...elements: Array): Array; + difference(array: ?Array, values?: Array): Array; + differenceBy( + array: ?Array, + values: Array, + iteratee: ValueOnlyIteratee + ): T[]; + differenceWith(array: T[], values: T[], comparator?: Comparator): T[]; + drop(array: ?Array, n?: number): Array; + dropRight(array: ?Array, n?: number): Array; + dropRightWhile(array: ?Array, predicate?: Predicate): Array; + dropWhile(array: ?Array, predicate?: Predicate): Array; + fill( + array: ?Array, + value: U, + start?: number, + end?: number + ): Array; + findIndex( + array: ?$ReadOnlyArray, + predicate?: Predicate, + fromIndex?: number + ): number; + findLastIndex( + array: ?$ReadOnlyArray, + predicate?: Predicate, + fromIndex?: number + ): number; + // alias of _.head + first(array: ?Array): T; + flatten(array: Array | X>): Array; + flattenDeep(array: any[]): Array; + flattenDepth(array: any[], depth?: number): any[]; + fromPairs(pairs: Array<[A, B]>): { [key: A]: B }; + head(array: ?Array): T; + indexOf(array: ?Array, value: T, fromIndex?: number): number; + initial(array: ?Array): Array; + intersection(...arrays: Array>): Array; + //Workaround until (...parameter: T, parameter2: U) works + intersectionBy(a1: Array, iteratee?: ValueOnlyIteratee): Array; + intersectionBy( + a1: Array, + a2: Array, + iteratee?: ValueOnlyIteratee + ): Array; + intersectionBy( + a1: Array, + a2: Array, + a3: Array, + iteratee?: ValueOnlyIteratee + ): Array; + intersectionBy( + a1: Array, + a2: Array, + a3: Array, + a4: Array, + iteratee?: ValueOnlyIteratee + ): Array; + //Workaround until (...parameter: T, parameter2: U) works + intersectionWith(a1: Array, comparator: Comparator): Array; + intersectionWith( + a1: Array, + a2: Array, + comparator: Comparator + ): Array; + intersectionWith( + a1: Array, + a2: Array, + a3: Array, + comparator: Comparator + ): Array; + intersectionWith( + a1: Array, + a2: Array, + a3: Array, + a4: Array, + comparator: Comparator + ): Array; + join(array: ?Array, separator?: string): string; + last(array: ?Array): T; + lastIndexOf(array: ?Array, value: T, fromIndex?: number): number; + nth(array: T[], n?: number): T; + pull(array: ?Array, ...values?: Array): Array; + pullAll(array: ?Array, values: Array): Array; + pullAllBy( + array: ?Array, + values: Array, + iteratee?: ValueOnlyIteratee + ): Array; + pullAllWith(array?: T[], values: T[], comparator?: Function): T[]; + pullAt(array: ?Array, ...indexed?: Array): Array; + pullAt(array: ?Array, indexed?: Array): Array; + remove(array: ?Array, predicate?: Predicate): Array; + reverse(array: ?Array): Array; + slice(array: ?Array, start?: number, end?: number): Array; + sortedIndex(array: ?Array, value: T): number; + sortedIndexBy( + array: ?Array, + value: T, + iteratee?: ValueOnlyIteratee + ): number; + sortedIndexOf(array: ?Array, value: T): number; + sortedLastIndex(array: ?Array, value: T): number; + sortedLastIndexBy( + array: ?Array, + value: T, + iteratee?: ValueOnlyIteratee + ): number; + sortedLastIndexOf(array: ?Array, value: T): number; + sortedUniq(array: ?Array): Array; + sortedUniqBy(array: ?Array, iteratee?: (value: T) => mixed): Array; + tail(array: ?Array): Array; + take(array: ?Array, n?: number): Array; + takeRight(array: ?Array, n?: number): Array; + takeRightWhile(array: ?Array, predicate?: Predicate): Array; + takeWhile(array: ?Array, predicate?: Predicate): Array; + union(...arrays?: Array>): Array; + //Workaround until (...parameter: T, parameter2: U) works + unionBy(a1: Array, iteratee?: ValueOnlyIteratee): Array; + unionBy( + a1: Array, + a2: Array, + iteratee?: ValueOnlyIteratee + ): Array; + unionBy( + a1: Array, + a2: Array, + a3: Array, + iteratee?: ValueOnlyIteratee + ): Array; + unionBy( + a1: Array, + a2: Array, + a3: Array, + a4: Array, + iteratee?: ValueOnlyIteratee + ): Array; + //Workaround until (...parameter: T, parameter2: U) works + unionWith(a1: Array, comparator?: Comparator): Array; + unionWith( + a1: Array, + a2: Array, + comparator?: Comparator + ): Array; + unionWith( + a1: Array, + a2: Array, + a3: Array, + comparator?: Comparator + ): Array; + unionWith( + a1: Array, + a2: Array, + a3: Array, + a4: Array, + comparator?: Comparator + ): Array; + uniq(array: ?Array): Array; + uniqBy(array: ?Array, iteratee?: ValueOnlyIteratee): Array; + uniqWith(array: ?Array, comparator?: Comparator): Array; + unzip(array: ?Array): Array; + unzipWith(array: ?Array, iteratee?: Iteratee): Array; + without(array: ?Array, ...values?: Array): Array; + xor(...array: Array>): Array; + //Workaround until (...parameter: T, parameter2: U) works + xorBy(a1: Array, iteratee?: ValueOnlyIteratee): Array; + xorBy( + a1: Array, + a2: Array, + iteratee?: ValueOnlyIteratee + ): Array; + xorBy( + a1: Array, + a2: Array, + a3: Array, + iteratee?: ValueOnlyIteratee + ): Array; + xorBy( + a1: Array, + a2: Array, + a3: Array, + a4: Array, + iteratee?: ValueOnlyIteratee + ): Array; + //Workaround until (...parameter: T, parameter2: U) works + xorWith(a1: Array, comparator?: Comparator): Array; + xorWith( + a1: Array, + a2: Array, + comparator?: Comparator + ): Array; + xorWith( + a1: Array, + a2: Array, + a3: Array, + comparator?: Comparator + ): Array; + xorWith( + a1: Array, + a2: Array, + a3: Array, + a4: Array, + comparator?: Comparator + ): Array; + zip(a1: A[], a2: B[]): Array<[A, B]>; + zip(a1: A[], a2: B[], a3: C[]): Array<[A, B, C]>; + zip(a1: A[], a2: B[], a3: C[], a4: D[]): Array<[A, B, C, D]>; + zip( + a1: A[], + a2: B[], + a3: C[], + a4: D[], + a5: E[] + ): Array<[A, B, C, D, E]>; + + zipObject(props?: Array, values?: Array): { [key: K]: V }; + zipObjectDeep(props?: any[], values?: any): Object; + //Workaround until (...parameter: T, parameter2: U) works + zipWith(a1: NestedArray, iteratee?: Iteratee): Array; + zipWith( + a1: NestedArray, + a2: NestedArray, + iteratee?: Iteratee + ): Array; + zipWith( + a1: NestedArray, + a2: NestedArray, + a3: NestedArray, + iteratee?: Iteratee + ): Array; + zipWith( + a1: NestedArray, + a2: NestedArray, + a3: NestedArray, + a4: NestedArray, + iteratee?: Iteratee + ): Array; + + // Collection + countBy(array: ?Array, iteratee?: ValueOnlyIteratee): Object; + countBy(object: T, iteratee?: ValueOnlyIteratee): Object; + // alias of _.forEach + each(array: ?Array, iteratee?: Iteratee): Array; + each(object: T, iteratee?: OIteratee): T; + // alias of _.forEachRight + eachRight(array: ?Array, iteratee?: Iteratee): Array; + eachRight(object: T, iteratee?: OIteratee): T; + every(array: ?Array, iteratee?: Iteratee): boolean; + every(object: T, iteratee?: OIteratee): boolean; + filter(array: ?Array, predicate?: Predicate): Array; + filter( + object: T, + predicate?: OPredicate + ): Array; + find( + array: ?$ReadOnlyArray, + predicate?: Predicate, + fromIndex?: number + ): T | void; + find( + object: T, + predicate?: OPredicate, + fromIndex?: number + ): V; + findLast( + array: ?$ReadOnlyArray, + predicate?: Predicate, + fromIndex?: number + ): T | void; + findLast( + object: T, + predicate?: OPredicate + ): V; + flatMap(array: ?Array, iteratee?: FlatMapIteratee): Array; + flatMap( + object: T, + iteratee?: OFlatMapIteratee + ): Array; + flatMapDeep( + array: ?Array, + iteratee?: FlatMapIteratee + ): Array; + flatMapDeep( + object: T, + iteratee?: OFlatMapIteratee + ): Array; + flatMapDepth( + array: ?Array, + iteratee?: FlatMapIteratee, + depth?: number + ): Array; + flatMapDepth( + object: T, + iteratee?: OFlatMapIteratee, + depth?: number + ): Array; + forEach(array: ?Array, iteratee?: Iteratee): Array; + forEach(object: T, iteratee?: OIteratee): T; + forEachRight(array: ?Array, iteratee?: Iteratee): Array; + forEachRight(object: T, iteratee?: OIteratee): T; + groupBy( + array: ?Array, + iteratee?: ValueOnlyIteratee + ): { [key: V]: Array }; + groupBy( + object: T, + iteratee?: ValueOnlyIteratee + ): { [key: V]: Array }; + includes(array: ?Array, value: T, fromIndex?: number): boolean; + includes(object: T, value: any, fromIndex?: number): boolean; + includes(str: string, value: string, fromIndex?: number): boolean; + invokeMap( + array: ?Array, + path: ((value: T) => Array | string) | Array | string, + ...args?: Array + ): Array; + invokeMap( + object: T, + path: ((value: any) => Array | string) | Array | string, + ...args?: Array + ): Array; + keyBy( + array: ?Array, + iteratee?: ValueOnlyIteratee + ): { [key: V]: ?T }; + keyBy( + object: T, + iteratee?: ValueOnlyIteratee + ): { [key: V]: ?A }; + map(array: ?Array, iteratee?: MapIterator): Array; + map( + object: ?T, + iteratee?: OMapIterator + ): Array; + map( + str: ?string, + iteratee?: (char: string, index: number, str: string) => any + ): string; + orderBy( + array: ?Array, + iteratees?: Array> | string, + orders?: Array<"asc" | "desc"> | string + ): Array; + orderBy( + object: T, + iteratees?: Array> | string, + orders?: Array<"asc" | "desc"> | string + ): Array; + partition( + array: ?Array, + predicate?: Predicate + ): [Array, Array]; + partition( + object: T, + predicate?: OPredicate + ): [Array, Array]; + reduce( + array: ?Array, + iteratee?: ( + accumulator: U, + value: T, + index: number, + array: ?Array + ) => U, + accumulator?: U + ): U; + reduce( + object: T, + iteratee?: (accumulator: U, value: any, key: string, object: T) => U, + accumulator?: U + ): U; + reduceRight( + array: ?Array, + iteratee?: ( + accumulator: U, + value: T, + index: number, + array: ?Array + ) => U, + accumulator?: U + ): U; + reduceRight( + object: T, + iteratee?: (accumulator: U, value: any, key: string, object: T) => U, + accumulator?: U + ): U; + reject(array: ?Array, predicate?: Predicate): Array; + reject( + object: T, + predicate?: OPredicate + ): Array; + sample(array: ?Array): T; + sample(object: T): V; + sampleSize(array: ?Array, n?: number): Array; + sampleSize(object: T, n?: number): Array; + shuffle(array: ?Array): Array; + shuffle(object: T): Array; + size(collection: Array | Object): number; + some(array: ?Array, predicate?: Predicate): boolean; + some( + object?: ?T, + predicate?: OPredicate + ): boolean; + sortBy(array: ?Array, ...iteratees?: Array>): Array; + sortBy(array: ?Array, iteratees?: Array>): Array; + sortBy( + object: T, + ...iteratees?: Array> + ): Array; + sortBy(object: T, iteratees?: Array>): Array; + + // Date + now(): number; + + // Function + after(n: number, fn: Function): Function; + ary(func: Function, n?: number): Function; + before(n: number, fn: Function): Function; + bind(func: Function, thisArg: any, ...partials: Array): Function; + bindKey(obj: Object, key: string, ...partials: Array): Function; + curry: Curry; + curry(func: Function, arity?: number): Function; + curryRight(func: Function, arity?: number): Function; + debounce(func: F, wait?: number, options?: DebounceOptions): F; + defer(func: Function, ...args?: Array): number; + delay(func: Function, wait: number, ...args?: Array): number; + flip(func: Function): Function; + memoize(func: F, resolver?: Function): F; + negate(predicate: Function): Function; + once(func: Function): Function; + overArgs(func: Function, ...transforms: Array): Function; + overArgs(func: Function, transforms: Array): Function; + partial(func: Function, ...partials: any[]): Function; + partialRight(func: Function, ...partials: Array): Function; + partialRight(func: Function, partials: Array): Function; + rearg(func: Function, ...indexes: Array): Function; + rearg(func: Function, indexes: Array): Function; + rest(func: Function, start?: number): Function; + spread(func: Function): Function; + throttle( + func: Function, + wait?: number, + options?: ThrottleOptions + ): Function; + unary(func: Function): Function; + wrap(value: any, wrapper: Function): Function; + + // Lang + castArray(value: *): any[]; + clone(value: T): T; + cloneDeep(value: T): T; + cloneDeepWith( + value: T, + customizer?: ?(value: T, key: number | string, object: T, stack: any) => U + ): U; + cloneWith( + value: T, + customizer?: ?(value: T, key: number | string, object: T, stack: any) => U + ): U; + conformsTo( + source: T, + predicates: T & { [key: string]: (x: any) => boolean } + ): boolean; + eq(value: any, other: any): boolean; + gt(value: any, other: any): boolean; + gte(value: any, other: any): boolean; + isArguments(value: any): boolean; + isArray(value: any): boolean; + isArrayBuffer(value: any): boolean; + isArrayLike(value: any): boolean; + isArrayLikeObject(value: any): boolean; + isBoolean(value: any): boolean; + isBuffer(value: any): boolean; + isDate(value: any): boolean; + isElement(value: any): boolean; + isEmpty(value: any): boolean; + isEqual(value: any, other: any): boolean; + isEqualWith( + value: T, + other: U, + customizer?: ( + objValue: any, + otherValue: any, + key: number | string, + object: T, + other: U, + stack: any + ) => boolean | void + ): boolean; + isError(value: any): boolean; + isFinite(value: any): boolean; + isFunction(value: Function): true; + isFunction(value: number | string | void | null | Object): false; + isInteger(value: any): boolean; + isLength(value: any): boolean; + isMap(value: any): boolean; + isMatch(object?: ?Object, source: Object): boolean; + isMatchWith( + object: T, + source: U, + customizer?: ( + objValue: any, + srcValue: any, + key: number | string, + object: T, + source: U + ) => boolean | void + ): boolean; + isNaN(value: any): boolean; + isNative(value: any): boolean; + isNil(value: any): boolean; + isNull(value: any): boolean; + isNumber(value: any): boolean; + isObject(value: any): boolean; + isObjectLike(value: any): boolean; + isPlainObject(value: any): boolean; + isRegExp(value: any): boolean; + isSafeInteger(value: any): boolean; + isSet(value: any): boolean; + isString(value: string): true; + isString( + value: number | boolean | Function | void | null | Object | Array + ): false; + isSymbol(value: any): boolean; + isTypedArray(value: any): boolean; + isUndefined(value: any): boolean; + isWeakMap(value: any): boolean; + isWeakSet(value: any): boolean; + lt(value: any, other: any): boolean; + lte(value: any, other: any): boolean; + toArray(value: any): Array; + toFinite(value: any): number; + toInteger(value: any): number; + toLength(value: any): number; + toNumber(value: any): number; + toPlainObject(value: any): Object; + toSafeInteger(value: any): number; + toString(value: any): string; + + // Math + add(augend: number, addend: number): number; + ceil(number: number, precision?: number): number; + divide(dividend: number, divisor: number): number; + floor(number: number, precision?: number): number; + max(array: ?Array): T; + maxBy(array: ?Array, iteratee?: Iteratee): T; + mean(array: Array<*>): number; + meanBy(array: Array, iteratee?: Iteratee): number; + min(array: ?Array): T; + minBy(array: ?Array, iteratee?: Iteratee): T; + multiply(multiplier: number, multiplicand: number): number; + round(number: number, precision?: number): number; + subtract(minuend: number, subtrahend: number): number; + sum(array: Array<*>): number; + sumBy(array: Array, iteratee?: Iteratee): number; + + // number + clamp(number: number, lower?: number, upper: number): number; + inRange(number: number, start?: number, end: number): boolean; + random(lower?: number, upper?: number, floating?: boolean): number; + + // Object + assign(object?: ?Object, ...sources?: Array): Object; + assignIn(a: A, b: B): A & B; + assignIn(a: A, b: B, c: C): A & B & C; + assignIn(a: A, b: B, c: C, d: D): A & B & C & D; + assignIn(a: A, b: B, c: C, d: D, e: E): A & B & C & D & E; + assignInWith( + object: T, + s1: A, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void + ): Object; + assignInWith( + object: T, + s1: A, + s2: B, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B + ) => any | void + ): Object; + assignInWith( + object: T, + s1: A, + s2: B, + s3: C, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B | C + ) => any | void + ): Object; + assignInWith( + object: T, + s1: A, + s2: B, + s3: C, + s4: D, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B | C | D + ) => any | void + ): Object; + assignWith( + object: T, + s1: A, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void + ): Object; + assignWith( + object: T, + s1: A, + s2: B, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B + ) => any | void + ): Object; + assignWith( + object: T, + s1: A, + s2: B, + s3: C, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B | C + ) => any | void + ): Object; + assignWith( + object: T, + s1: A, + s2: B, + s3: C, + s4: D, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B | C | D + ) => any | void + ): Object; + at(object?: ?Object, ...paths: Array): Array; + at(object?: ?Object, paths: Array): Array; + create(prototype: T, properties?: Object): $Supertype; + defaults(object?: ?Object, ...sources?: Array): Object; + defaultsDeep(object?: ?Object, ...sources?: Array): Object; + // alias for _.toPairs + entries(object?: ?Object): Array<[string, any]>; + // alias for _.toPairsIn + entriesIn(object?: ?Object): Array<[string, any]>; + // alias for _.assignIn + extend(a: A, b: B): A & B; + extend(a: A, b: B, c: C): A & B & C; + extend(a: A, b: B, c: C, d: D): A & B & C & D; + extend(a: A, b: B, c: C, d: D, e: E): A & B & C & D & E; + // alias for _.assignInWith + extendWith( + object: T, + s1: A, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void + ): Object; + extendWith( + object: T, + s1: A, + s2: B, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B + ) => any | void + ): Object; + extendWith( + object: T, + s1: A, + s2: B, + s3: C, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B | C + ) => any | void + ): Object; + extendWith( + object: T, + s1: A, + s2: B, + s3: C, + s4: D, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B | C | D + ) => any | void + ): Object; + findKey( + object?: ?T, + predicate?: OPredicate + ): string | void; + findLastKey( + object?: ?T, + predicate?: OPredicate + ): string | void; + forIn(object?: ?Object, iteratee?: OIteratee<*>): Object; + forInRight(object?: ?Object, iteratee?: OIteratee<*>): Object; + forOwn(object?: ?Object, iteratee?: OIteratee<*>): Object; + forOwnRight(object?: ?Object, iteratee?: OIteratee<*>): Object; + functions(object?: ?Object): Array; + functionsIn(object?: ?Object): Array; + get( + object?: ?Object | ?Array, + path?: ?Array | string, + defaultValue?: any + ): any; + has(object?: ?Object, path?: ?Array | string): boolean; + hasIn(object?: ?Object, path?: ?Array | string): boolean; + invert(object?: ?Object, multiVal?: boolean): Object; + invertBy(object: ?Object, iteratee?: Function): Object; + invoke( + object?: ?Object, + path?: ?Array | string, + ...args?: Array + ): any; + keys(object?: ?{ [key: K]: any }): Array; + keys(object?: ?Object): Array; + keysIn(object?: ?Object): Array; + mapKeys(object?: ?Object, iteratee?: OIteratee<*>): Object; + mapValues(object?: ?Object, iteratee?: OIteratee<*>): Object; + merge(object?: ?Object, ...sources?: Array): Object; + mergeWith( + object: T, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void + ): Object; + mergeWith( + object: T, + s1: A, + s2: B, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B + ) => any | void + ): Object; + mergeWith( + object: T, + s1: A, + s2: B, + s3: C, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B | C + ) => any | void + ): Object; + mergeWith( + object: T, + s1: A, + s2: B, + s3: C, + s4: D, + customizer?: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B | C | D + ) => any | void + ): Object; + omit(object?: ?Object, ...props: Array): Object; + omit(object?: ?Object, props: Array): Object; + omitBy( + object?: ?T, + predicate?: OPredicate + ): Object; + pick(object?: ?Object, ...props: Array): Object; + pick(object?: ?Object, props: Array): Object; + pickBy( + object?: ?T, + predicate?: OPredicate + ): Object; + result( + object?: ?Object, + path?: ?Array | string, + defaultValue?: any + ): any; + set(object?: ?Object, path?: ?Array | string, value: any): Object; + setWith( + object: T, + path?: ?Array | string, + value: any, + customizer?: (nsValue: any, key: string, nsObject: T) => any + ): Object; + toPairs(object?: ?Object | Array<*>): Array<[string, any]>; + toPairsIn(object?: ?Object): Array<[string, any]>; + transform( + collection: Object | Array, + iteratee?: OIteratee<*>, + accumulator?: any + ): any; + unset(object?: ?Object, path?: ?Array | string): boolean; + update(object: Object, path: string[] | string, updater: Function): Object; + updateWith( + object: Object, + path: string[] | string, + updater: Function, + customizer?: Function + ): Object; + values(object?: ?Object): Array; + valuesIn(object?: ?Object): Array; + + // Seq + // harder to read, but this is _() + (value: any): any; + chain(value: T): any; + tap(value: T, interceptor: (value: T) => any): T; + thru(value: T1, interceptor: (value: T1) => T2): T2; + // TODO: _.prototype.* + + // String + camelCase(string?: ?string): string; + capitalize(string?: string): string; + deburr(string?: string): string; + endsWith(string?: string, target?: string, position?: number): boolean; + escape(string?: string): string; + escapeRegExp(string?: string): string; + kebabCase(string?: string): string; + lowerCase(string?: string): string; + lowerFirst(string?: string): string; + pad(string?: string, length?: number, chars?: string): string; + padEnd(string?: string, length?: number, chars?: string): string; + padStart(string?: string, length?: number, chars?: string): string; + parseInt(string: string, radix?: number): number; + repeat(string?: string, n?: number): string; + replace( + string?: string, + pattern: RegExp | string, + replacement: ((string: string) => string) | string + ): string; + snakeCase(string?: string): string; + split( + string?: string, + separator: RegExp | string, + limit?: number + ): Array; + startCase(string?: string): string; + startsWith(string?: string, target?: string, position?: number): boolean; + template(string?: string, options?: TemplateSettings): Function; + toLower(string?: string): string; + toUpper(string?: string): string; + trim(string?: string, chars?: string): string; + trimEnd(string?: string, chars?: string): string; + trimStart(string?: string, chars?: string): string; + truncate(string?: string, options?: TruncateOptions): string; + unescape(string?: string): string; + upperCase(string?: string): string; + upperFirst(string?: string): string; + words(string?: string, pattern?: RegExp | string): Array; + + // Util + attempt(func: Function, ...args: Array): any; + bindAll(object?: ?Object, methodNames: Array): Object; + bindAll(object?: ?Object, ...methodNames: Array): Object; + cond(pairs: NestedArray): Function; + conforms(source: Object): Function; + constant(value: T): () => T; + defaultTo( + value: T1, + defaultValue: T2 + ): T1; + // NaN is a number instead of its own type, otherwise it would behave like null/void + defaultTo(value: T1, defaultValue: T2): T1 | T2; + defaultTo(value: T1, defaultValue: T2): T2; + flow: $ComposeReverse; + flow(funcs?: Array): Function; + flowRight: $Compose; + flowRight(funcs?: Array): Function; + identity(value: T): T; + iteratee(func?: any): Function; + matches(source: Object): Function; + matchesProperty(path?: ?Array | string, srcValue: any): Function; + method(path?: ?Array | string, ...args?: Array): Function; + methodOf(object?: ?Object, ...args?: Array): Function; + mixin( + object?: T, + source: Object, + options?: { chain: boolean } + ): T; + noConflict(): Lodash; + noop(...args: Array): void; + nthArg(n?: number): Function; + over(...iteratees: Array): Function; + over(iteratees: Array): Function; + overEvery(...predicates: Array): Function; + overEvery(predicates: Array): Function; + overSome(...predicates: Array): Function; + overSome(predicates: Array): Function; + property(path?: ?Array | string): Function; + propertyOf(object?: ?Object): Function; + range(start: number, end: number, step?: number): Array; + range(end: number, step?: number): Array; + rangeRight(start: number, end: number, step?: number): Array; + rangeRight(end: number, step?: number): Array; + runInContext(context?: Object): Function; + + stubArray(): Array<*>; + stubFalse(): false; + stubObject(): {}; + stubString(): ""; + stubTrue(): true; + times(n: number, ...rest: Array): Array; + times(n: number, iteratee: (i: number) => T): Array; + toPath(value: any): Array; + uniqueId(prefix?: string): string; + + // Properties + VERSION: string; + templateSettings: TemplateSettings; + } + + declare module.exports: Lodash; +} + +declare module "lodash/fp" { + declare type __CurriedFunction1 = (...r: [AA]) => R; + declare type CurriedFunction1 = __CurriedFunction1; + + declare type __CurriedFunction2 = (( + ...r: [AA] + ) => CurriedFunction1) & + ((...r: [AA, BB]) => R); + declare type CurriedFunction2 = __CurriedFunction2; + + declare type __CurriedFunction3 = (( + ...r: [AA] + ) => CurriedFunction2) & + ((...r: [AA, BB]) => CurriedFunction1) & + ((...r: [AA, BB, CC]) => R); + declare type CurriedFunction3 = __CurriedFunction3< + A, + B, + C, + R, + *, + *, + * + >; + + declare type __CurriedFunction4< + A, + B, + C, + D, + R, + AA: A, + BB: B, + CC: C, + DD: D + > = ((...r: [AA]) => CurriedFunction3) & + ((...r: [AA, BB]) => CurriedFunction2) & + ((...r: [AA, BB, CC]) => CurriedFunction1) & + ((...r: [AA, BB, CC, DD]) => R); + declare type CurriedFunction4 = __CurriedFunction4< + A, + B, + C, + D, + R, + *, + *, + *, + * + >; + + declare type __CurriedFunction5< + A, + B, + C, + D, + E, + R, + AA: A, + BB: B, + CC: C, + DD: D, + EE: E + > = ((...r: [AA]) => CurriedFunction4) & + ((...r: [AA, BB]) => CurriedFunction3) & + ((...r: [AA, BB, CC]) => CurriedFunction2) & + ((...r: [AA, BB, CC, DD]) => CurriedFunction1) & + ((...r: [AA, BB, CC, DD, EE]) => R); + declare type CurriedFunction5 = __CurriedFunction5< + A, + B, + C, + D, + E, + R, + *, + *, + *, + *, + * + >; + + declare type __CurriedFunction6< + A, + B, + C, + D, + E, + F, + R, + AA: A, + BB: B, + CC: C, + DD: D, + EE: E, + FF: F + > = ((...r: [AA]) => CurriedFunction5) & + ((...r: [AA, BB]) => CurriedFunction4) & + ((...r: [AA, BB, CC]) => CurriedFunction3) & + ((...r: [AA, BB, CC, DD]) => CurriedFunction2) & + ((...r: [AA, BB, CC, DD, EE]) => CurriedFunction1) & + ((...r: [AA, BB, CC, DD, EE, FF]) => R); + declare type CurriedFunction6 = __CurriedFunction6< + A, + B, + C, + D, + E, + F, + R, + *, + *, + *, + *, + *, + * + >; + + declare type Curry = (((...r: [A]) => R) => CurriedFunction1) & + (((...r: [A, B]) => R) => CurriedFunction2) & + (((...r: [A, B, C]) => R) => CurriedFunction3) & + (( + (...r: [A, B, C, D]) => R + ) => CurriedFunction4) & + (( + (...r: [A, B, C, D, E]) => R + ) => CurriedFunction5) & + (( + (...r: [A, B, C, D, E, F]) => R + ) => CurriedFunction6); + + declare type UnaryFn = (a: A) => R; + + declare type TemplateSettings = { + escape?: RegExp, + evaluate?: RegExp, + imports?: Object, + interpolate?: RegExp, + variable?: string + }; + + declare type TruncateOptions = { + length?: number, + omission?: string, + separator?: RegExp | string + }; + + declare type DebounceOptions = { + leading?: boolean, + maxWait?: number, + trailing?: boolean + }; + + declare type ThrottleOptions = { + leading?: boolean, + trailing?: boolean + }; + + declare type NestedArray = Array>; + + declare type matchesIterateeShorthand = Object; + declare type matchesPropertyIterateeShorthand = [string, any]; + declare type propertyIterateeShorthand = string; + + declare type OPredicate = + | ((value: A) => any) + | matchesIterateeShorthand + | matchesPropertyIterateeShorthand + | propertyIterateeShorthand; + + declare type OIterateeWithResult = Object | string | ((value: V) => R); + declare type OIteratee = OIterateeWithResult; + declare type OFlatMapIteratee = OIterateeWithResult>; + + declare type Predicate = + | ((value: T) => any) + | matchesIterateeShorthand + | matchesPropertyIterateeShorthand + | propertyIterateeShorthand; + + declare type _ValueOnlyIteratee = (value: T) => mixed; + declare type ValueOnlyIteratee = _ValueOnlyIteratee | string; + declare type _Iteratee = (item: T) => mixed; + declare type Iteratee = _Iteratee | Object | string; + declare type FlatMapIteratee = + | ((item: T) => Array) + | Object + | string; + declare type Comparator = (item: T, item2: T) => boolean; + + declare type MapIterator = ((item: T) => U) | propertyIterateeShorthand; + + declare type OMapIterator = + | ((item: T) => U) + | propertyIterateeShorthand; + + declare class Lodash { + // Array + chunk(size: number): (array: Array) => Array>; + chunk(size: number, array: Array): Array>; + compact(array: Array): Array; + concat | T, B: Array | U>( + base: A + ): (elements: B) => Array; + concat | T, B: Array | U>( + base: A, + elements: B + ): Array; + difference(values: Array): (array: Array) => Array; + difference(values: Array, array: Array): Array; + differenceBy( + iteratee: ValueOnlyIteratee + ): ((values: Array) => (array: Array) => T[]) & + ((values: Array, array: Array) => T[]); + differenceBy( + iteratee: ValueOnlyIteratee, + values: Array + ): (array: Array) => T[]; + differenceBy( + iteratee: ValueOnlyIteratee, + values: Array, + array: Array + ): T[]; + differenceWith( + values: T[] + ): ((comparator: Comparator) => (array: T[]) => T[]) & + ((comparator: Comparator, array: T[]) => T[]); + differenceWith( + values: T[], + comparator: Comparator + ): (array: T[]) => T[]; + differenceWith(values: T[], comparator: Comparator, array: T[]): T[]; + drop(n: number): (array: Array) => Array; + drop(n: number, array: Array): Array; + dropLast(n: number): (array: Array) => Array; + dropLast(n: number, array: Array): Array; + dropRight(n: number): (array: Array) => Array; + dropRight(n: number, array: Array): Array; + dropRightWhile(predicate: Predicate): (array: Array) => Array; + dropRightWhile(predicate: Predicate, array: Array): Array; + dropWhile(predicate: Predicate): (array: Array) => Array; + dropWhile(predicate: Predicate, array: Array): Array; + dropLastWhile(predicate: Predicate): (array: Array) => Array; + dropLastWhile(predicate: Predicate, array: Array): Array; + fill( + start: number + ): (( + end: number + ) => ((value: U) => (array: Array) => Array) & + ((value: U, array: Array) => Array)) & + ((end: number, value: U) => (array: Array) => Array) & + ((end: number, value: U, array: Array) => Array); + fill( + start: number, + end: number + ): ((value: U) => (array: Array) => Array) & + ((value: U, array: Array) => Array); + fill( + start: number, + end: number, + value: U + ): (array: Array) => Array; + fill( + start: number, + end: number, + value: U, + array: Array + ): Array; + findIndex(predicate: Predicate): (array: $ReadOnlyArray) => number; + findIndex(predicate: Predicate, array: $ReadOnlyArray): number; + findIndexFrom( + predicate: Predicate + ): ((fromIndex: number) => (array: $ReadOnlyArray) => number) & + ((fromIndex: number, array: $ReadOnlyArray) => number); + findIndexFrom( + predicate: Predicate, + fromIndex: number + ): (array: $ReadOnlyArray) => number; + findIndexFrom( + predicate: Predicate, + fromIndex: number, + array: $ReadOnlyArray + ): number; + findLastIndex( + predicate: Predicate + ): (array: $ReadOnlyArray) => number; + findLastIndex(predicate: Predicate, array: $ReadOnlyArray): number; + findLastIndexFrom( + predicate: Predicate + ): ((fromIndex: number) => (array: $ReadOnlyArray) => number) & + ((fromIndex: number, array: $ReadOnlyArray) => number); + findLastIndexFrom( + predicate: Predicate, + fromIndex: number + ): (array: $ReadOnlyArray) => number; + findLastIndexFrom( + predicate: Predicate, + fromIndex: number, + array: $ReadOnlyArray + ): number; + // alias of _.head + first(array: Array): T; + flatten(array: Array | X>): Array; + unnest(array: Array | X>): Array; + flattenDeep(array: any[]): Array; + flattenDepth(depth: number): (array: any[]) => any[]; + flattenDepth(depth: number, array: any[]): any[]; + fromPairs(pairs: Array<[A, B]>): { [key: A]: B }; + head(array: Array): T; + indexOf(value: T): (array: Array) => number; + indexOf(value: T, array: Array): number; + indexOfFrom( + value: T + ): ((fromIndex: number) => (array: Array) => number) & + ((fromIndex: number, array: Array) => number); + indexOfFrom(value: T, fromIndex: number): (array: Array) => number; + indexOfFrom(value: T, fromIndex: number, array: Array): number; + initial(array: Array): Array; + init(array: Array): Array; + intersection(a1: Array): (a2: Array) => Array; + intersection(a1: Array, a2: Array): Array; + intersectionBy( + iteratee: ValueOnlyIteratee + ): ((a1: Array) => (a2: Array) => Array) & + ((a1: Array, a2: Array) => Array); + intersectionBy( + iteratee: ValueOnlyIteratee, + a1: Array + ): (a2: Array) => Array; + intersectionBy( + iteratee: ValueOnlyIteratee, + a1: Array, + a2: Array + ): Array; + intersectionWith( + comparator: Comparator + ): ((a1: Array) => (a2: Array) => Array) & + ((a1: Array, a2: Array) => Array); + intersectionWith( + comparator: Comparator, + a1: Array + ): (a2: Array) => Array; + intersectionWith( + comparator: Comparator, + a1: Array, + a2: Array + ): Array; + join(separator: string): (array: Array) => string; + join(separator: string, array: Array): string; + last(array: Array): T; + lastIndexOf(value: T): (array: Array) => number; + lastIndexOf(value: T, array: Array): number; + lastIndexOfFrom( + value: T + ): ((fromIndex: number) => (array: Array) => number) & + ((fromIndex: number, array: Array) => number); + lastIndexOfFrom( + value: T, + fromIndex: number + ): (array: Array) => number; + lastIndexOfFrom(value: T, fromIndex: number, array: Array): number; + nth(n: number): (array: T[]) => T; + nth(n: number, array: T[]): T; + pull(value: T): (array: Array) => Array; + pull(value: T, array: Array): Array; + pullAll(values: Array): (array: Array) => Array; + pullAll(values: Array, array: Array): Array; + pullAllBy( + iteratee: ValueOnlyIteratee + ): ((values: Array) => (array: Array) => Array) & + ((values: Array, array: Array) => Array); + pullAllBy( + iteratee: ValueOnlyIteratee, + values: Array + ): (array: Array) => Array; + pullAllBy( + iteratee: ValueOnlyIteratee, + values: Array, + array: Array + ): Array; + pullAllWith( + comparator: Function + ): ((values: T[]) => (array: T[]) => T[]) & + ((values: T[], array: T[]) => T[]); + pullAllWith(comparator: Function, values: T[]): (array: T[]) => T[]; + pullAllWith(comparator: Function, values: T[], array: T[]): T[]; + pullAt(indexed: Array): (array: Array) => Array; + pullAt(indexed: Array, array: Array): Array; + remove(predicate: Predicate): (array: Array) => Array; + remove(predicate: Predicate, array: Array): Array; + reverse(array: Array): Array; + slice( + start: number + ): ((end: number) => (array: Array) => Array) & + ((end: number, array: Array) => Array); + slice(start: number, end: number): (array: Array) => Array; + slice(start: number, end: number, array: Array): Array; + sortedIndex(value: T): (array: Array) => number; + sortedIndex(value: T, array: Array): number; + sortedIndexBy( + iteratee: ValueOnlyIteratee + ): ((value: T) => (array: Array) => number) & + ((value: T, array: Array) => number); + sortedIndexBy( + iteratee: ValueOnlyIteratee, + value: T + ): (array: Array) => number; + sortedIndexBy( + iteratee: ValueOnlyIteratee, + value: T, + array: Array + ): number; + sortedIndexOf(value: T): (array: Array) => number; + sortedIndexOf(value: T, array: Array): number; + sortedLastIndex(value: T): (array: Array) => number; + sortedLastIndex(value: T, array: Array): number; + sortedLastIndexBy( + iteratee: ValueOnlyIteratee + ): ((value: T) => (array: Array) => number) & + ((value: T, array: Array) => number); + sortedLastIndexBy( + iteratee: ValueOnlyIteratee, + value: T + ): (array: Array) => number; + sortedLastIndexBy( + iteratee: ValueOnlyIteratee, + value: T, + array: Array + ): number; + sortedLastIndexOf(value: T): (array: Array) => number; + sortedLastIndexOf(value: T, array: Array): number; + sortedUniq(array: Array): Array; + sortedUniqBy( + iteratee: (value: T) => mixed + ): (array: Array) => Array; + sortedUniqBy(iteratee: (value: T) => mixed, array: Array): Array; + tail(array: Array): Array; + take(n: number): (array: Array) => Array; + take(n: number, array: Array): Array; + takeRight(n: number): (array: Array) => Array; + takeRight(n: number, array: Array): Array; + takeLast(n: number): (array: Array) => Array; + takeLast(n: number, array: Array): Array; + takeRightWhile(predicate: Predicate): (array: Array) => Array; + takeRightWhile(predicate: Predicate, array: Array): Array; + takeLastWhile(predicate: Predicate): (array: Array) => Array; + takeLastWhile(predicate: Predicate, array: Array): Array; + takeWhile(predicate: Predicate): (array: Array) => Array; + takeWhile(predicate: Predicate, array: Array): Array; + union(a1: Array): (a2: Array) => Array; + union(a1: Array, a2: Array): Array; + unionBy( + iteratee: ValueOnlyIteratee + ): ((a1: Array) => (a2: Array) => Array) & + ((a1: Array, a2: Array) => Array); + unionBy( + iteratee: ValueOnlyIteratee, + a1: Array + ): (a2: Array) => Array; + unionBy( + iteratee: ValueOnlyIteratee, + a1: Array, + a2: Array + ): Array; + unionWith( + comparator: Comparator + ): ((a1: Array) => (a2: Array) => Array) & + ((a1: Array, a2: Array) => Array); + unionWith( + comparator: Comparator, + a1: Array + ): (a2: Array) => Array; + unionWith( + comparator: Comparator, + a1: Array, + a2: Array + ): Array; + uniq(array: Array): Array; + uniqBy(iteratee: ValueOnlyIteratee): (array: Array) => Array; + uniqBy(iteratee: ValueOnlyIteratee, array: Array): Array; + uniqWith(comparator: Comparator): (array: Array) => Array; + uniqWith(comparator: Comparator, array: Array): Array; + unzip(array: Array): Array; + unzipWith(iteratee: Iteratee): (array: Array) => Array; + unzipWith(iteratee: Iteratee, array: Array): Array; + without(values: Array): (array: Array) => Array; + without(values: Array, array: Array): Array; + xor(a1: Array): (a2: Array) => Array; + xor(a1: Array, a2: Array): Array; + symmetricDifference(a1: Array): (a2: Array) => Array; + symmetricDifference(a1: Array, a2: Array): Array; + xorBy( + iteratee: ValueOnlyIteratee + ): ((a1: Array) => (a2: Array) => Array) & + ((a1: Array, a2: Array) => Array); + xorBy( + iteratee: ValueOnlyIteratee, + a1: Array + ): (a2: Array) => Array; + xorBy( + iteratee: ValueOnlyIteratee, + a1: Array, + a2: Array + ): Array; + symmetricDifferenceBy( + iteratee: ValueOnlyIteratee + ): ((a1: Array) => (a2: Array) => Array) & + ((a1: Array, a2: Array) => Array); + symmetricDifferenceBy( + iteratee: ValueOnlyIteratee, + a1: Array + ): (a2: Array) => Array; + symmetricDifferenceBy( + iteratee: ValueOnlyIteratee, + a1: Array, + a2: Array + ): Array; + xorWith( + comparator: Comparator + ): ((a1: Array) => (a2: Array) => Array) & + ((a1: Array, a2: Array) => Array); + xorWith( + comparator: Comparator, + a1: Array + ): (a2: Array) => Array; + xorWith(comparator: Comparator, a1: Array, a2: Array): Array; + symmetricDifferenceWith( + comparator: Comparator + ): ((a1: Array) => (a2: Array) => Array) & + ((a1: Array, a2: Array) => Array); + symmetricDifferenceWith( + comparator: Comparator, + a1: Array + ): (a2: Array) => Array; + symmetricDifferenceWith( + comparator: Comparator, + a1: Array, + a2: Array + ): Array; + zip(a1: A[]): (a2: B[]) => Array<[A, B]>; + zip(a1: A[], a2: B[]): Array<[A, B]>; + zipAll(arrays: Array>): Array; + zipObject(props?: Array): (values?: Array) => { [key: K]: V }; + zipObject(props?: Array, values?: Array): { [key: K]: V }; + zipObj(props: Array): (values: Array) => Object; + zipObj(props: Array, values: Array): Object; + zipObjectDeep(props: any[]): (values: any) => Object; + zipObjectDeep(props: any[], values: any): Object; + zipWith( + iteratee: Iteratee + ): ((a1: NestedArray) => (a2: NestedArray) => Array) & + ((a1: NestedArray, a2: NestedArray) => Array); + zipWith( + iteratee: Iteratee, + a1: NestedArray + ): (a2: NestedArray) => Array; + zipWith( + iteratee: Iteratee, + a1: NestedArray, + a2: NestedArray + ): Array; + // Collection + countBy( + iteratee: ValueOnlyIteratee + ): (collection: Array | { [id: any]: T }) => { [string]: number }; + countBy( + iteratee: ValueOnlyIteratee, + collection: Array | { [id: any]: T } + ): { [string]: number }; + // alias of _.forEach + each( + iteratee: Iteratee | OIteratee + ): (collection: Array | { [id: any]: T }) => Array; + each( + iteratee: Iteratee | OIteratee, + collection: Array | { [id: any]: T } + ): Array; + // alias of _.forEachRight + eachRight( + iteratee: Iteratee | OIteratee + ): (collection: Array | { [id: any]: T }) => Array; + eachRight( + iteratee: Iteratee | OIteratee, + collection: Array | { [id: any]: T } + ): Array; + every( + iteratee: Iteratee | OIteratee + ): (collection: Array | { [id: any]: T }) => boolean; + every( + iteratee: Iteratee | OIteratee, + collection: Array | { [id: any]: T } + ): boolean; + all( + iteratee: Iteratee | OIteratee + ): (collection: Array | { [id: any]: T }) => boolean; + all( + iteratee: Iteratee | OIteratee, + collection: Array | { [id: any]: T } + ): boolean; + filter( + predicate: Predicate | OPredicate + ): (collection: Array | { [id: any]: T }) => Array; + filter( + predicate: Predicate | OPredicate, + collection: Array | { [id: any]: T } + ): Array; + find( + predicate: Predicate | OPredicate + ): (collection: $ReadOnlyArray | { [id: any]: T }) => T | void; + find( + predicate: Predicate | OPredicate, + collection: $ReadOnlyArray | { [id: any]: T } + ): T | void; + findFrom( + predicate: Predicate | OPredicate + ): (( + fromIndex: number + ) => (collection: $ReadOnlyArray | { [id: any]: T }) => T | void) & + (( + fromIndex: number, + collection: $ReadOnlyArray | { [id: any]: T } + ) => T | void); + findFrom( + predicate: Predicate | OPredicate, + fromIndex: number + ): (collection: Array | { [id: any]: T }) => T | void; + findFrom( + predicate: Predicate | OPredicate, + fromIndex: number, + collection: $ReadOnlyArray | { [id: any]: T } + ): T | void; + findLast( + predicate: Predicate | OPredicate + ): (collection: $ReadOnlyArray | { [id: any]: T }) => T | void; + findLast( + predicate: Predicate | OPredicate, + collection: $ReadOnlyArray | { [id: any]: T } + ): T | void; + findLastFrom( + predicate: Predicate | OPredicate + ): (( + fromIndex: number + ) => (collection: $ReadOnlyArray | { [id: any]: T }) => T | void) & + (( + fromIndex: number, + collection: $ReadOnlyArray | { [id: any]: T } + ) => T | void); + findLastFrom( + predicate: Predicate | OPredicate, + fromIndex: number + ): (collection: $ReadOnlyArray | { [id: any]: T }) => T | void; + findLastFrom( + predicate: Predicate | OPredicate, + fromIndex: number, + collection: $ReadOnlyArray | { [id: any]: T } + ): T | void; + flatMap( + iteratee: FlatMapIteratee | OFlatMapIteratee + ): (collection: Array | { [id: any]: T }) => Array; + flatMap( + iteratee: FlatMapIteratee | OFlatMapIteratee, + collection: Array | { [id: any]: T } + ): Array; + flatMapDeep( + iteratee: FlatMapIteratee | OFlatMapIteratee + ): (collection: Array | { [id: any]: T }) => Array; + flatMapDeep( + iteratee: FlatMapIteratee | OFlatMapIteratee, + collection: Array | { [id: any]: T } + ): Array; + flatMapDepth( + iteratee: FlatMapIteratee | OFlatMapIteratee + ): (( + depth: number + ) => (collection: Array | { [id: any]: T }) => Array) & + ((depth: number, collection: Array | { [id: any]: T }) => Array); + flatMapDepth( + iteratee: FlatMapIteratee | OFlatMapIteratee, + depth: number + ): (collection: Array | { [id: any]: T }) => Array; + flatMapDepth( + iteratee: FlatMapIteratee | OFlatMapIteratee, + depth: number, + collection: Array | { [id: any]: T } + ): Array; + forEach( + iteratee: Iteratee | OIteratee + ): (collection: Array | { [id: any]: T }) => Array; + forEach( + iteratee: Iteratee | OIteratee, + collection: Array | { [id: any]: T } + ): Array; + forEachRight( + iteratee: Iteratee | OIteratee + ): (collection: Array | { [id: any]: T }) => Array; + forEachRight( + iteratee: Iteratee | OIteratee, + collection: Array | { [id: any]: T } + ): Array; + groupBy( + iteratee: ValueOnlyIteratee + ): (collection: Array | { [id: any]: T }) => { [key: V]: Array }; + groupBy( + iteratee: ValueOnlyIteratee, + collection: Array | { [id: any]: T } + ): { [key: V]: Array }; + includes(value: string): (str: string) => boolean; + includes(value: string, str: string): boolean; + includes(value: T): (collection: Array | { [id: any]: T }) => boolean; + includes(value: T, collection: Array | { [id: any]: T }): boolean; + contains(value: string): (str: string) => boolean; + contains(value: string, str: string): boolean; + contains(value: T): (collection: Array | { [id: any]: T }) => boolean; + contains(value: T, collection: Array | { [id: any]: T }): boolean; + includesFrom( + value: string + ): ((fromIndex: number) => (str: string) => boolean) & + ((fromIndex: number, str: string) => boolean); + includesFrom(value: string, fromIndex: number): (str: string) => boolean; + includesFrom(value: string, fromIndex: number, str: string): boolean; + includesFrom( + value: T + ): ((fromIndex: number) => (collection: Array) => boolean) & + ((fromIndex: number, collection: Array) => boolean); + includesFrom( + value: T, + fromIndex: number + ): (collection: Array) => boolean; + includesFrom(value: T, fromIndex: number, collection: Array): boolean; + invokeMap( + path: ((value: T) => Array | string) | Array | string + ): (collection: Array | { [id: any]: T }) => Array; + invokeMap( + path: ((value: T) => Array | string) | Array | string, + collection: Array | { [id: any]: T } + ): Array; + invokeArgsMap( + path: ((value: T) => Array | string) | Array | string + ): (( + collection: Array | { [id: any]: T } + ) => (args: Array) => Array) & + (( + collection: Array | { [id: any]: T }, + args: Array + ) => Array); + invokeArgsMap( + path: ((value: T) => Array | string) | Array | string, + collection: Array | { [id: any]: T } + ): (args: Array) => Array; + invokeArgsMap( + path: ((value: T) => Array | string) | Array | string, + collection: Array | { [id: any]: T }, + args: Array + ): Array; + keyBy( + iteratee: ValueOnlyIteratee + ): (collection: Array | { [id: any]: T }) => { [key: V]: T }; + keyBy( + iteratee: ValueOnlyIteratee, + collection: Array | { [id: any]: T } + ): { [key: V]: T }; + indexBy( + iteratee: ValueOnlyIteratee + ): (collection: Array | { [id: any]: T }) => { [key: V]: T }; + indexBy( + iteratee: ValueOnlyIteratee, + collection: Array | { [id: any]: T } + ): { [key: V]: T }; + map( + iteratee: MapIterator | OMapIterator + ): (collection: Array | { [id: any]: T }) => Array; + map( + iteratee: MapIterator | OMapIterator, + collection: Array | { [id: any]: T } + ): Array; + map(iteratee: (char: string) => any): (str: string) => string; + map(iteratee: (char: string) => any, str: string): string; + pluck( + iteratee: MapIterator | OMapIterator + ): (collection: Array | { [id: any]: T }) => Array; + pluck( + iteratee: MapIterator | OMapIterator, + collection: Array | { [id: any]: T } + ): Array; + pluck(iteratee: (char: string) => any): (str: string) => string; + pluck(iteratee: (char: string) => any, str: string): string; + orderBy( + iteratees: Array | OIteratee<*>> | string + ): (( + orders: Array<"asc" | "desc"> | string + ) => (collection: Array | { [id: any]: T }) => Array) & + (( + orders: Array<"asc" | "desc"> | string, + collection: Array | { [id: any]: T } + ) => Array); + orderBy( + iteratees: Array | OIteratee<*>> | string, + orders: Array<"asc" | "desc"> | string + ): (collection: Array | { [id: any]: T }) => Array; + orderBy( + iteratees: Array | OIteratee<*>> | string, + orders: Array<"asc" | "desc"> | string, + collection: Array | { [id: any]: T } + ): Array; + partition( + predicate: Predicate | OPredicate + ): (collection: Array | { [id: any]: T }) => [Array, Array]; + partition( + predicate: Predicate | OPredicate, + collection: Array | { [id: any]: T } + ): [Array, Array]; + reduce( + iteratee: (accumulator: U, value: T) => U + ): ((accumulator: U) => (collection: Array | { [id: any]: T }) => U) & + ((accumulator: U, collection: Array | { [id: any]: T }) => U); + reduce( + iteratee: (accumulator: U, value: T) => U, + accumulator: U + ): (collection: Array | { [id: any]: T }) => U; + reduce( + iteratee: (accumulator: U, value: T) => U, + accumulator: U, + collection: Array | { [id: any]: T } + ): U; + reduceRight( + iteratee: (value: T, accumulator: U) => U + ): ((accumulator: U) => (collection: Array | { [id: any]: T }) => U) & + ((accumulator: U, collection: Array | { [id: any]: T }) => U); + reduceRight( + iteratee: (value: T, accumulator: U) => U, + accumulator: U + ): (collection: Array | { [id: any]: T }) => U; + reduceRight( + iteratee: (value: T, accumulator: U) => U, + accumulator: U, + collection: Array | { [id: any]: T } + ): U; + reject( + predicate: Predicate | OPredicate + ): (collection: Array | { [id: any]: T }) => Array; + reject( + predicate: Predicate | OPredicate, + collection: Array | { [id: any]: T } + ): Array; + sample(collection: Array | { [id: any]: T }): T; + sampleSize( + n: number + ): (collection: Array | { [id: any]: T }) => Array; + sampleSize(n: number, collection: Array | { [id: any]: T }): Array; + shuffle(collection: Array | { [id: any]: T }): Array; + size(collection: Array | Object): number; + some( + predicate: Predicate | OPredicate + ): (collection: Array | { [id: any]: T }) => boolean; + some( + predicate: Predicate | OPredicate, + collection: Array | { [id: any]: T } + ): boolean; + any( + predicate: Predicate | OPredicate + ): (collection: Array | { [id: any]: T }) => boolean; + any( + predicate: Predicate | OPredicate, + collection: Array | { [id: any]: T } + ): boolean; + sortBy( + iteratees: Array | OIteratee> | Iteratee | OIteratee + ): (collection: Array | { [id: any]: T }) => Array; + sortBy( + iteratees: Array | OIteratee> | Iteratee | OIteratee, + collection: Array | { [id: any]: T } + ): Array; + + // Date + now(): number; + + // Function + after(fn: Function): (n: number) => Function; + after(fn: Function, n: number): Function; + ary(func: Function): Function; + nAry(n: number): (func: Function) => Function; + nAry(n: number, func: Function): Function; + before(fn: Function): (n: number) => Function; + before(fn: Function, n: number): Function; + bind(func: Function): (thisArg: any) => Function; + bind(func: Function, thisArg: any): Function; + bindKey(obj: Object): (key: string) => Function; + bindKey(obj: Object, key: string): Function; + curry: Curry; + curryN(arity: number): (func: Function) => Function; + curryN(arity: number, func: Function): Function; + curryRight(func: Function): Function; + curryRightN(arity: number): (func: Function) => Function; + curryRightN(arity: number, func: Function): Function; + debounce(wait: number): (func: F) => F; + debounce(wait: number, func: F): F; + defer(func: Function): number; + delay(wait: number): (func: Function) => number; + delay(wait: number, func: Function): number; + flip(func: Function): Function; + memoize(func: F): F; + negate(predicate: Function): Function; + complement(predicate: Function): Function; + once(func: Function): Function; + overArgs(func: Function): (transforms: Array) => Function; + overArgs(func: Function, transforms: Array): Function; + useWith(func: Function): (transforms: Array) => Function; + useWith(func: Function, transforms: Array): Function; + partial(func: Function): (partials: any[]) => Function; + partial(func: Function, partials: any[]): Function; + partialRight(func: Function): (partials: Array) => Function; + partialRight(func: Function, partials: Array): Function; + rearg(indexes: Array): (func: Function) => Function; + rearg(indexes: Array, func: Function): Function; + rest(func: Function): Function; + unapply(func: Function): Function; + restFrom(start: number): (func: Function) => Function; + restFrom(start: number, func: Function): Function; + spread(func: Function): Function; + apply(func: Function): Function; + spreadFrom(start: number): (func: Function) => Function; + spreadFrom(start: number, func: Function): Function; + throttle(wait: number): (func: Function) => Function; + throttle(wait: number, func: Function): Function; + unary(func: Function): Function; + wrap(wrapper: Function): (value: any) => Function; + wrap(wrapper: Function, value: any): Function; + + // Lang + castArray(value: *): any[]; + clone(value: T): T; + cloneDeep(value: T): T; + cloneDeepWith( + customizer: (value: T, key: number | string, object: T, stack: any) => U + ): (value: T) => U; + cloneDeepWith( + customizer: (value: T, key: number | string, object: T, stack: any) => U, + value: T + ): U; + cloneWith( + customizer: (value: T, key: number | string, object: T, stack: any) => U + ): (value: T) => U; + cloneWith( + customizer: (value: T, key: number | string, object: T, stack: any) => U, + value: T + ): U; + conformsTo( + predicates: T & { [key: string]: (x: any) => boolean } + ): (source: T) => boolean; + conformsTo( + predicates: T & { [key: string]: (x: any) => boolean }, + source: T + ): boolean; + where( + predicates: T & { [key: string]: (x: any) => boolean } + ): (source: T) => boolean; + where( + predicates: T & { [key: string]: (x: any) => boolean }, + source: T + ): boolean; + conforms( + predicates: T & { [key: string]: (x: any) => boolean } + ): (source: T) => boolean; + conforms( + predicates: T & { [key: string]: (x: any) => boolean }, + source: T + ): boolean; + eq(value: any): (other: any) => boolean; + eq(value: any, other: any): boolean; + identical(value: any): (other: any) => boolean; + identical(value: any, other: any): boolean; + gt(value: any): (other: any) => boolean; + gt(value: any, other: any): boolean; + gte(value: any): (other: any) => boolean; + gte(value: any, other: any): boolean; + isArguments(value: any): boolean; + isArray(value: any): boolean; + isArrayBuffer(value: any): boolean; + isArrayLike(value: any): boolean; + isArrayLikeObject(value: any): boolean; + isBoolean(value: any): boolean; + isBuffer(value: any): boolean; + isDate(value: any): boolean; + isElement(value: any): boolean; + isEmpty(value: any): boolean; + isEqual(value: any): (other: any) => boolean; + isEqual(value: any, other: any): boolean; + equals(value: any): (other: any) => boolean; + equals(value: any, other: any): boolean; + isEqualWith( + customizer: ( + objValue: any, + otherValue: any, + key: number | string, + object: T, + other: U, + stack: any + ) => boolean | void + ): ((value: T) => (other: U) => boolean) & + ((value: T, other: U) => boolean); + isEqualWith( + customizer: ( + objValue: any, + otherValue: any, + key: number | string, + object: T, + other: U, + stack: any + ) => boolean | void, + value: T + ): (other: U) => boolean; + isEqualWith( + customizer: ( + objValue: any, + otherValue: any, + key: number | string, + object: T, + other: U, + stack: any + ) => boolean | void, + value: T, + other: U + ): boolean; + isError(value: any): boolean; + isFinite(value: any): boolean; + isFunction(value: Function): true; + isFunction(value: number | string | void | null | Object): false; + isInteger(value: any): boolean; + isLength(value: any): boolean; + isMap(value: any): boolean; + isMatch(source: Object): (object: Object) => boolean; + isMatch(source: Object, object: Object): boolean; + whereEq(source: Object): (object: Object) => boolean; + whereEq(source: Object, object: Object): boolean; + isMatchWith( + customizer: ( + objValue: any, + srcValue: any, + key: number | string, + object: T, + source: U + ) => boolean | void + ): ((source: U) => (object: T) => boolean) & + ((source: U, object: T) => boolean); + isMatchWith( + customizer: ( + objValue: any, + srcValue: any, + key: number | string, + object: T, + source: U + ) => boolean | void, + source: U + ): (object: T) => boolean; + isMatchWith( + customizer: ( + objValue: any, + srcValue: any, + key: number | string, + object: T, + source: U + ) => boolean | void, + source: U, + object: T + ): boolean; + isNaN(value: any): boolean; + isNative(value: any): boolean; + isNil(value: any): boolean; + isNull(value: any): boolean; + isNumber(value: any): boolean; + isObject(value: any): boolean; + isObjectLike(value: any): boolean; + isPlainObject(value: any): boolean; + isRegExp(value: any): boolean; + isSafeInteger(value: any): boolean; + isSet(value: any): boolean; + isString(value: string): true; + isString( + value: number | boolean | Function | void | null | Object | Array + ): false; + isSymbol(value: any): boolean; + isTypedArray(value: any): boolean; + isUndefined(value: any): boolean; + isWeakMap(value: any): boolean; + isWeakSet(value: any): boolean; + lt(value: any): (other: any) => boolean; + lt(value: any, other: any): boolean; + lte(value: any): (other: any) => boolean; + lte(value: any, other: any): boolean; + toArray(value: any): Array; + toFinite(value: any): number; + toInteger(value: any): number; + toLength(value: any): number; + toNumber(value: any): number; + toPlainObject(value: any): Object; + toSafeInteger(value: any): number; + toString(value: any): string; + + // Math + add(augend: number): (addend: number) => number; + add(augend: number, addend: number): number; + ceil(number: number): number; + divide(dividend: number): (divisor: number) => number; + divide(dividend: number, divisor: number): number; + floor(number: number): number; + max(array: Array): T; + maxBy(iteratee: Iteratee): (array: Array) => T; + maxBy(iteratee: Iteratee, array: Array): T; + mean(array: Array<*>): number; + meanBy(iteratee: Iteratee): (array: Array) => number; + meanBy(iteratee: Iteratee, array: Array): number; + min(array: Array): T; + minBy(iteratee: Iteratee): (array: Array) => T; + minBy(iteratee: Iteratee, array: Array): T; + multiply(multiplier: number): (multiplicand: number) => number; + multiply(multiplier: number, multiplicand: number): number; + round(number: number): number; + subtract(minuend: number): (subtrahend: number) => number; + subtract(minuend: number, subtrahend: number): number; + sum(array: Array<*>): number; + sumBy(iteratee: Iteratee): (array: Array) => number; + sumBy(iteratee: Iteratee, array: Array): number; + + // number + clamp( + lower: number + ): ((upper: number) => (number: number) => number) & + ((upper: number, number: number) => number); + clamp(lower: number, upper: number): (number: number) => number; + clamp(lower: number, upper: number, number: number): number; + inRange( + start: number + ): ((end: number) => (number: number) => boolean) & + ((end: number, number: number) => boolean); + inRange(start: number, end: number): (number: number) => boolean; + inRange(start: number, end: number, number: number): boolean; + random(lower: number): (upper: number) => number; + random(lower: number, upper: number): number; + + // Object + assign(object: Object): (source: Object) => Object; + assign(object: Object, source: Object): Object; + assignAll(objects: Array): Object; + assignInAll(objects: Array): Object; + extendAll(objects: Array): Object; + assignIn(a: A): (b: B) => A & B; + assignIn(a: A, b: B): A & B; + assignInWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void + ): ((object: T) => (s1: A) => Object) & ((object: T, s1: A) => Object); + assignInWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void, + object: T + ): (s1: A) => Object; + assignInWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void, + object: T, + s1: A + ): Object; + assignWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void + ): ((object: T) => (s1: A) => Object) & ((object: T, s1: A) => Object); + assignWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void, + object: T + ): (s1: A) => Object; + assignWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void, + object: T, + s1: A + ): Object; + assignInAllWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: Object, + source: Object + ) => any | void + ): (objects: Array) => Object; + assignInAllWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: Object, + source: Object + ) => any | void, + objects: Array + ): Object; + extendAllWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: Object, + source: Object + ) => any | void + ): (objects: Array) => Object; + extendAllWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: Object, + source: Object + ) => any | void, + objects: Array + ): Object; + assignAllWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: Object, + source: Object + ) => any | void + ): (objects: Array) => Object; + assignAllWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: Object, + source: Object + ) => any | void, + objects: Array + ): Object; + at(paths: Array): (object: Object) => Array; + at(paths: Array, object: Object): Array; + props(paths: Array): (object: Object) => Array; + props(paths: Array, object: Object): Array; + paths(paths: Array): (object: Object) => Array; + paths(paths: Array, object: Object): Array; + create(prototype: T): $Supertype; + defaults(source: Object): (object: Object) => Object; + defaults(source: Object, object: Object): Object; + defaultsAll(objects: Array): Object; + defaultsDeep(source: Object): (object: Object) => Object; + defaultsDeep(source: Object, object: Object): Object; + defaultsDeepAll(objects: Array): Object; + // alias for _.toPairs + entries(object: Object): Array<[string, any]>; + // alias for _.toPairsIn + entriesIn(object: Object): Array<[string, any]>; + // alias for _.assignIn + extend(a: A): (b: B) => A & B; + extend(a: A, b: B): A & B; + // alias for _.assignInWith + extendWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void + ): ((object: T) => (s1: A) => Object) & ((object: T, s1: A) => Object); + extendWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void, + object: T + ): (s1: A) => Object; + extendWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A + ) => any | void, + object: T, + s1: A + ): Object; + findKey( + predicate: OPredicate + ): (object: T) => string | void; + findKey( + predicate: OPredicate, + object: T + ): string | void; + findLastKey( + predicate: OPredicate + ): (object: T) => string | void; + findLastKey( + predicate: OPredicate, + object: T + ): string | void; + forIn(iteratee: OIteratee<*>): (object: Object) => Object; + forIn(iteratee: OIteratee<*>, object: Object): Object; + forInRight(iteratee: OIteratee<*>): (object: Object) => Object; + forInRight(iteratee: OIteratee<*>, object: Object): Object; + forOwn(iteratee: OIteratee<*>): (object: Object) => Object; + forOwn(iteratee: OIteratee<*>, object: Object): Object; + forOwnRight(iteratee: OIteratee<*>): (object: Object) => Object; + forOwnRight(iteratee: OIteratee<*>, object: Object): Object; + functions(object: Object): Array; + functionsIn(object: Object): Array; + get(path: Array | string): (object: Object | Array) => any; + get(path: Array | string, object: Object | Array): any; + prop(path: Array | string): (object: Object | Array) => any; + prop(path: Array | string, object: Object | Array): any; + path(path: Array | string): (object: Object | Array) => any; + path(path: Array | string, object: Object | Array): any; + getOr( + defaultValue: any + ): (( + path: Array | string + ) => (object: Object | Array) => any) & + ((path: Array | string, object: Object | Array) => any); + getOr( + defaultValue: any, + path: Array | string + ): (object: Object | Array) => any; + getOr( + defaultValue: any, + path: Array | string, + object: Object | Array + ): any; + propOr( + defaultValue: any + ): (( + path: Array | string + ) => (object: Object | Array) => any) & + ((path: Array | string, object: Object | Array) => any); + propOr( + defaultValue: any, + path: Array | string + ): (object: Object | Array) => any; + propOr( + defaultValue: any, + path: Array | string, + object: Object | Array + ): any; + pathOr( + defaultValue: any + ): (( + path: Array | string + ) => (object: Object | Array) => any) & + ((path: Array | string, object: Object | Array) => any); + pathOr( + defaultValue: any, + path: Array | string + ): (object: Object | Array) => any; + pathOr( + defaultValue: any, + path: Array | string, + object: Object | Array + ): any; + has(path: Array | string): (object: Object) => boolean; + has(path: Array | string, object: Object): boolean; + hasIn(path: Array | string): (object: Object) => boolean; + hasIn(path: Array | string, object: Object): boolean; + invert(object: Object): Object; + invertObj(object: Object): Object; + invertBy(iteratee: Function): (object: Object) => Object; + invertBy(iteratee: Function, object: Object): Object; + invoke(path: Array | string): (object: Object) => any; + invoke(path: Array | string, object: Object): any; + invokeArgs( + path: Array | string + ): ((object: Object) => (args: Array) => any) & + ((object: Object, args: Array) => any); + invokeArgs( + path: Array | string, + object: Object + ): (args: Array) => any; + invokeArgs( + path: Array | string, + object: Object, + args: Array + ): any; + keys(object: { [key: K]: any }): Array; + keys(object: Object): Array; + keysIn(object: Object): Array; + mapKeys(iteratee: OIteratee<*>): (object: Object) => Object; + mapKeys(iteratee: OIteratee<*>, object: Object): Object; + mapValues(iteratee: OIteratee<*>): (object: Object) => Object; + mapValues(iteratee: OIteratee<*>, object: Object): Object; + merge(object: Object): (source: Object) => Object; + merge(object: Object, source: Object): Object; + mergeAll(objects: Array): Object; + mergeWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B + ) => any | void + ): ((object: T) => (s1: A) => Object) & ((object: T, s1: A) => Object); + mergeWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B + ) => any | void, + object: T + ): (s1: A) => Object; + mergeWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: T, + source: A | B + ) => any | void, + object: T, + s1: A + ): Object; + mergeAllWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: Object, + source: Object + ) => any | void + ): (objects: Array) => Object; + mergeAllWith( + customizer: ( + objValue: any, + srcValue: any, + key: string, + object: Object, + source: Object + ) => any | void, + objects: Array + ): Object; + omit(props: Array): (object: Object) => Object; + omit(props: Array, object: Object): Object; + omitAll(props: Array): (object: Object) => Object; + omitAll(props: Array, object: Object): Object; + omitBy( + predicate: OPredicate + ): (object: T) => Object; + omitBy(predicate: OPredicate, object: T): Object; + pick(props: Array): (object: Object) => Object; + pick(props: Array, object: Object): Object; + pickAll(props: Array): (object: Object) => Object; + pickAll(props: Array, object: Object): Object; + pickBy( + predicate: OPredicate + ): (object: T) => Object; + pickBy(predicate: OPredicate, object: T): Object; + result(path: Array | string): (object: Object) => any; + result(path: Array | string, object: Object): any; + set( + path: Array | string + ): ((value: any) => (object: Object) => Object) & + ((value: any, object: Object) => Object); + set(path: Array | string, value: any): (object: Object) => Object; + set(path: Array | string, value: any, object: Object): Object; + assoc( + path: Array | string + ): ((value: any) => (object: Object) => Object) & + ((value: any, object: Object) => Object); + assoc(path: Array | string, value: any): (object: Object) => Object; + assoc(path: Array | string, value: any, object: Object): Object; + assocPath( + path: Array | string + ): ((value: any) => (object: Object) => Object) & + ((value: any, object: Object) => Object); + assocPath( + path: Array | string, + value: any + ): (object: Object) => Object; + assocPath(path: Array | string, value: any, object: Object): Object; + setWith( + customizer: (nsValue: any, key: string, nsObject: T) => any + ): (( + path: Array | string + ) => ((value: any) => (object: T) => Object) & + ((value: any, object: T) => Object)) & + ((path: Array | string, value: any) => (object: T) => Object) & + ((path: Array | string, value: any, object: T) => Object); + setWith( + customizer: (nsValue: any, key: string, nsObject: T) => any, + path: Array | string + ): ((value: any) => (object: T) => Object) & + ((value: any, object: T) => Object); + setWith( + customizer: (nsValue: any, key: string, nsObject: T) => any, + path: Array | string, + value: any + ): (object: T) => Object; + setWith( + customizer: (nsValue: any, key: string, nsObject: T) => any, + path: Array | string, + value: any, + object: T + ): Object; + toPairs(object: Object | Array<*>): Array<[string, any]>; + toPairsIn(object: Object): Array<[string, any]>; + transform( + iteratee: OIteratee<*> + ): ((accumulator: any) => (collection: Object | Array) => any) & + ((accumulator: any, collection: Object | Array) => any); + transform( + iteratee: OIteratee<*>, + accumulator: any + ): (collection: Object | Array) => any; + transform( + iteratee: OIteratee<*>, + accumulator: any, + collection: Object | Array + ): any; + unset(path: Array | string): (object: Object) => boolean; + unset(path: Array | string, object: Object): boolean; + dissoc(path: Array | string): (object: Object) => boolean; + dissoc(path: Array | string, object: Object): boolean; + dissocPath(path: Array | string): (object: Object) => boolean; + dissocPath(path: Array | string, object: Object): boolean; + update( + path: string[] | string + ): ((updater: Function) => (object: Object) => Object) & + ((updater: Function, object: Object) => Object); + update( + path: string[] | string, + updater: Function + ): (object: Object) => Object; + update(path: string[] | string, updater: Function, object: Object): Object; + updateWith( + customizer: Function + ): (( + path: string[] | string + ) => ((updater: Function) => (object: Object) => Object) & + ((updater: Function, object: Object) => Object)) & + (( + path: string[] | string, + updater: Function + ) => (object: Object) => Object) & + ((path: string[] | string, updater: Function, object: Object) => Object); + updateWith( + customizer: Function, + path: string[] | string + ): ((updater: Function) => (object: Object) => Object) & + ((updater: Function, object: Object) => Object); + updateWith( + customizer: Function, + path: string[] | string, + updater: Function + ): (object: Object) => Object; + updateWith( + customizer: Function, + path: string[] | string, + updater: Function, + object: Object + ): Object; + values(object: Object): Array; + valuesIn(object: Object): Array; + + tap(interceptor: (value: T) => any): (value: T) => T; + tap(interceptor: (value: T) => any, value: T): T; + thru(interceptor: (value: T1) => T2): (value: T1) => T2; + thru(interceptor: (value: T1) => T2, value: T1): T2; + + // String + camelCase(string: string): string; + capitalize(string: string): string; + deburr(string: string): string; + endsWith(target: string): (string: string) => boolean; + endsWith(target: string, string: string): boolean; + escape(string: string): string; + escapeRegExp(string: string): string; + kebabCase(string: string): string; + lowerCase(string: string): string; + lowerFirst(string: string): string; + pad(length: number): (string: string) => string; + pad(length: number, string: string): string; + padChars( + chars: string + ): ((length: number) => (string: string) => string) & + ((length: number, string: string) => string); + padChars(chars: string, length: number): (string: string) => string; + padChars(chars: string, length: number, string: string): string; + padEnd(length: number): (string: string) => string; + padEnd(length: number, string: string): string; + padCharsEnd( + chars: string + ): ((length: number) => (string: string) => string) & + ((length: number, string: string) => string); + padCharsEnd(chars: string, length: number): (string: string) => string; + padCharsEnd(chars: string, length: number, string: string): string; + padStart(length: number): (string: string) => string; + padStart(length: number, string: string): string; + padCharsStart( + chars: string + ): ((length: number) => (string: string) => string) & + ((length: number, string: string) => string); + padCharsStart(chars: string, length: number): (string: string) => string; + padCharsStart(chars: string, length: number, string: string): string; + parseInt(radix: number): (string: string) => number; + parseInt(radix: number, string: string): number; + repeat(n: number): (string: string) => string; + repeat(n: number, string: string): string; + replace( + pattern: RegExp | string + ): (( + replacement: ((string: string) => string) | string + ) => (string: string) => string) & + (( + replacement: ((string: string) => string) | string, + string: string + ) => string); + replace( + pattern: RegExp | string, + replacement: ((string: string) => string) | string + ): (string: string) => string; + replace( + pattern: RegExp | string, + replacement: ((string: string) => string) | string, + string: string + ): string; + snakeCase(string: string): string; + split(separator: RegExp | string): (string: string) => Array; + split(separator: RegExp | string, string: string): Array; + startCase(string: string): string; + startsWith(target: string): (string: string) => boolean; + startsWith(target: string, string: string): boolean; + template(string: string): Function; + toLower(string: string): string; + toUpper(string: string): string; + trim(string: string): string; + trimChars(chars: string): (string: string) => string; + trimChars(chars: string, string: string): string; + trimEnd(string: string): string; + trimCharsEnd(chars: string): (string: string) => string; + trimCharsEnd(chars: string, string: string): string; + trimStart(string: string): string; + trimCharsStart(chars: string): (string: string) => string; + trimCharsStart(chars: string, string: string): string; + truncate(options: TruncateOptions): (string: string) => string; + truncate(options: TruncateOptions, string: string): string; + unescape(string: string): string; + upperCase(string: string): string; + upperFirst(string: string): string; + words(string: string): Array; + + // Util + attempt(func: Function): any; + bindAll(methodNames: Array): (object: Object) => Object; + bindAll(methodNames: Array, object: Object): Object; + cond(pairs: NestedArray): Function; + constant(value: T): () => T; + always(value: T): () => T; + defaultTo( + defaultValue: T2 + ): (value: T1) => T1; + defaultTo( + defaultValue: T2, + value: T1 + ): T1; + // NaN is a number instead of its own type, otherwise it would behave like null/void + defaultTo(defaultValue: T2): (value: T1) => T1 | T2; + defaultTo(defaultValue: T2, value: T1): T1 | T2; + defaultTo(defaultValue: T2): (value: T1) => T2; + defaultTo(defaultValue: T2, value: T1): T2; + flow: $ComposeReverse; + flow(funcs: Array): Function; + pipe: $ComposeReverse; + pipe(funcs: Array): Function; + flowRight: $Compose; + flowRight(funcs: Array): Function; + compose: $Compose; + compose(funcs: Array): Function; + identity(value: T): T; + iteratee(func: any): Function; + matches(source: Object): (object: Object) => boolean; + matches(source: Object, object: Object): boolean; + matchesProperty(path: Array | string): (srcValue: any) => Function; + matchesProperty(path: Array | string, srcValue: any): Function; + propEq(path: Array | string): (srcValue: any) => Function; + propEq(path: Array | string, srcValue: any): Function; + pathEq(path: Array | string): (srcValue: any) => Function; + pathEq(path: Array | string, srcValue: any): Function; + method(path: Array | string): Function; + methodOf(object: Object): Function; + mixin( + object: T + ): ((source: Object) => (options: { chain: boolean }) => T) & + ((source: Object, options: { chain: boolean }) => T); + mixin( + object: T, + source: Object + ): (options: { chain: boolean }) => T; + mixin( + object: T, + source: Object, + options: { chain: boolean } + ): T; + noConflict(): Lodash; + noop(...args: Array): void; + nthArg(n: number): Function; + over(iteratees: Array): Function; + juxt(iteratees: Array): Function; + overEvery(predicates: Array): Function; + allPass(predicates: Array): Function; + overSome(predicates: Array): Function; + anyPass(predicates: Array): Function; + property( + path: Array | string + ): (object: Object | Array) => any; + property(path: Array | string, object: Object | Array): any; + propertyOf(object: Object): (path: Array | string) => Function; + propertyOf(object: Object, path: Array | string): Function; + range(start: number): (end: number) => Array; + range(start: number, end: number): Array; + rangeStep( + step: number + ): ((start: number) => (end: number) => Array) & + ((start: number, end: number) => Array); + rangeStep(step: number, start: number): (end: number) => Array; + rangeStep(step: number, start: number, end: number): Array; + rangeRight(start: number): (end: number) => Array; + rangeRight(start: number, end: number): Array; + rangeStepRight( + step: number + ): ((start: number) => (end: number) => Array) & + ((start: number, end: number) => Array); + rangeStepRight(step: number, start: number): (end: number) => Array; + rangeStepRight(step: number, start: number, end: number): Array; + runInContext(context: Object): Function; + + stubArray(): Array<*>; + stubFalse(): false; + F(): false; + stubObject(): {}; + stubString(): ""; + stubTrue(): true; + T(): true; + times(iteratee: (i: number) => T): (n: number) => Array; + times(iteratee: (i: number) => T, n: number): Array; + toPath(value: any): Array; + uniqueId(prefix: string): string; + + __: any; + placeholder: any; + + convert(options: { + cap?: boolean, + curry?: boolean, + fixed?: boolean, + immutable?: boolean, + rearg?: boolean + }): void; + + // Properties + VERSION: string; + templateSettings: TemplateSettings; + } + + declare module.exports: Lodash; +} + +declare module "lodash/chunk" { + declare module.exports: $PropertyType<$Exports<"lodash">, "chunk">; +} + +declare module "lodash/compact" { + declare module.exports: $PropertyType<$Exports<"lodash">, "compact">; +} + +declare module "lodash/concat" { + declare module.exports: $PropertyType<$Exports<"lodash">, "concat">; +} + +declare module "lodash/difference" { + declare module.exports: $PropertyType<$Exports<"lodash">, "difference">; +} + +declare module "lodash/differenceBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "differenceBy">; +} + +declare module "lodash/differenceWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "differenceWith">; +} + +declare module "lodash/drop" { + declare module.exports: $PropertyType<$Exports<"lodash">, "drop">; +} + +declare module "lodash/dropRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "dropRight">; +} + +declare module "lodash/dropRightWhile" { + declare module.exports: $PropertyType<$Exports<"lodash">, "dropRightWhile">; +} + +declare module "lodash/dropWhile" { + declare module.exports: $PropertyType<$Exports<"lodash">, "dropWhile">; +} + +declare module "lodash/fill" { + declare module.exports: $PropertyType<$Exports<"lodash">, "fill">; +} + +declare module "lodash/findIndex" { + declare module.exports: $PropertyType<$Exports<"lodash">, "findIndex">; +} + +declare module "lodash/findLastIndex" { + declare module.exports: $PropertyType<$Exports<"lodash">, "findLastIndex">; +} + +declare module "lodash/first" { + declare module.exports: $PropertyType<$Exports<"lodash">, "first">; +} + +declare module "lodash/flatten" { + declare module.exports: $PropertyType<$Exports<"lodash">, "flatten">; +} + +declare module "lodash/flattenDeep" { + declare module.exports: $PropertyType<$Exports<"lodash">, "flattenDeep">; +} + +declare module "lodash/flattenDepth" { + declare module.exports: $PropertyType<$Exports<"lodash">, "flattenDepth">; +} + +declare module "lodash/fromPairs" { + declare module.exports: $PropertyType<$Exports<"lodash">, "fromPairs">; +} + +declare module "lodash/head" { + declare module.exports: $PropertyType<$Exports<"lodash">, "head">; +} + +declare module "lodash/indexOf" { + declare module.exports: $PropertyType<$Exports<"lodash">, "indexOf">; +} + +declare module "lodash/initial" { + declare module.exports: $PropertyType<$Exports<"lodash">, "initial">; +} + +declare module "lodash/intersection" { + declare module.exports: $PropertyType<$Exports<"lodash">, "intersection">; +} + +declare module "lodash/intersectionBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "intersectionBy">; +} + +declare module "lodash/intersectionWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "intersectionWith">; +} + +declare module "lodash/join" { + declare module.exports: $PropertyType<$Exports<"lodash">, "join">; +} + +declare module "lodash/last" { + declare module.exports: $PropertyType<$Exports<"lodash">, "last">; +} + +declare module "lodash/lastIndexOf" { + declare module.exports: $PropertyType<$Exports<"lodash">, "lastIndexOf">; +} + +declare module "lodash/nth" { + declare module.exports: $PropertyType<$Exports<"lodash">, "nth">; +} + +declare module "lodash/pull" { + declare module.exports: $PropertyType<$Exports<"lodash">, "pull">; +} + +declare module "lodash/pullAll" { + declare module.exports: $PropertyType<$Exports<"lodash">, "pullAll">; +} + +declare module "lodash/pullAllBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "pullAllBy">; +} + +declare module "lodash/pullAllWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "pullAllWith">; +} + +declare module "lodash/pullAt" { + declare module.exports: $PropertyType<$Exports<"lodash">, "pullAt">; +} + +declare module "lodash/remove" { + declare module.exports: $PropertyType<$Exports<"lodash">, "remove">; +} + +declare module "lodash/reverse" { + declare module.exports: $PropertyType<$Exports<"lodash">, "reverse">; +} + +declare module "lodash/slice" { + declare module.exports: $PropertyType<$Exports<"lodash">, "slice">; +} + +declare module "lodash/sortedIndex" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sortedIndex">; +} + +declare module "lodash/sortedIndexBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sortedIndexBy">; +} + +declare module "lodash/sortedIndexOf" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sortedIndexOf">; +} + +declare module "lodash/sortedLastIndex" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sortedLastIndex">; +} + +declare module "lodash/sortedLastIndexBy" { + declare module.exports: $PropertyType< + $Exports<"lodash">, + "sortedLastIndexBy" + >; +} + +declare module "lodash/sortedLastIndexOf" { + declare module.exports: $PropertyType< + $Exports<"lodash">, + "sortedLastIndexOf" + >; +} + +declare module "lodash/sortedUniq" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sortedUniq">; +} + +declare module "lodash/sortedUniqBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sortedUniqBy">; +} + +declare module "lodash/tail" { + declare module.exports: $PropertyType<$Exports<"lodash">, "tail">; +} + +declare module "lodash/take" { + declare module.exports: $PropertyType<$Exports<"lodash">, "take">; +} + +declare module "lodash/takeRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "takeRight">; +} + +declare module "lodash/takeRightWhile" { + declare module.exports: $PropertyType<$Exports<"lodash">, "takeRightWhile">; +} + +declare module "lodash/takeWhile" { + declare module.exports: $PropertyType<$Exports<"lodash">, "takeWhile">; +} + +declare module "lodash/union" { + declare module.exports: $PropertyType<$Exports<"lodash">, "union">; +} + +declare module "lodash/unionBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "unionBy">; +} + +declare module "lodash/unionWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "unionWith">; +} + +declare module "lodash/uniq" { + declare module.exports: $PropertyType<$Exports<"lodash">, "uniq">; +} + +declare module "lodash/uniqBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "uniqBy">; +} + +declare module "lodash/uniqWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "uniqWith">; +} + +declare module "lodash/unzip" { + declare module.exports: $PropertyType<$Exports<"lodash">, "unzip">; +} + +declare module "lodash/unzipWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "unzipWith">; +} + +declare module "lodash/without" { + declare module.exports: $PropertyType<$Exports<"lodash">, "without">; +} + +declare module "lodash/xor" { + declare module.exports: $PropertyType<$Exports<"lodash">, "xor">; +} + +declare module "lodash/xorBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "xorBy">; +} + +declare module "lodash/xorWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "xorWith">; +} + +declare module "lodash/zip" { + declare module.exports: $PropertyType<$Exports<"lodash">, "zip">; +} + +declare module "lodash/zipObject" { + declare module.exports: $PropertyType<$Exports<"lodash">, "zipObject">; +} + +declare module "lodash/zipObjectDeep" { + declare module.exports: $PropertyType<$Exports<"lodash">, "zipObjectDeep">; +} + +declare module "lodash/zipWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "zipWith">; +} + +declare module "lodash/countBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "countBy">; +} + +declare module "lodash/each" { + declare module.exports: $PropertyType<$Exports<"lodash">, "each">; +} + +declare module "lodash/eachRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "eachRight">; +} + +declare module "lodash/every" { + declare module.exports: $PropertyType<$Exports<"lodash">, "every">; +} + +declare module "lodash/filter" { + declare module.exports: $PropertyType<$Exports<"lodash">, "filter">; +} + +declare module "lodash/find" { + declare module.exports: $PropertyType<$Exports<"lodash">, "find">; +} + +declare module "lodash/findLast" { + declare module.exports: $PropertyType<$Exports<"lodash">, "findLast">; +} + +declare module "lodash/flatMap" { + declare module.exports: $PropertyType<$Exports<"lodash">, "flatMap">; +} + +declare module "lodash/flatMapDeep" { + declare module.exports: $PropertyType<$Exports<"lodash">, "flatMapDeep">; +} + +declare module "lodash/flatMapDepth" { + declare module.exports: $PropertyType<$Exports<"lodash">, "flatMapDepth">; +} + +declare module "lodash/forEach" { + declare module.exports: $PropertyType<$Exports<"lodash">, "forEach">; +} + +declare module "lodash/forEachRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "forEachRight">; +} + +declare module "lodash/groupBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "groupBy">; +} + +declare module "lodash/includes" { + declare module.exports: $PropertyType<$Exports<"lodash">, "includes">; +} + +declare module "lodash/invokeMap" { + declare module.exports: $PropertyType<$Exports<"lodash">, "invokeMap">; +} + +declare module "lodash/keyBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "keyBy">; +} + +declare module "lodash/map" { + declare module.exports: $PropertyType<$Exports<"lodash">, "map">; +} + +declare module "lodash/orderBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "orderBy">; +} + +declare module "lodash/partition" { + declare module.exports: $PropertyType<$Exports<"lodash">, "partition">; +} + +declare module "lodash/reduce" { + declare module.exports: $PropertyType<$Exports<"lodash">, "reduce">; +} + +declare module "lodash/reduceRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "reduceRight">; +} + +declare module "lodash/reject" { + declare module.exports: $PropertyType<$Exports<"lodash">, "reject">; +} + +declare module "lodash/sample" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sample">; +} + +declare module "lodash/sampleSize" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sampleSize">; +} + +declare module "lodash/shuffle" { + declare module.exports: $PropertyType<$Exports<"lodash">, "shuffle">; +} + +declare module "lodash/size" { + declare module.exports: $PropertyType<$Exports<"lodash">, "size">; +} + +declare module "lodash/some" { + declare module.exports: $PropertyType<$Exports<"lodash">, "some">; +} + +declare module "lodash/sortBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sortBy">; +} + +declare module "lodash/now" { + declare module.exports: $PropertyType<$Exports<"lodash">, "now">; +} + +declare module "lodash/after" { + declare module.exports: $PropertyType<$Exports<"lodash">, "after">; +} + +declare module "lodash/ary" { + declare module.exports: $PropertyType<$Exports<"lodash">, "ary">; +} + +declare module "lodash/before" { + declare module.exports: $PropertyType<$Exports<"lodash">, "before">; +} + +declare module "lodash/bind" { + declare module.exports: $PropertyType<$Exports<"lodash">, "bind">; +} + +declare module "lodash/bindKey" { + declare module.exports: $PropertyType<$Exports<"lodash">, "bindKey">; +} + +declare module "lodash/curry" { + declare module.exports: $PropertyType<$Exports<"lodash">, "curry">; +} + +declare module "lodash/curryRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "curryRight">; +} + +declare module "lodash/debounce" { + declare module.exports: $PropertyType<$Exports<"lodash">, "debounce">; +} + +declare module "lodash/defer" { + declare module.exports: $PropertyType<$Exports<"lodash">, "defer">; +} + +declare module "lodash/delay" { + declare module.exports: $PropertyType<$Exports<"lodash">, "delay">; +} + +declare module "lodash/flip" { + declare module.exports: $PropertyType<$Exports<"lodash">, "flip">; +} + +declare module "lodash/memoize" { + declare module.exports: $PropertyType<$Exports<"lodash">, "memoize">; +} + +declare module "lodash/negate" { + declare module.exports: $PropertyType<$Exports<"lodash">, "negate">; +} + +declare module "lodash/once" { + declare module.exports: $PropertyType<$Exports<"lodash">, "once">; +} + +declare module "lodash/overArgs" { + declare module.exports: $PropertyType<$Exports<"lodash">, "overArgs">; +} + +declare module "lodash/partial" { + declare module.exports: $PropertyType<$Exports<"lodash">, "partial">; +} + +declare module "lodash/partialRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "partialRight">; +} + +declare module "lodash/rearg" { + declare module.exports: $PropertyType<$Exports<"lodash">, "rearg">; +} + +declare module "lodash/rest" { + declare module.exports: $PropertyType<$Exports<"lodash">, "rest">; +} + +declare module "lodash/spread" { + declare module.exports: $PropertyType<$Exports<"lodash">, "spread">; +} + +declare module "lodash/throttle" { + declare module.exports: $PropertyType<$Exports<"lodash">, "throttle">; +} + +declare module "lodash/unary" { + declare module.exports: $PropertyType<$Exports<"lodash">, "unary">; +} + +declare module "lodash/wrap" { + declare module.exports: $PropertyType<$Exports<"lodash">, "wrap">; +} + +declare module "lodash/castArray" { + declare module.exports: $PropertyType<$Exports<"lodash">, "castArray">; +} + +declare module "lodash/clone" { + declare module.exports: $PropertyType<$Exports<"lodash">, "clone">; +} + +declare module "lodash/cloneDeep" { + declare module.exports: $PropertyType<$Exports<"lodash">, "cloneDeep">; +} + +declare module "lodash/cloneDeepWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "cloneDeepWith">; +} + +declare module "lodash/cloneWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "cloneWith">; +} + +declare module "lodash/conformsTo" { + declare module.exports: $PropertyType<$Exports<"lodash">, "conformsTo">; +} + +declare module "lodash/eq" { + declare module.exports: $PropertyType<$Exports<"lodash">, "eq">; +} + +declare module "lodash/gt" { + declare module.exports: $PropertyType<$Exports<"lodash">, "gt">; +} + +declare module "lodash/gte" { + declare module.exports: $PropertyType<$Exports<"lodash">, "gte">; +} + +declare module "lodash/isArguments" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isArguments">; +} + +declare module "lodash/isArray" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isArray">; +} + +declare module "lodash/isArrayBuffer" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isArrayBuffer">; +} + +declare module "lodash/isArrayLike" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isArrayLike">; +} + +declare module "lodash/isArrayLikeObject" { + declare module.exports: $PropertyType< + $Exports<"lodash">, + "isArrayLikeObject" + >; +} + +declare module "lodash/isBoolean" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isBoolean">; +} + +declare module "lodash/isBuffer" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isBuffer">; +} + +declare module "lodash/isDate" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isDate">; +} + +declare module "lodash/isElement" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isElement">; +} + +declare module "lodash/isEmpty" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isEmpty">; +} + +declare module "lodash/isEqual" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isEqual">; +} + +declare module "lodash/isEqualWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isEqualWith">; +} + +declare module "lodash/isError" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isError">; +} + +declare module "lodash/isFinite" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isFinite">; +} + +declare module "lodash/isFunction" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isFunction">; +} + +declare module "lodash/isInteger" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isInteger">; +} + +declare module "lodash/isLength" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isLength">; +} + +declare module "lodash/isMap" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isMap">; +} + +declare module "lodash/isMatch" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isMatch">; +} + +declare module "lodash/isMatchWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isMatchWith">; +} + +declare module "lodash/isNaN" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isNaN">; +} + +declare module "lodash/isNative" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isNative">; +} + +declare module "lodash/isNil" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isNil">; +} + +declare module "lodash/isNull" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isNull">; +} + +declare module "lodash/isNumber" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isNumber">; +} + +declare module "lodash/isObject" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isObject">; +} + +declare module "lodash/isObjectLike" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isObjectLike">; +} + +declare module "lodash/isPlainObject" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isPlainObject">; +} + +declare module "lodash/isRegExp" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isRegExp">; +} + +declare module "lodash/isSafeInteger" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isSafeInteger">; +} + +declare module "lodash/isSet" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isSet">; +} + +declare module "lodash/isString" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isString">; +} + +declare module "lodash/isSymbol" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isSymbol">; +} + +declare module "lodash/isTypedArray" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isTypedArray">; +} + +declare module "lodash/isUndefined" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isUndefined">; +} + +declare module "lodash/isWeakMap" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isWeakMap">; +} + +declare module "lodash/isWeakSet" { + declare module.exports: $PropertyType<$Exports<"lodash">, "isWeakSet">; +} + +declare module "lodash/lt" { + declare module.exports: $PropertyType<$Exports<"lodash">, "lt">; +} + +declare module "lodash/lte" { + declare module.exports: $PropertyType<$Exports<"lodash">, "lte">; +} + +declare module "lodash/toArray" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toArray">; +} + +declare module "lodash/toFinite" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toFinite">; +} + +declare module "lodash/toInteger" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toInteger">; +} + +declare module "lodash/toLength" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toLength">; +} + +declare module "lodash/toNumber" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toNumber">; +} + +declare module "lodash/toPlainObject" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toPlainObject">; +} + +declare module "lodash/toSafeInteger" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toSafeInteger">; +} + +declare module "lodash/toString" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toString">; +} + +declare module "lodash/add" { + declare module.exports: $PropertyType<$Exports<"lodash">, "add">; +} + +declare module "lodash/ceil" { + declare module.exports: $PropertyType<$Exports<"lodash">, "ceil">; +} + +declare module "lodash/divide" { + declare module.exports: $PropertyType<$Exports<"lodash">, "divide">; +} + +declare module "lodash/floor" { + declare module.exports: $PropertyType<$Exports<"lodash">, "floor">; +} + +declare module "lodash/max" { + declare module.exports: $PropertyType<$Exports<"lodash">, "max">; +} + +declare module "lodash/maxBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "maxBy">; +} + +declare module "lodash/mean" { + declare module.exports: $PropertyType<$Exports<"lodash">, "mean">; +} + +declare module "lodash/meanBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "meanBy">; +} + +declare module "lodash/min" { + declare module.exports: $PropertyType<$Exports<"lodash">, "min">; +} + +declare module "lodash/minBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "minBy">; +} + +declare module "lodash/multiply" { + declare module.exports: $PropertyType<$Exports<"lodash">, "multiply">; +} + +declare module "lodash/round" { + declare module.exports: $PropertyType<$Exports<"lodash">, "round">; +} + +declare module "lodash/subtract" { + declare module.exports: $PropertyType<$Exports<"lodash">, "subtract">; +} + +declare module "lodash/sum" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sum">; +} + +declare module "lodash/sumBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "sumBy">; +} + +declare module "lodash/clamp" { + declare module.exports: $PropertyType<$Exports<"lodash">, "clamp">; +} + +declare module "lodash/inRange" { + declare module.exports: $PropertyType<$Exports<"lodash">, "inRange">; +} + +declare module "lodash/random" { + declare module.exports: $PropertyType<$Exports<"lodash">, "random">; +} + +declare module "lodash/assign" { + declare module.exports: $PropertyType<$Exports<"lodash">, "assign">; +} + +declare module "lodash/assignIn" { + declare module.exports: $PropertyType<$Exports<"lodash">, "assignIn">; +} + +declare module "lodash/assignInWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "assignInWith">; +} + +declare module "lodash/assignWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "assignWith">; +} + +declare module "lodash/at" { + declare module.exports: $PropertyType<$Exports<"lodash">, "at">; +} + +declare module "lodash/create" { + declare module.exports: $PropertyType<$Exports<"lodash">, "create">; +} + +declare module "lodash/defaults" { + declare module.exports: $PropertyType<$Exports<"lodash">, "defaults">; +} + +declare module "lodash/defaultsDeep" { + declare module.exports: $PropertyType<$Exports<"lodash">, "defaultsDeep">; +} + +declare module "lodash/entries" { + declare module.exports: $PropertyType<$Exports<"lodash">, "entries">; +} + +declare module "lodash/entriesIn" { + declare module.exports: $PropertyType<$Exports<"lodash">, "entriesIn">; +} + +declare module "lodash/extend" { + declare module.exports: $PropertyType<$Exports<"lodash">, "extend">; +} + +declare module "lodash/extendWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "extendWith">; +} + +declare module "lodash/findKey" { + declare module.exports: $PropertyType<$Exports<"lodash">, "findKey">; +} + +declare module "lodash/findLastKey" { + declare module.exports: $PropertyType<$Exports<"lodash">, "findLastKey">; +} + +declare module "lodash/forIn" { + declare module.exports: $PropertyType<$Exports<"lodash">, "forIn">; +} + +declare module "lodash/forInRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "forInRight">; +} + +declare module "lodash/forOwn" { + declare module.exports: $PropertyType<$Exports<"lodash">, "forOwn">; +} + +declare module "lodash/forOwnRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "forOwnRight">; +} + +declare module "lodash/functions" { + declare module.exports: $PropertyType<$Exports<"lodash">, "functions">; +} + +declare module "lodash/functionsIn" { + declare module.exports: $PropertyType<$Exports<"lodash">, "functionsIn">; +} + +declare module "lodash/get" { + declare module.exports: $PropertyType<$Exports<"lodash">, "get">; +} + +declare module "lodash/has" { + declare module.exports: $PropertyType<$Exports<"lodash">, "has">; +} + +declare module "lodash/hasIn" { + declare module.exports: $PropertyType<$Exports<"lodash">, "hasIn">; +} + +declare module "lodash/invert" { + declare module.exports: $PropertyType<$Exports<"lodash">, "invert">; +} + +declare module "lodash/invertBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "invertBy">; +} + +declare module "lodash/invoke" { + declare module.exports: $PropertyType<$Exports<"lodash">, "invoke">; +} + +declare module "lodash/keys" { + declare module.exports: $PropertyType<$Exports<"lodash">, "keys">; +} + +declare module "lodash/keysIn" { + declare module.exports: $PropertyType<$Exports<"lodash">, "keysIn">; +} + +declare module "lodash/mapKeys" { + declare module.exports: $PropertyType<$Exports<"lodash">, "mapKeys">; +} + +declare module "lodash/mapValues" { + declare module.exports: $PropertyType<$Exports<"lodash">, "mapValues">; +} + +declare module "lodash/merge" { + declare module.exports: $PropertyType<$Exports<"lodash">, "merge">; +} + +declare module "lodash/mergeWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "mergeWith">; +} + +declare module "lodash/omit" { + declare module.exports: $PropertyType<$Exports<"lodash">, "omit">; +} + +declare module "lodash/omitBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "omitBy">; +} + +declare module "lodash/pick" { + declare module.exports: $PropertyType<$Exports<"lodash">, "pick">; +} + +declare module "lodash/pickBy" { + declare module.exports: $PropertyType<$Exports<"lodash">, "pickBy">; +} + +declare module "lodash/result" { + declare module.exports: $PropertyType<$Exports<"lodash">, "result">; +} + +declare module "lodash/set" { + declare module.exports: $PropertyType<$Exports<"lodash">, "set">; +} + +declare module "lodash/setWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "setWith">; +} + +declare module "lodash/toPairs" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toPairs">; +} + +declare module "lodash/toPairsIn" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toPairsIn">; +} + +declare module "lodash/transform" { + declare module.exports: $PropertyType<$Exports<"lodash">, "transform">; +} + +declare module "lodash/unset" { + declare module.exports: $PropertyType<$Exports<"lodash">, "unset">; +} + +declare module "lodash/update" { + declare module.exports: $PropertyType<$Exports<"lodash">, "update">; +} + +declare module "lodash/updateWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "updateWith">; +} + +declare module "lodash/values" { + declare module.exports: $PropertyType<$Exports<"lodash">, "values">; +} + +declare module "lodash/valuesIn" { + declare module.exports: $PropertyType<$Exports<"lodash">, "valuesIn">; +} + +declare module "lodash/chain" { + declare module.exports: $PropertyType<$Exports<"lodash">, "chain">; +} + +declare module "lodash/tap" { + declare module.exports: $PropertyType<$Exports<"lodash">, "tap">; +} + +declare module "lodash/thru" { + declare module.exports: $PropertyType<$Exports<"lodash">, "thru">; +} + +declare module "lodash/camelCase" { + declare module.exports: $PropertyType<$Exports<"lodash">, "camelCase">; +} + +declare module "lodash/capitalize" { + declare module.exports: $PropertyType<$Exports<"lodash">, "capitalize">; +} + +declare module "lodash/deburr" { + declare module.exports: $PropertyType<$Exports<"lodash">, "deburr">; +} + +declare module "lodash/endsWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "endsWith">; +} + +declare module "lodash/escape" { + declare module.exports: $PropertyType<$Exports<"lodash">, "escape">; +} + +declare module "lodash/escapeRegExp" { + declare module.exports: $PropertyType<$Exports<"lodash">, "escapeRegExp">; +} + +declare module "lodash/kebabCase" { + declare module.exports: $PropertyType<$Exports<"lodash">, "kebabCase">; +} + +declare module "lodash/lowerCase" { + declare module.exports: $PropertyType<$Exports<"lodash">, "lowerCase">; +} + +declare module "lodash/lowerFirst" { + declare module.exports: $PropertyType<$Exports<"lodash">, "lowerFirst">; +} + +declare module "lodash/pad" { + declare module.exports: $PropertyType<$Exports<"lodash">, "pad">; +} + +declare module "lodash/padEnd" { + declare module.exports: $PropertyType<$Exports<"lodash">, "padEnd">; +} + +declare module "lodash/padStart" { + declare module.exports: $PropertyType<$Exports<"lodash">, "padStart">; +} + +declare module "lodash/parseInt" { + declare module.exports: $PropertyType<$Exports<"lodash">, "parseInt">; +} + +declare module "lodash/repeat" { + declare module.exports: $PropertyType<$Exports<"lodash">, "repeat">; +} + +declare module "lodash/replace" { + declare module.exports: $PropertyType<$Exports<"lodash">, "replace">; +} + +declare module "lodash/snakeCase" { + declare module.exports: $PropertyType<$Exports<"lodash">, "snakeCase">; +} + +declare module "lodash/split" { + declare module.exports: $PropertyType<$Exports<"lodash">, "split">; +} + +declare module "lodash/startCase" { + declare module.exports: $PropertyType<$Exports<"lodash">, "startCase">; +} + +declare module "lodash/startsWith" { + declare module.exports: $PropertyType<$Exports<"lodash">, "startsWith">; +} + +declare module "lodash/template" { + declare module.exports: $PropertyType<$Exports<"lodash">, "template">; +} + +declare module "lodash/toLower" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toLower">; +} + +declare module "lodash/toUpper" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toUpper">; +} + +declare module "lodash/trim" { + declare module.exports: $PropertyType<$Exports<"lodash">, "trim">; +} + +declare module "lodash/trimEnd" { + declare module.exports: $PropertyType<$Exports<"lodash">, "trimEnd">; +} + +declare module "lodash/trimStart" { + declare module.exports: $PropertyType<$Exports<"lodash">, "trimStart">; +} + +declare module "lodash/truncate" { + declare module.exports: $PropertyType<$Exports<"lodash">, "truncate">; +} + +declare module "lodash/unescape" { + declare module.exports: $PropertyType<$Exports<"lodash">, "unescape">; +} + +declare module "lodash/upperCase" { + declare module.exports: $PropertyType<$Exports<"lodash">, "upperCase">; +} + +declare module "lodash/upperFirst" { + declare module.exports: $PropertyType<$Exports<"lodash">, "upperFirst">; +} + +declare module "lodash/words" { + declare module.exports: $PropertyType<$Exports<"lodash">, "words">; +} + +declare module "lodash/attempt" { + declare module.exports: $PropertyType<$Exports<"lodash">, "attempt">; +} + +declare module "lodash/bindAll" { + declare module.exports: $PropertyType<$Exports<"lodash">, "bindAll">; +} + +declare module "lodash/cond" { + declare module.exports: $PropertyType<$Exports<"lodash">, "cond">; +} + +declare module "lodash/conforms" { + declare module.exports: $PropertyType<$Exports<"lodash">, "conforms">; +} + +declare module "lodash/constant" { + declare module.exports: $PropertyType<$Exports<"lodash">, "constant">; +} + +declare module "lodash/defaultTo" { + declare module.exports: $PropertyType<$Exports<"lodash">, "defaultTo">; +} + +declare module "lodash/flow" { + declare module.exports: $PropertyType<$Exports<"lodash">, "flow">; +} + +declare module "lodash/flowRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "flowRight">; +} + +declare module "lodash/identity" { + declare module.exports: $PropertyType<$Exports<"lodash">, "identity">; +} + +declare module "lodash/iteratee" { + declare module.exports: $PropertyType<$Exports<"lodash">, "iteratee">; +} + +declare module "lodash/matches" { + declare module.exports: $PropertyType<$Exports<"lodash">, "matches">; +} + +declare module "lodash/matchesProperty" { + declare module.exports: $PropertyType<$Exports<"lodash">, "matchesProperty">; +} + +declare module "lodash/method" { + declare module.exports: $PropertyType<$Exports<"lodash">, "method">; +} + +declare module "lodash/methodOf" { + declare module.exports: $PropertyType<$Exports<"lodash">, "methodOf">; +} + +declare module "lodash/mixin" { + declare module.exports: $PropertyType<$Exports<"lodash">, "mixin">; +} + +declare module "lodash/noConflict" { + declare module.exports: $PropertyType<$Exports<"lodash">, "noConflict">; +} + +declare module "lodash/noop" { + declare module.exports: $PropertyType<$Exports<"lodash">, "noop">; +} + +declare module "lodash/nthArg" { + declare module.exports: $PropertyType<$Exports<"lodash">, "nthArg">; +} + +declare module "lodash/over" { + declare module.exports: $PropertyType<$Exports<"lodash">, "over">; +} + +declare module "lodash/overEvery" { + declare module.exports: $PropertyType<$Exports<"lodash">, "overEvery">; +} + +declare module "lodash/overSome" { + declare module.exports: $PropertyType<$Exports<"lodash">, "overSome">; +} + +declare module "lodash/property" { + declare module.exports: $PropertyType<$Exports<"lodash">, "property">; +} + +declare module "lodash/propertyOf" { + declare module.exports: $PropertyType<$Exports<"lodash">, "propertyOf">; +} + +declare module "lodash/range" { + declare module.exports: $PropertyType<$Exports<"lodash">, "range">; +} + +declare module "lodash/rangeRight" { + declare module.exports: $PropertyType<$Exports<"lodash">, "rangeRight">; +} + +declare module "lodash/runInContext" { + declare module.exports: $PropertyType<$Exports<"lodash">, "runInContext">; +} + +declare module "lodash/stubArray" { + declare module.exports: $PropertyType<$Exports<"lodash">, "stubArray">; +} + +declare module "lodash/stubFalse" { + declare module.exports: $PropertyType<$Exports<"lodash">, "stubFalse">; +} + +declare module "lodash/stubObject" { + declare module.exports: $PropertyType<$Exports<"lodash">, "stubObject">; +} + +declare module "lodash/stubString" { + declare module.exports: $PropertyType<$Exports<"lodash">, "stubString">; +} + +declare module "lodash/stubTrue" { + declare module.exports: $PropertyType<$Exports<"lodash">, "stubTrue">; +} + +declare module "lodash/times" { + declare module.exports: $PropertyType<$Exports<"lodash">, "times">; +} + +declare module "lodash/toPath" { + declare module.exports: $PropertyType<$Exports<"lodash">, "toPath">; +} + +declare module "lodash/uniqueId" { + declare module.exports: $PropertyType<$Exports<"lodash">, "uniqueId">; +} diff --git a/flow-typed/npm/openssl-wrapper_vx.x.x.js b/flow-typed/npm/openssl-wrapper_vx.x.x.js new file mode 100644 index 000000000..4b4c2438c --- /dev/null +++ b/flow-typed/npm/openssl-wrapper_vx.x.x.js @@ -0,0 +1,46 @@ +// flow-typed signature: de535bf82f2186287df01b1d05ef7355 +// flow-typed version: <>/openssl-wrapper_v0.3.4/flow_v0.69.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'openssl-wrapper' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'openssl-wrapper' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'openssl-wrapper/lib/index' { + declare module.exports: any; +} + +declare module 'openssl-wrapper/src/index' { + declare module.exports: any; +} + +declare module 'openssl-wrapper/test/index.spec' { + declare module.exports: any; +} + +// Filename aliases +declare module 'openssl-wrapper/lib/index.js' { + declare module.exports: $Exports<'openssl-wrapper/lib/index'>; +} +declare module 'openssl-wrapper/src/index.js' { + declare module.exports: $Exports<'openssl-wrapper/src/index'>; +} +declare module 'openssl-wrapper/test/index.spec.js' { + declare module.exports: $Exports<'openssl-wrapper/test/index.spec'>; +} diff --git a/flow-typed/npm/polished_vx.x.x.js b/flow-typed/npm/polished_vx.x.x.js new file mode 100644 index 000000000..3b9d63d2d --- /dev/null +++ b/flow-typed/npm/polished_vx.x.x.js @@ -0,0 +1,585 @@ +// flow-typed signature: 41ed726d42e8bdc8e940fef3395a9649 +// flow-typed version: <>/polished_v^1.1.1/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'polished' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'polished' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'polished/dist/polished.es' { + declare module.exports: any; +} + +declare module 'polished/dist/polished' { + declare module.exports: any; +} + +declare module 'polished/dist/polished.min' { + declare module.exports: any; +} + +declare module 'polished/docs/assets/anchor' { + declare module.exports: any; +} + +declare module 'polished/docs/assets/docs' { + declare module.exports: any; +} + +declare module 'polished/docs/assets/highlight.pack' { + declare module.exports: any; +} + +declare module 'polished/docs/assets/polished' { + declare module.exports: any; +} + +declare module 'polished/docs/assets/script' { + declare module.exports: any; +} + +declare module 'polished/lib/color/adjustHue' { + declare module.exports: any; +} + +declare module 'polished/lib/color/complement' { + declare module.exports: any; +} + +declare module 'polished/lib/color/darken' { + declare module.exports: any; +} + +declare module 'polished/lib/color/desaturate' { + declare module.exports: any; +} + +declare module 'polished/lib/color/grayscale' { + declare module.exports: any; +} + +declare module 'polished/lib/color/hsl' { + declare module.exports: any; +} + +declare module 'polished/lib/color/hsla' { + declare module.exports: any; +} + +declare module 'polished/lib/color/invert' { + declare module.exports: any; +} + +declare module 'polished/lib/color/lighten' { + declare module.exports: any; +} + +declare module 'polished/lib/color/mix' { + declare module.exports: any; +} + +declare module 'polished/lib/color/opacify' { + declare module.exports: any; +} + +declare module 'polished/lib/color/parseToHsl' { + declare module.exports: any; +} + +declare module 'polished/lib/color/parseToRgb' { + declare module.exports: any; +} + +declare module 'polished/lib/color/readableColor' { + declare module.exports: any; +} + +declare module 'polished/lib/color/rgb' { + declare module.exports: any; +} + +declare module 'polished/lib/color/rgba' { + declare module.exports: any; +} + +declare module 'polished/lib/color/saturate' { + declare module.exports: any; +} + +declare module 'polished/lib/color/setHue' { + declare module.exports: any; +} + +declare module 'polished/lib/color/setLightness' { + declare module.exports: any; +} + +declare module 'polished/lib/color/setSaturation' { + declare module.exports: any; +} + +declare module 'polished/lib/color/shade' { + declare module.exports: any; +} + +declare module 'polished/lib/color/tint' { + declare module.exports: any; +} + +declare module 'polished/lib/color/toColorString' { + declare module.exports: any; +} + +declare module 'polished/lib/color/transparentize' { + declare module.exports: any; +} + +declare module 'polished/lib/helpers/directionalProperty' { + declare module.exports: any; +} + +declare module 'polished/lib/helpers/em' { + declare module.exports: any; +} + +declare module 'polished/lib/helpers/modularScale' { + declare module.exports: any; +} + +declare module 'polished/lib/helpers/rem' { + declare module.exports: any; +} + +declare module 'polished/lib/helpers/stripUnit' { + declare module.exports: any; +} + +declare module 'polished/lib/index' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_capitalizeString' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_curry' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_endsWith' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_guard' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_hslToHex' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_hslToRgb' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_isValidDimensionValue' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_nameToHex' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_numberToHex' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_polishedLogs' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_pxto' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_reduceHexValue' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_rgbToHsl' { + declare module.exports: any; +} + +declare module 'polished/lib/internalHelpers/_statefulSelectors' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/clearFix' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/ellipsis' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/fontFace' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/hideText' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/hiDPI' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/normalize' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/placeholder' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/radialGradient' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/retinaImage' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/selection' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/timingFunctions' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/triangle' { + declare module.exports: any; +} + +declare module 'polished/lib/mixins/wordWrap' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/animation' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/backgroundImages' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/backgrounds' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/borderColor' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/borderRadius' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/borderStyle' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/borderWidth' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/buttons' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/margin' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/padding' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/position' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/size' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/textInputs' { + declare module.exports: any; +} + +declare module 'polished/lib/shorthands/transitions' { + declare module.exports: any; +} + +declare module 'polished/lib/types/color' { + declare module.exports: any; +} + +// Filename aliases +declare module 'polished/dist/polished.es.js' { + declare module.exports: $Exports<'polished/dist/polished.es'>; +} +declare module 'polished/dist/polished.js' { + declare module.exports: $Exports<'polished/dist/polished'>; +} +declare module 'polished/dist/polished.min.js' { + declare module.exports: $Exports<'polished/dist/polished.min'>; +} +declare module 'polished/docs/assets/anchor.js' { + declare module.exports: $Exports<'polished/docs/assets/anchor'>; +} +declare module 'polished/docs/assets/docs.js' { + declare module.exports: $Exports<'polished/docs/assets/docs'>; +} +declare module 'polished/docs/assets/highlight.pack.js' { + declare module.exports: $Exports<'polished/docs/assets/highlight.pack'>; +} +declare module 'polished/docs/assets/polished.js' { + declare module.exports: $Exports<'polished/docs/assets/polished'>; +} +declare module 'polished/docs/assets/script.js' { + declare module.exports: $Exports<'polished/docs/assets/script'>; +} +declare module 'polished/lib/color/adjustHue.js' { + declare module.exports: $Exports<'polished/lib/color/adjustHue'>; +} +declare module 'polished/lib/color/complement.js' { + declare module.exports: $Exports<'polished/lib/color/complement'>; +} +declare module 'polished/lib/color/darken.js' { + declare module.exports: $Exports<'polished/lib/color/darken'>; +} +declare module 'polished/lib/color/desaturate.js' { + declare module.exports: $Exports<'polished/lib/color/desaturate'>; +} +declare module 'polished/lib/color/grayscale.js' { + declare module.exports: $Exports<'polished/lib/color/grayscale'>; +} +declare module 'polished/lib/color/hsl.js' { + declare module.exports: $Exports<'polished/lib/color/hsl'>; +} +declare module 'polished/lib/color/hsla.js' { + declare module.exports: $Exports<'polished/lib/color/hsla'>; +} +declare module 'polished/lib/color/invert.js' { + declare module.exports: $Exports<'polished/lib/color/invert'>; +} +declare module 'polished/lib/color/lighten.js' { + declare module.exports: $Exports<'polished/lib/color/lighten'>; +} +declare module 'polished/lib/color/mix.js' { + declare module.exports: $Exports<'polished/lib/color/mix'>; +} +declare module 'polished/lib/color/opacify.js' { + declare module.exports: $Exports<'polished/lib/color/opacify'>; +} +declare module 'polished/lib/color/parseToHsl.js' { + declare module.exports: $Exports<'polished/lib/color/parseToHsl'>; +} +declare module 'polished/lib/color/parseToRgb.js' { + declare module.exports: $Exports<'polished/lib/color/parseToRgb'>; +} +declare module 'polished/lib/color/readableColor.js' { + declare module.exports: $Exports<'polished/lib/color/readableColor'>; +} +declare module 'polished/lib/color/rgb.js' { + declare module.exports: $Exports<'polished/lib/color/rgb'>; +} +declare module 'polished/lib/color/rgba.js' { + declare module.exports: $Exports<'polished/lib/color/rgba'>; +} +declare module 'polished/lib/color/saturate.js' { + declare module.exports: $Exports<'polished/lib/color/saturate'>; +} +declare module 'polished/lib/color/setHue.js' { + declare module.exports: $Exports<'polished/lib/color/setHue'>; +} +declare module 'polished/lib/color/setLightness.js' { + declare module.exports: $Exports<'polished/lib/color/setLightness'>; +} +declare module 'polished/lib/color/setSaturation.js' { + declare module.exports: $Exports<'polished/lib/color/setSaturation'>; +} +declare module 'polished/lib/color/shade.js' { + declare module.exports: $Exports<'polished/lib/color/shade'>; +} +declare module 'polished/lib/color/tint.js' { + declare module.exports: $Exports<'polished/lib/color/tint'>; +} +declare module 'polished/lib/color/toColorString.js' { + declare module.exports: $Exports<'polished/lib/color/toColorString'>; +} +declare module 'polished/lib/color/transparentize.js' { + declare module.exports: $Exports<'polished/lib/color/transparentize'>; +} +declare module 'polished/lib/helpers/directionalProperty.js' { + declare module.exports: $Exports<'polished/lib/helpers/directionalProperty'>; +} +declare module 'polished/lib/helpers/em.js' { + declare module.exports: $Exports<'polished/lib/helpers/em'>; +} +declare module 'polished/lib/helpers/modularScale.js' { + declare module.exports: $Exports<'polished/lib/helpers/modularScale'>; +} +declare module 'polished/lib/helpers/rem.js' { + declare module.exports: $Exports<'polished/lib/helpers/rem'>; +} +declare module 'polished/lib/helpers/stripUnit.js' { + declare module.exports: $Exports<'polished/lib/helpers/stripUnit'>; +} +declare module 'polished/lib/index.js' { + declare module.exports: $Exports<'polished/lib/index'>; +} +declare module 'polished/lib/internalHelpers/_capitalizeString.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_capitalizeString'>; +} +declare module 'polished/lib/internalHelpers/_curry.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_curry'>; +} +declare module 'polished/lib/internalHelpers/_endsWith.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_endsWith'>; +} +declare module 'polished/lib/internalHelpers/_guard.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_guard'>; +} +declare module 'polished/lib/internalHelpers/_hslToHex.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_hslToHex'>; +} +declare module 'polished/lib/internalHelpers/_hslToRgb.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_hslToRgb'>; +} +declare module 'polished/lib/internalHelpers/_isValidDimensionValue.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_isValidDimensionValue'>; +} +declare module 'polished/lib/internalHelpers/_nameToHex.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_nameToHex'>; +} +declare module 'polished/lib/internalHelpers/_numberToHex.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_numberToHex'>; +} +declare module 'polished/lib/internalHelpers/_polishedLogs.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_polishedLogs'>; +} +declare module 'polished/lib/internalHelpers/_pxto.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_pxto'>; +} +declare module 'polished/lib/internalHelpers/_reduceHexValue.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_reduceHexValue'>; +} +declare module 'polished/lib/internalHelpers/_rgbToHsl.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_rgbToHsl'>; +} +declare module 'polished/lib/internalHelpers/_statefulSelectors.js' { + declare module.exports: $Exports<'polished/lib/internalHelpers/_statefulSelectors'>; +} +declare module 'polished/lib/mixins/clearFix.js' { + declare module.exports: $Exports<'polished/lib/mixins/clearFix'>; +} +declare module 'polished/lib/mixins/ellipsis.js' { + declare module.exports: $Exports<'polished/lib/mixins/ellipsis'>; +} +declare module 'polished/lib/mixins/fontFace.js' { + declare module.exports: $Exports<'polished/lib/mixins/fontFace'>; +} +declare module 'polished/lib/mixins/hideText.js' { + declare module.exports: $Exports<'polished/lib/mixins/hideText'>; +} +declare module 'polished/lib/mixins/hiDPI.js' { + declare module.exports: $Exports<'polished/lib/mixins/hiDPI'>; +} +declare module 'polished/lib/mixins/normalize.js' { + declare module.exports: $Exports<'polished/lib/mixins/normalize'>; +} +declare module 'polished/lib/mixins/placeholder.js' { + declare module.exports: $Exports<'polished/lib/mixins/placeholder'>; +} +declare module 'polished/lib/mixins/radialGradient.js' { + declare module.exports: $Exports<'polished/lib/mixins/radialGradient'>; +} +declare module 'polished/lib/mixins/retinaImage.js' { + declare module.exports: $Exports<'polished/lib/mixins/retinaImage'>; +} +declare module 'polished/lib/mixins/selection.js' { + declare module.exports: $Exports<'polished/lib/mixins/selection'>; +} +declare module 'polished/lib/mixins/timingFunctions.js' { + declare module.exports: $Exports<'polished/lib/mixins/timingFunctions'>; +} +declare module 'polished/lib/mixins/triangle.js' { + declare module.exports: $Exports<'polished/lib/mixins/triangle'>; +} +declare module 'polished/lib/mixins/wordWrap.js' { + declare module.exports: $Exports<'polished/lib/mixins/wordWrap'>; +} +declare module 'polished/lib/shorthands/animation.js' { + declare module.exports: $Exports<'polished/lib/shorthands/animation'>; +} +declare module 'polished/lib/shorthands/backgroundImages.js' { + declare module.exports: $Exports<'polished/lib/shorthands/backgroundImages'>; +} +declare module 'polished/lib/shorthands/backgrounds.js' { + declare module.exports: $Exports<'polished/lib/shorthands/backgrounds'>; +} +declare module 'polished/lib/shorthands/borderColor.js' { + declare module.exports: $Exports<'polished/lib/shorthands/borderColor'>; +} +declare module 'polished/lib/shorthands/borderRadius.js' { + declare module.exports: $Exports<'polished/lib/shorthands/borderRadius'>; +} +declare module 'polished/lib/shorthands/borderStyle.js' { + declare module.exports: $Exports<'polished/lib/shorthands/borderStyle'>; +} +declare module 'polished/lib/shorthands/borderWidth.js' { + declare module.exports: $Exports<'polished/lib/shorthands/borderWidth'>; +} +declare module 'polished/lib/shorthands/buttons.js' { + declare module.exports: $Exports<'polished/lib/shorthands/buttons'>; +} +declare module 'polished/lib/shorthands/margin.js' { + declare module.exports: $Exports<'polished/lib/shorthands/margin'>; +} +declare module 'polished/lib/shorthands/padding.js' { + declare module.exports: $Exports<'polished/lib/shorthands/padding'>; +} +declare module 'polished/lib/shorthands/position.js' { + declare module.exports: $Exports<'polished/lib/shorthands/position'>; +} +declare module 'polished/lib/shorthands/size.js' { + declare module.exports: $Exports<'polished/lib/shorthands/size'>; +} +declare module 'polished/lib/shorthands/textInputs.js' { + declare module.exports: $Exports<'polished/lib/shorthands/textInputs'>; +} +declare module 'polished/lib/shorthands/transitions.js' { + declare module.exports: $Exports<'polished/lib/shorthands/transitions'>; +} +declare module 'polished/lib/types/color.js' { + declare module.exports: $Exports<'polished/lib/types/color'>; +} diff --git a/flow-typed/npm/prettier_vx.x.x.js b/flow-typed/npm/prettier_vx.x.x.js new file mode 100644 index 000000000..f1730aef6 --- /dev/null +++ b/flow-typed/npm/prettier_vx.x.x.js @@ -0,0 +1,87 @@ +// flow-typed signature: 8bbe6221bd08557b01d489734074fd02 +// flow-typed version: <>/prettier_v1.5.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'prettier' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'prettier' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'prettier/bin/prettier' { + declare module.exports: any; +} + +declare module 'prettier/parser-babylon' { + declare module.exports: any; +} + +declare module 'prettier/parser-flow' { + declare module.exports: any; +} + +declare module 'prettier/parser-graphql' { + declare module.exports: any; +} + +declare module 'prettier/parser-json' { + declare module.exports: any; +} + +declare module 'prettier/parser-parse5' { + declare module.exports: any; +} + +declare module 'prettier/parser-postcss' { + declare module.exports: any; +} + +declare module 'prettier/parser-typescript' { + declare module.exports: any; +} + +// Filename aliases +declare module 'prettier/bin/prettier.js' { + declare module.exports: $Exports<'prettier/bin/prettier'>; +} +declare module 'prettier/index' { + declare module.exports: $Exports<'prettier'>; +} +declare module 'prettier/index.js' { + declare module.exports: $Exports<'prettier'>; +} +declare module 'prettier/parser-babylon.js' { + declare module.exports: $Exports<'prettier/parser-babylon'>; +} +declare module 'prettier/parser-flow.js' { + declare module.exports: $Exports<'prettier/parser-flow'>; +} +declare module 'prettier/parser-graphql.js' { + declare module.exports: $Exports<'prettier/parser-graphql'>; +} +declare module 'prettier/parser-json.js' { + declare module.exports: $Exports<'prettier/parser-json'>; +} +declare module 'prettier/parser-parse5.js' { + declare module.exports: $Exports<'prettier/parser-parse5'>; +} +declare module 'prettier/parser-postcss.js' { + declare module.exports: $Exports<'prettier/parser-postcss'>; +} +declare module 'prettier/parser-typescript.js' { + declare module.exports: $Exports<'prettier/parser-typescript'>; +} diff --git a/flow-typed/npm/prop-types_v15.x.x.js b/flow-typed/npm/prop-types_v15.x.x.js new file mode 100644 index 000000000..113b0b5c4 --- /dev/null +++ b/flow-typed/npm/prop-types_v15.x.x.js @@ -0,0 +1,34 @@ +// flow-typed signature: 3eaa1f24c7397b78a7481992d2cddcb2 +// flow-typed version: a1a20d4928/prop-types_v15.x.x/flow_>=v0.41.x + +type $npm$propTypes$ReactPropsCheckType = ( + props: any, + propName: string, + componentName: string, + href?: string) => ?Error; + +declare module 'prop-types' { + declare var array: React$PropType$Primitive>; + declare var bool: React$PropType$Primitive; + declare var func: React$PropType$Primitive; + declare var number: React$PropType$Primitive; + declare var object: React$PropType$Primitive; + declare var string: React$PropType$Primitive; + declare var any: React$PropType$Primitive; + declare var arrayOf: React$PropType$ArrayOf; + declare var element: React$PropType$Primitive; /* TODO */ + declare var instanceOf: React$PropType$InstanceOf; + declare var node: React$PropType$Primitive; /* TODO */ + declare var objectOf: React$PropType$ObjectOf; + declare var oneOf: React$PropType$OneOf; + declare var oneOfType: React$PropType$OneOfType; + declare var shape: React$PropType$Shape; + + declare function checkPropTypes( + propTypes: $Subtype<{[_: $Keys]: $npm$propTypes$ReactPropsCheckType}>, + values: V, + location: string, + componentName: string, + getStack: ?(() => ?string) + ) : void; +} diff --git a/flow-typed/npm/react-color_vx.x.x.js b/flow-typed/npm/react-color_vx.x.x.js new file mode 100644 index 000000000..cab5f273f --- /dev/null +++ b/flow-typed/npm/react-color_vx.x.x.js @@ -0,0 +1,704 @@ +// flow-typed signature: d3fd33836d3ae586d973d7728260a311 +// flow-typed version: <>/react-color_v^2.11.7/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'react-color' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'react-color' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'react-color/examples/Button' { + declare module.exports: any; +} + +declare module 'react-color/examples/index' { + declare module.exports: any; +} + +declare module 'react-color/examples/Sketch' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/alpha/Alpha' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/alpha/AlphaPointer' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/alpha/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/block/Block' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/block/BlockSwatches' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/block/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/chrome/Chrome' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/chrome/ChromeFields' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/chrome/ChromePointer' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/chrome/ChromePointerCircle' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/chrome/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/circle/Circle' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/circle/CircleSwatch' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/circle/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/common/Alpha' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/common/Checkboard' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/common/ColorWrap' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/common/EditableInput' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/common/Hue' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/common/index' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/common/Saturation' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/common/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/common/Swatch' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/compact/Compact' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/compact/CompactColor' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/compact/CompactFields' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/compact/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/github/Github' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/github/GithubSwatch' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/github/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/hue/Hue' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/hue/HuePointer' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/hue/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/material/Material' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/material/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/photoshop/Photoshop' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/photoshop/PhotoshopButton' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/photoshop/PhotoshopFields' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/photoshop/PhotoshopPointer' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/photoshop/PhotoshopPointerCircle' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/photoshop/PhotoshopPreviews' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/photoshop/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/sketch/Sketch' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/sketch/SketchFields' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/sketch/SketchPresetColors' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/sketch/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/slider/Slider' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/slider/SliderPointer' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/slider/SliderSwatch' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/slider/SliderSwatches' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/slider/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/swatches/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/swatches/Swatches' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/swatches/SwatchesColor' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/swatches/SwatchesGroup' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/twitter/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/components/twitter/Twitter' { + declare module.exports: any; +} + +declare module 'react-color/lib/helpers/alpha' { + declare module.exports: any; +} + +declare module 'react-color/lib/helpers/checkboard' { + declare module.exports: any; +} + +declare module 'react-color/lib/helpers/color' { + declare module.exports: any; +} + +declare module 'react-color/lib/helpers/hue' { + declare module.exports: any; +} + +declare module 'react-color/lib/helpers/interaction' { + declare module.exports: any; +} + +declare module 'react-color/lib/helpers/saturation' { + declare module.exports: any; +} + +declare module 'react-color/lib/helpers/spec' { + declare module.exports: any; +} + +declare module 'react-color/lib/index' { + declare module.exports: any; +} + +declare module 'react-color/modules/highlight' { + declare module.exports: any; +} + +declare module 'react-color/modules/highlight.js/highlight' { + declare module.exports: any; +} + +declare module 'react-color/modules/highlight.js/index' { + declare module.exports: any; +} + +declare module 'react-color/modules/highlight.js/languages/javascript' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-basic-layout/index' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-basic-layout/lib/components/Container' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-basic-layout/lib/components/Grid' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-docs/index' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-docs/lib/components/Code' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-docs/lib/components/Docs' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-docs/lib/components/Markdown' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-docs/lib/components/MarkdownTitle' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-docs/lib/components/Sidebar' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-docs/lib/components/SidebarItem' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-docs/lib/helpers/markdown' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-material-design/index' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-material-design/lib/components/Link' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-material-design/lib/components/Raised' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-material-design/lib/components/Tab' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-material-design/lib/components/Tabs' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-material-design/lib/components/Tile' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-move/index' { + declare module.exports: any; +} + +declare module 'react-color/modules/react-move/lib/components/Move' { + declare module.exports: any; +} + +declare module 'react-color/modules/remarkable/index' { + declare module.exports: any; +} + +declare module 'react-color/modules/tinycolor2/index' { + declare module.exports: any; +} + +declare module 'react-color/modules/webpack-react-static/index' { + declare module.exports: any; +} + +declare module 'react-color/scripts/docs-dist' { + declare module.exports: any; +} + +declare module 'react-color/scripts/docs-server' { + declare module.exports: any; +} + +declare module 'react-color/webpack.config' { + declare module.exports: any; +} + +// Filename aliases +declare module 'react-color/examples/Button.js' { + declare module.exports: $Exports<'react-color/examples/Button'>; +} +declare module 'react-color/examples/index.js' { + declare module.exports: $Exports<'react-color/examples/index'>; +} +declare module 'react-color/examples/Sketch.js' { + declare module.exports: $Exports<'react-color/examples/Sketch'>; +} +declare module 'react-color/lib/components/alpha/Alpha.js' { + declare module.exports: $Exports<'react-color/lib/components/alpha/Alpha'>; +} +declare module 'react-color/lib/components/alpha/AlphaPointer.js' { + declare module.exports: $Exports<'react-color/lib/components/alpha/AlphaPointer'>; +} +declare module 'react-color/lib/components/alpha/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/alpha/spec'>; +} +declare module 'react-color/lib/components/block/Block.js' { + declare module.exports: $Exports<'react-color/lib/components/block/Block'>; +} +declare module 'react-color/lib/components/block/BlockSwatches.js' { + declare module.exports: $Exports<'react-color/lib/components/block/BlockSwatches'>; +} +declare module 'react-color/lib/components/block/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/block/spec'>; +} +declare module 'react-color/lib/components/chrome/Chrome.js' { + declare module.exports: $Exports<'react-color/lib/components/chrome/Chrome'>; +} +declare module 'react-color/lib/components/chrome/ChromeFields.js' { + declare module.exports: $Exports<'react-color/lib/components/chrome/ChromeFields'>; +} +declare module 'react-color/lib/components/chrome/ChromePointer.js' { + declare module.exports: $Exports<'react-color/lib/components/chrome/ChromePointer'>; +} +declare module 'react-color/lib/components/chrome/ChromePointerCircle.js' { + declare module.exports: $Exports<'react-color/lib/components/chrome/ChromePointerCircle'>; +} +declare module 'react-color/lib/components/chrome/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/chrome/spec'>; +} +declare module 'react-color/lib/components/circle/Circle.js' { + declare module.exports: $Exports<'react-color/lib/components/circle/Circle'>; +} +declare module 'react-color/lib/components/circle/CircleSwatch.js' { + declare module.exports: $Exports<'react-color/lib/components/circle/CircleSwatch'>; +} +declare module 'react-color/lib/components/circle/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/circle/spec'>; +} +declare module 'react-color/lib/components/common/Alpha.js' { + declare module.exports: $Exports<'react-color/lib/components/common/Alpha'>; +} +declare module 'react-color/lib/components/common/Checkboard.js' { + declare module.exports: $Exports<'react-color/lib/components/common/Checkboard'>; +} +declare module 'react-color/lib/components/common/ColorWrap.js' { + declare module.exports: $Exports<'react-color/lib/components/common/ColorWrap'>; +} +declare module 'react-color/lib/components/common/EditableInput.js' { + declare module.exports: $Exports<'react-color/lib/components/common/EditableInput'>; +} +declare module 'react-color/lib/components/common/Hue.js' { + declare module.exports: $Exports<'react-color/lib/components/common/Hue'>; +} +declare module 'react-color/lib/components/common/index.js' { + declare module.exports: $Exports<'react-color/lib/components/common/index'>; +} +declare module 'react-color/lib/components/common/Saturation.js' { + declare module.exports: $Exports<'react-color/lib/components/common/Saturation'>; +} +declare module 'react-color/lib/components/common/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/common/spec'>; +} +declare module 'react-color/lib/components/common/Swatch.js' { + declare module.exports: $Exports<'react-color/lib/components/common/Swatch'>; +} +declare module 'react-color/lib/components/compact/Compact.js' { + declare module.exports: $Exports<'react-color/lib/components/compact/Compact'>; +} +declare module 'react-color/lib/components/compact/CompactColor.js' { + declare module.exports: $Exports<'react-color/lib/components/compact/CompactColor'>; +} +declare module 'react-color/lib/components/compact/CompactFields.js' { + declare module.exports: $Exports<'react-color/lib/components/compact/CompactFields'>; +} +declare module 'react-color/lib/components/compact/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/compact/spec'>; +} +declare module 'react-color/lib/components/github/Github.js' { + declare module.exports: $Exports<'react-color/lib/components/github/Github'>; +} +declare module 'react-color/lib/components/github/GithubSwatch.js' { + declare module.exports: $Exports<'react-color/lib/components/github/GithubSwatch'>; +} +declare module 'react-color/lib/components/github/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/github/spec'>; +} +declare module 'react-color/lib/components/hue/Hue.js' { + declare module.exports: $Exports<'react-color/lib/components/hue/Hue'>; +} +declare module 'react-color/lib/components/hue/HuePointer.js' { + declare module.exports: $Exports<'react-color/lib/components/hue/HuePointer'>; +} +declare module 'react-color/lib/components/hue/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/hue/spec'>; +} +declare module 'react-color/lib/components/material/Material.js' { + declare module.exports: $Exports<'react-color/lib/components/material/Material'>; +} +declare module 'react-color/lib/components/material/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/material/spec'>; +} +declare module 'react-color/lib/components/photoshop/Photoshop.js' { + declare module.exports: $Exports<'react-color/lib/components/photoshop/Photoshop'>; +} +declare module 'react-color/lib/components/photoshop/PhotoshopButton.js' { + declare module.exports: $Exports<'react-color/lib/components/photoshop/PhotoshopButton'>; +} +declare module 'react-color/lib/components/photoshop/PhotoshopFields.js' { + declare module.exports: $Exports<'react-color/lib/components/photoshop/PhotoshopFields'>; +} +declare module 'react-color/lib/components/photoshop/PhotoshopPointer.js' { + declare module.exports: $Exports<'react-color/lib/components/photoshop/PhotoshopPointer'>; +} +declare module 'react-color/lib/components/photoshop/PhotoshopPointerCircle.js' { + declare module.exports: $Exports<'react-color/lib/components/photoshop/PhotoshopPointerCircle'>; +} +declare module 'react-color/lib/components/photoshop/PhotoshopPreviews.js' { + declare module.exports: $Exports<'react-color/lib/components/photoshop/PhotoshopPreviews'>; +} +declare module 'react-color/lib/components/photoshop/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/photoshop/spec'>; +} +declare module 'react-color/lib/components/sketch/Sketch.js' { + declare module.exports: $Exports<'react-color/lib/components/sketch/Sketch'>; +} +declare module 'react-color/lib/components/sketch/SketchFields.js' { + declare module.exports: $Exports<'react-color/lib/components/sketch/SketchFields'>; +} +declare module 'react-color/lib/components/sketch/SketchPresetColors.js' { + declare module.exports: $Exports<'react-color/lib/components/sketch/SketchPresetColors'>; +} +declare module 'react-color/lib/components/sketch/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/sketch/spec'>; +} +declare module 'react-color/lib/components/slider/Slider.js' { + declare module.exports: $Exports<'react-color/lib/components/slider/Slider'>; +} +declare module 'react-color/lib/components/slider/SliderPointer.js' { + declare module.exports: $Exports<'react-color/lib/components/slider/SliderPointer'>; +} +declare module 'react-color/lib/components/slider/SliderSwatch.js' { + declare module.exports: $Exports<'react-color/lib/components/slider/SliderSwatch'>; +} +declare module 'react-color/lib/components/slider/SliderSwatches.js' { + declare module.exports: $Exports<'react-color/lib/components/slider/SliderSwatches'>; +} +declare module 'react-color/lib/components/slider/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/slider/spec'>; +} +declare module 'react-color/lib/components/swatches/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/swatches/spec'>; +} +declare module 'react-color/lib/components/swatches/Swatches.js' { + declare module.exports: $Exports<'react-color/lib/components/swatches/Swatches'>; +} +declare module 'react-color/lib/components/swatches/SwatchesColor.js' { + declare module.exports: $Exports<'react-color/lib/components/swatches/SwatchesColor'>; +} +declare module 'react-color/lib/components/swatches/SwatchesGroup.js' { + declare module.exports: $Exports<'react-color/lib/components/swatches/SwatchesGroup'>; +} +declare module 'react-color/lib/components/twitter/spec.js' { + declare module.exports: $Exports<'react-color/lib/components/twitter/spec'>; +} +declare module 'react-color/lib/components/twitter/Twitter.js' { + declare module.exports: $Exports<'react-color/lib/components/twitter/Twitter'>; +} +declare module 'react-color/lib/helpers/alpha.js' { + declare module.exports: $Exports<'react-color/lib/helpers/alpha'>; +} +declare module 'react-color/lib/helpers/checkboard.js' { + declare module.exports: $Exports<'react-color/lib/helpers/checkboard'>; +} +declare module 'react-color/lib/helpers/color.js' { + declare module.exports: $Exports<'react-color/lib/helpers/color'>; +} +declare module 'react-color/lib/helpers/hue.js' { + declare module.exports: $Exports<'react-color/lib/helpers/hue'>; +} +declare module 'react-color/lib/helpers/interaction.js' { + declare module.exports: $Exports<'react-color/lib/helpers/interaction'>; +} +declare module 'react-color/lib/helpers/saturation.js' { + declare module.exports: $Exports<'react-color/lib/helpers/saturation'>; +} +declare module 'react-color/lib/helpers/spec.js' { + declare module.exports: $Exports<'react-color/lib/helpers/spec'>; +} +declare module 'react-color/lib/index.js' { + declare module.exports: $Exports<'react-color/lib/index'>; +} +declare module 'react-color/modules/highlight.js' { + declare module.exports: $Exports<'react-color/modules/highlight'>; +} +declare module 'react-color/modules/highlight.js/highlight.js' { + declare module.exports: $Exports<'react-color/modules/highlight.js/highlight'>; +} +declare module 'react-color/modules/highlight.js/index.js' { + declare module.exports: $Exports<'react-color/modules/highlight.js/index'>; +} +declare module 'react-color/modules/highlight.js/languages/javascript.js' { + declare module.exports: $Exports<'react-color/modules/highlight.js/languages/javascript'>; +} +declare module 'react-color/modules/react-basic-layout/index.js' { + declare module.exports: $Exports<'react-color/modules/react-basic-layout/index'>; +} +declare module 'react-color/modules/react-basic-layout/lib/components/Container.js' { + declare module.exports: $Exports<'react-color/modules/react-basic-layout/lib/components/Container'>; +} +declare module 'react-color/modules/react-basic-layout/lib/components/Grid.js' { + declare module.exports: $Exports<'react-color/modules/react-basic-layout/lib/components/Grid'>; +} +declare module 'react-color/modules/react-docs/index.js' { + declare module.exports: $Exports<'react-color/modules/react-docs/index'>; +} +declare module 'react-color/modules/react-docs/lib/components/Code.js' { + declare module.exports: $Exports<'react-color/modules/react-docs/lib/components/Code'>; +} +declare module 'react-color/modules/react-docs/lib/components/Docs.js' { + declare module.exports: $Exports<'react-color/modules/react-docs/lib/components/Docs'>; +} +declare module 'react-color/modules/react-docs/lib/components/Markdown.js' { + declare module.exports: $Exports<'react-color/modules/react-docs/lib/components/Markdown'>; +} +declare module 'react-color/modules/react-docs/lib/components/MarkdownTitle.js' { + declare module.exports: $Exports<'react-color/modules/react-docs/lib/components/MarkdownTitle'>; +} +declare module 'react-color/modules/react-docs/lib/components/Sidebar.js' { + declare module.exports: $Exports<'react-color/modules/react-docs/lib/components/Sidebar'>; +} +declare module 'react-color/modules/react-docs/lib/components/SidebarItem.js' { + declare module.exports: $Exports<'react-color/modules/react-docs/lib/components/SidebarItem'>; +} +declare module 'react-color/modules/react-docs/lib/helpers/markdown.js' { + declare module.exports: $Exports<'react-color/modules/react-docs/lib/helpers/markdown'>; +} +declare module 'react-color/modules/react-material-design/index.js' { + declare module.exports: $Exports<'react-color/modules/react-material-design/index'>; +} +declare module 'react-color/modules/react-material-design/lib/components/Link.js' { + declare module.exports: $Exports<'react-color/modules/react-material-design/lib/components/Link'>; +} +declare module 'react-color/modules/react-material-design/lib/components/Raised.js' { + declare module.exports: $Exports<'react-color/modules/react-material-design/lib/components/Raised'>; +} +declare module 'react-color/modules/react-material-design/lib/components/Tab.js' { + declare module.exports: $Exports<'react-color/modules/react-material-design/lib/components/Tab'>; +} +declare module 'react-color/modules/react-material-design/lib/components/Tabs.js' { + declare module.exports: $Exports<'react-color/modules/react-material-design/lib/components/Tabs'>; +} +declare module 'react-color/modules/react-material-design/lib/components/Tile.js' { + declare module.exports: $Exports<'react-color/modules/react-material-design/lib/components/Tile'>; +} +declare module 'react-color/modules/react-move/index.js' { + declare module.exports: $Exports<'react-color/modules/react-move/index'>; +} +declare module 'react-color/modules/react-move/lib/components/Move.js' { + declare module.exports: $Exports<'react-color/modules/react-move/lib/components/Move'>; +} +declare module 'react-color/modules/remarkable/index.js' { + declare module.exports: $Exports<'react-color/modules/remarkable/index'>; +} +declare module 'react-color/modules/tinycolor2/index.js' { + declare module.exports: $Exports<'react-color/modules/tinycolor2/index'>; +} +declare module 'react-color/modules/webpack-react-static/index.js' { + declare module.exports: $Exports<'react-color/modules/webpack-react-static/index'>; +} +declare module 'react-color/scripts/docs-dist.js' { + declare module.exports: $Exports<'react-color/scripts/docs-dist'>; +} +declare module 'react-color/scripts/docs-server.js' { + declare module.exports: $Exports<'react-color/scripts/docs-server'>; +} +declare module 'react-color/webpack.config.js' { + declare module.exports: $Exports<'react-color/webpack.config'>; +} diff --git a/flow-typed/npm/react-devtools-core_vx.x.x.js b/flow-typed/npm/react-devtools-core_vx.x.x.js new file mode 100644 index 000000000..3ae4bcb67 --- /dev/null +++ b/flow-typed/npm/react-devtools-core_vx.x.x.js @@ -0,0 +1,52 @@ +// flow-typed signature: fd664fcf7a2fc39e3ce5d7776ec7ae24 +// flow-typed version: <>/react-devtools-core_v3.0.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'react-devtools-core' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'react-devtools-core' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'react-devtools-core/build/backend' { + declare module.exports: any; +} + +declare module 'react-devtools-core/build/standalone' { + declare module.exports: any; +} + +declare module 'react-devtools-core/standalone' { + declare module.exports: any; +} + +// Filename aliases +declare module 'react-devtools-core/build/backend.js' { + declare module.exports: $Exports<'react-devtools-core/build/backend'>; +} +declare module 'react-devtools-core/build/standalone.js' { + declare module.exports: $Exports<'react-devtools-core/build/standalone'>; +} +declare module 'react-devtools-core/index' { + declare module.exports: $Exports<'react-devtools-core'>; +} +declare module 'react-devtools-core/index.js' { + declare module.exports: $Exports<'react-devtools-core'>; +} +declare module 'react-devtools-core/standalone.js' { + declare module.exports: $Exports<'react-devtools-core/standalone'>; +} diff --git a/flow-typed/npm/react-redux_v5.x.x.js b/flow-typed/npm/react-redux_v5.x.x.js new file mode 100644 index 000000000..ac2894267 --- /dev/null +++ b/flow-typed/npm/react-redux_v5.x.x.js @@ -0,0 +1,157 @@ +// flow-typed signature: 16b40ff613d36712444ef20fb107de7c +// flow-typed version: be6cfc6753/react-redux_v5.x.x/flow_>=v0.62.0 + +import type { Dispatch, Store } from "redux"; + +declare module "react-redux" { + import type { ComponentType, ElementConfig } from 'react'; + + declare export class Provider extends React$Component<{ + store: Store, + children?: any + }> {} + + declare export function createProvider( + storeKey?: string, + subKey?: string + ): Provider<*, *>; + + /* + + S = State + A = Action + OP = OwnProps + SP = StateProps + DP = DispatchProps + MP = Merge props + MDP = Map dispatch to props object + RSP = Returned state props + RDP = Returned dispatch props + RMP = Returned merge props + CP = Props for returned component + Com = React Component + */ + + declare type MapStateToProps = (state: S, props: SP) => RSP; + + declare type MapDispatchToProps = (dispatch: Dispatch, ownProps: OP) => RDP; + + declare type MergeProps = ( + stateProps: SP, + dispatchProps: DP, + ownProps: MP + ) => RMP; + + declare type ConnectOptions = {| + pure?: boolean, + withRef?: boolean, + areStatesEqual?: (next: S, prev: S) => boolean, + areOwnPropsEqual?: (next: OP, prev: OP) => boolean, + areStatePropsEqual?: (next: RSP, prev: RSP) => boolean, + areMergedPropsEqual?: (next: RMP, prev: RMP) => boolean, + storeKey?: string + |}; + + declare type OmitDispatch = $Diff}>; + + declare export function connect< + Com: ComponentType<*>, + S: Object, + DP: Object, + RSP: Object, + CP: $Diff>, RSP> + >( + mapStateToProps: MapStateToProps, + mapDispatchToProps?: null + ): (component: Com) => ComponentType; + + declare export function connect>( + mapStateToProps?: null, + mapDispatchToProps?: null + ): (component: Com) => ComponentType>>; + + declare export function connect< + Com: ComponentType<*>, + A, + S: Object, + DP: Object, + SP: Object, + RSP: Object, + RDP: Object, + CP: $Diff<$Diff, RSP>, RDP> + >( + mapStateToProps: MapStateToProps, + mapDispatchToProps: MapDispatchToProps + ): (component: Com) => ComponentType; + + declare export function connect< + Com: ComponentType<*>, + A, + OP: Object, + DP: Object, + PR: Object, + CP: $Diff, DP> + >( + mapStateToProps?: null, + mapDispatchToProps: MapDispatchToProps + ): (Com) => ComponentType; + + declare export function connect< + Com: ComponentType<*>, + MDP: Object + >( + mapStateToProps?: null, + mapDispatchToProps: MDP + ): (component: Com) => ComponentType<$Diff, MDP>>; + + declare export function connect< + Com: ComponentType<*>, + S: Object, + SP: Object, + RSP: Object, + MDP: Object, + CP: $Diff, RSP> + >( + mapStateToProps: MapStateToProps, + mapDispatchToPRops: MDP + ): (component: Com) => ComponentType<$Diff & SP>; + + declare export function connect< + Com: ComponentType<*>, + A, + S: Object, + DP: Object, + SP: Object, + RSP: Object, + RDP: Object, + MP: Object, + RMP: Object, + CP: $Diff, RMP> + >( + mapStateToProps: MapStateToProps, + mapDispatchToProps: ?MapDispatchToProps, + mergeProps: MergeProps + ): (component: Com) => ComponentType; + + declare export function connect, + A, + S: Object, + DP: Object, + SP: Object, + RSP: Object, + RDP: Object, + MP: Object, + RMP: Object + >( + mapStateToProps: ?MapStateToProps, + mapDispatchToProps: ?MapDispatchToProps, + mergeProps: ?MergeProps, + options: ConnectOptions + ): (component: Com) => ComponentType<$Diff, RMP> & SP & DP & MP>; + + declare export default { + Provider: typeof Provider, + createProvider: typeof createProvider, + connect: typeof connect, + }; +} diff --git a/flow-typed/npm/react-test-renderer_vx.x.x.js b/flow-typed/npm/react-test-renderer_vx.x.x.js new file mode 100644 index 000000000..3669cea22 --- /dev/null +++ b/flow-typed/npm/react-test-renderer_vx.x.x.js @@ -0,0 +1,66 @@ +// flow-typed signature: d75e9be7ed16f88e288f787e982584b8 +// flow-typed version: <>/react-test-renderer_v^16/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'react-test-renderer' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'react-test-renderer' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'react-test-renderer/cjs/react-test-renderer-shallow.development' { + declare module.exports: any; +} + +declare module 'react-test-renderer/cjs/react-test-renderer-shallow.production.min' { + declare module.exports: any; +} + +declare module 'react-test-renderer/cjs/react-test-renderer.development' { + declare module.exports: any; +} + +declare module 'react-test-renderer/cjs/react-test-renderer.production.min' { + declare module.exports: any; +} + +declare module 'react-test-renderer/shallow' { + declare module.exports: any; +} + +// Filename aliases +declare module 'react-test-renderer/cjs/react-test-renderer-shallow.development.js' { + declare module.exports: $Exports<'react-test-renderer/cjs/react-test-renderer-shallow.development'>; +} +declare module 'react-test-renderer/cjs/react-test-renderer-shallow.production.min.js' { + declare module.exports: $Exports<'react-test-renderer/cjs/react-test-renderer-shallow.production.min'>; +} +declare module 'react-test-renderer/cjs/react-test-renderer.development.js' { + declare module.exports: $Exports<'react-test-renderer/cjs/react-test-renderer.development'>; +} +declare module 'react-test-renderer/cjs/react-test-renderer.production.min.js' { + declare module.exports: $Exports<'react-test-renderer/cjs/react-test-renderer.production.min'>; +} +declare module 'react-test-renderer/index' { + declare module.exports: $Exports<'react-test-renderer'>; +} +declare module 'react-test-renderer/index.js' { + declare module.exports: $Exports<'react-test-renderer'>; +} +declare module 'react-test-renderer/shallow.js' { + declare module.exports: $Exports<'react-test-renderer/shallow'>; +} diff --git a/flow-typed/npm/react-virtualized_vx.x.x.js b/flow-typed/npm/react-virtualized_vx.x.x.js new file mode 100644 index 000000000..e26d1aa95 --- /dev/null +++ b/flow-typed/npm/react-virtualized_vx.x.x.js @@ -0,0 +1,1376 @@ +// flow-typed signature: 7ad3746a26284155ba00efee65b4be9b +// flow-typed version: <>/react-virtualized_v^9.13.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'react-virtualized' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'react-virtualized' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'react-virtualized/dist/commonjs/ArrowKeyStepper/ArrowKeyStepper.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/ArrowKeyStepper/ArrowKeyStepper' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/ArrowKeyStepper/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/ArrowKeyStepper/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/AutoSizer/AutoSizer.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/AutoSizer/AutoSizer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/AutoSizer/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/AutoSizer/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurer.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/CellMeasurer/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/Collection.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/Collection' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/CollectionView' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/Section.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/Section' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/SectionManager.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/SectionManager' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/TestData' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/utils/calculateSizeAndPositionData.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Collection/utils/calculateSizeAndPositionData' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/ColumnSizer/ColumnSizer.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/ColumnSizer/ColumnSizer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/ColumnSizer/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/accessibilityOverscanIndicesGetter.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/accessibilityOverscanIndicesGetter' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/defaultCellRangeRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/defaultOverscanIndicesGetter.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/defaultOverscanIndicesGetter' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/Grid.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/Grid' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/utils/CellSizeAndPositionManager.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/utils/CellSizeAndPositionManager' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/utils/ScalingCellSizeAndPositionManager.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/utils/ScalingCellSizeAndPositionManager' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/utils/updateScrollIndexHelper.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Grid/utils/updateScrollIndexHelper' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/InfiniteLoader/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/InfiniteLoader/InfiniteLoader.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/InfiniteLoader/InfiniteLoader' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/jest-setup' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/List/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/List/List.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/List/List' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/List/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Masonry/createCellPositioner' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Masonry/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Masonry/Masonry.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Masonry/Masonry' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Masonry/PositionCache' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/MultiGrid/CellMeasurerCacheDecorator' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/MultiGrid/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/MultiGrid/MultiGrid.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/MultiGrid/MultiGrid' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/ScrollSync/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/ScrollSync/ScrollSync.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/ScrollSync/ScrollSync' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/Column.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/Column' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/defaultCellDataGetter' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/defaultCellRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/defaultHeaderRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/defaultHeaderRowRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/defaultRowRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/SortDirection' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/SortIndicator' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/Table.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/Table' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/Table/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/TestUtils' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/utils/animationFrame' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/utils/createCallbackMemoizer.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/utils/createCallbackMemoizer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/utils/getUpdatedOffsetForIndex.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/utils/getUpdatedOffsetForIndex' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/utils/initCellMetadata' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/utils/requestAnimationTimeout' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/utils/TestHelper' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/vendor/binarySearchBounds' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/vendor/detectElementResize' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/vendor/intervalTree' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/WindowScroller/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/WindowScroller/utils/dimensions' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/WindowScroller/utils/onScroll' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/WindowScroller/WindowScroller.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/commonjs/WindowScroller/WindowScroller' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ArrowKeyStepper/ArrowKeyStepper.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ArrowKeyStepper/ArrowKeyStepper' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ArrowKeyStepper/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ArrowKeyStepper/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/AutoSizer/AutoSizer.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/AutoSizer/AutoSizer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/AutoSizer/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/AutoSizer/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/CellMeasurer/CellMeasurer.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/CellMeasurer/CellMeasurer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/CellMeasurer/CellMeasurerCache.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/CellMeasurer/CellMeasurerCache' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/CellMeasurer/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/Collection.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/Collection' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/CollectionView' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/Section.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/Section' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/SectionManager.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/SectionManager' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/TestData' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/utils/calculateSizeAndPositionData.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Collection/utils/calculateSizeAndPositionData' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ColumnSizer/ColumnSizer.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ColumnSizer/ColumnSizer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ColumnSizer/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/accessibilityOverscanIndicesGetter.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/accessibilityOverscanIndicesGetter' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/defaultCellRangeRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/defaultOverscanIndicesGetter.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/defaultOverscanIndicesGetter' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/Grid.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/Grid' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/utils/CellSizeAndPositionManager.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/utils/CellSizeAndPositionManager' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/utils/ScalingCellSizeAndPositionManager.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/utils/ScalingCellSizeAndPositionManager' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/utils/updateScrollIndexHelper.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Grid/utils/updateScrollIndexHelper' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/InfiniteLoader/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/InfiniteLoader/InfiniteLoader.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/InfiniteLoader/InfiniteLoader' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/jest-setup' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/List/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/List/List.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/List/List' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/List/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Masonry/createCellPositioner' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Masonry/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Masonry/Masonry.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Masonry/Masonry' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Masonry/PositionCache' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/MultiGrid/CellMeasurerCacheDecorator' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/MultiGrid/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/MultiGrid/MultiGrid.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/MultiGrid/MultiGrid' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ScrollSync/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ScrollSync/ScrollSync.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/ScrollSync/ScrollSync' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/Column.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/Column' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/defaultCellDataGetter' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/defaultCellRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/defaultHeaderRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/defaultHeaderRowRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/defaultRowRenderer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/SortDirection' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/SortIndicator' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/Table.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/Table' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/Table/types' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/TestUtils' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/utils/animationFrame' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/utils/createCallbackMemoizer.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/utils/createCallbackMemoizer' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/utils/getUpdatedOffsetForIndex.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/utils/getUpdatedOffsetForIndex' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/utils/initCellMetadata' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/utils/requestAnimationTimeout' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/utils/TestHelper' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/vendor/binarySearchBounds' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/vendor/detectElementResize' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/vendor/intervalTree' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/WindowScroller/index' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/WindowScroller/utils/dimensions' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/WindowScroller/utils/onScroll' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/WindowScroller/WindowScroller.jest' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/es/WindowScroller/WindowScroller' { + declare module.exports: any; +} + +declare module 'react-virtualized/dist/umd/react-virtualized' { + declare module.exports: any; +} + +// Filename aliases +declare module 'react-virtualized/dist/commonjs/ArrowKeyStepper/ArrowKeyStepper.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ArrowKeyStepper/ArrowKeyStepper.jest'>; +} +declare module 'react-virtualized/dist/commonjs/ArrowKeyStepper/ArrowKeyStepper.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ArrowKeyStepper/ArrowKeyStepper'>; +} +declare module 'react-virtualized/dist/commonjs/ArrowKeyStepper/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ArrowKeyStepper/index'>; +} +declare module 'react-virtualized/dist/commonjs/ArrowKeyStepper/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ArrowKeyStepper/types'>; +} +declare module 'react-virtualized/dist/commonjs/AutoSizer/AutoSizer.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/AutoSizer/AutoSizer.jest'>; +} +declare module 'react-virtualized/dist/commonjs/AutoSizer/AutoSizer.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/AutoSizer/AutoSizer'>; +} +declare module 'react-virtualized/dist/commonjs/AutoSizer/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/AutoSizer/index'>; +} +declare module 'react-virtualized/dist/commonjs/AutoSizer/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/AutoSizer/types'>; +} +declare module 'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurer.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurer.jest'>; +} +declare module 'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurer.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurer'>; +} +declare module 'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache.jest'>; +} +declare module 'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/CellMeasurer/CellMeasurerCache'>; +} +declare module 'react-virtualized/dist/commonjs/CellMeasurer/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/CellMeasurer/index'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/Collection.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/Collection.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/Collection.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/Collection'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/CollectionView.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/CollectionView'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/index'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/Section.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/Section.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/Section.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/Section'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/SectionManager.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/SectionManager.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/SectionManager.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/SectionManager'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/TestData.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/TestData'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/types'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/utils/calculateSizeAndPositionData.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/utils/calculateSizeAndPositionData.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Collection/utils/calculateSizeAndPositionData.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Collection/utils/calculateSizeAndPositionData'>; +} +declare module 'react-virtualized/dist/commonjs/ColumnSizer/ColumnSizer.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ColumnSizer/ColumnSizer.jest'>; +} +declare module 'react-virtualized/dist/commonjs/ColumnSizer/ColumnSizer.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ColumnSizer/ColumnSizer'>; +} +declare module 'react-virtualized/dist/commonjs/ColumnSizer/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ColumnSizer/index'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/accessibilityOverscanIndicesGetter.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/accessibilityOverscanIndicesGetter.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/accessibilityOverscanIndicesGetter.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/accessibilityOverscanIndicesGetter'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/defaultCellRangeRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/defaultCellRangeRenderer'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/defaultOverscanIndicesGetter.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/defaultOverscanIndicesGetter.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/defaultOverscanIndicesGetter.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/defaultOverscanIndicesGetter'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/Grid.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/Grid.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/Grid.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/Grid'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/index'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/types'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/utils/CellSizeAndPositionManager.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/utils/CellSizeAndPositionManager.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/utils/CellSizeAndPositionManager.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/utils/CellSizeAndPositionManager'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/utils/ScalingCellSizeAndPositionManager.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/utils/ScalingCellSizeAndPositionManager.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/utils/ScalingCellSizeAndPositionManager.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/utils/ScalingCellSizeAndPositionManager'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/utils/updateScrollIndexHelper.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/utils/updateScrollIndexHelper.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Grid/utils/updateScrollIndexHelper.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Grid/utils/updateScrollIndexHelper'>; +} +declare module 'react-virtualized/dist/commonjs/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/index'>; +} +declare module 'react-virtualized/dist/commonjs/InfiniteLoader/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/InfiniteLoader/index'>; +} +declare module 'react-virtualized/dist/commonjs/InfiniteLoader/InfiniteLoader.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/InfiniteLoader/InfiniteLoader.jest'>; +} +declare module 'react-virtualized/dist/commonjs/InfiniteLoader/InfiniteLoader.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/InfiniteLoader/InfiniteLoader'>; +} +declare module 'react-virtualized/dist/commonjs/jest-setup.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/jest-setup'>; +} +declare module 'react-virtualized/dist/commonjs/List/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/List/index'>; +} +declare module 'react-virtualized/dist/commonjs/List/List.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/List/List.jest'>; +} +declare module 'react-virtualized/dist/commonjs/List/List.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/List/List'>; +} +declare module 'react-virtualized/dist/commonjs/List/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/List/types'>; +} +declare module 'react-virtualized/dist/commonjs/Masonry/createCellPositioner.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Masonry/createCellPositioner'>; +} +declare module 'react-virtualized/dist/commonjs/Masonry/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Masonry/index'>; +} +declare module 'react-virtualized/dist/commonjs/Masonry/Masonry.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Masonry/Masonry.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Masonry/Masonry.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Masonry/Masonry'>; +} +declare module 'react-virtualized/dist/commonjs/Masonry/PositionCache.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Masonry/PositionCache'>; +} +declare module 'react-virtualized/dist/commonjs/MultiGrid/CellMeasurerCacheDecorator.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/MultiGrid/CellMeasurerCacheDecorator'>; +} +declare module 'react-virtualized/dist/commonjs/MultiGrid/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/MultiGrid/index'>; +} +declare module 'react-virtualized/dist/commonjs/MultiGrid/MultiGrid.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/MultiGrid/MultiGrid.jest'>; +} +declare module 'react-virtualized/dist/commonjs/MultiGrid/MultiGrid.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/MultiGrid/MultiGrid'>; +} +declare module 'react-virtualized/dist/commonjs/ScrollSync/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ScrollSync/index'>; +} +declare module 'react-virtualized/dist/commonjs/ScrollSync/ScrollSync.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ScrollSync/ScrollSync.jest'>; +} +declare module 'react-virtualized/dist/commonjs/ScrollSync/ScrollSync.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/ScrollSync/ScrollSync'>; +} +declare module 'react-virtualized/dist/commonjs/Table/Column.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/Column.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Table/Column.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/Column'>; +} +declare module 'react-virtualized/dist/commonjs/Table/defaultCellDataGetter.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/defaultCellDataGetter'>; +} +declare module 'react-virtualized/dist/commonjs/Table/defaultCellRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/defaultCellRenderer'>; +} +declare module 'react-virtualized/dist/commonjs/Table/defaultHeaderRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/defaultHeaderRenderer'>; +} +declare module 'react-virtualized/dist/commonjs/Table/defaultHeaderRowRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/defaultHeaderRowRenderer'>; +} +declare module 'react-virtualized/dist/commonjs/Table/defaultRowRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/defaultRowRenderer'>; +} +declare module 'react-virtualized/dist/commonjs/Table/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/index'>; +} +declare module 'react-virtualized/dist/commonjs/Table/SortDirection.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/SortDirection'>; +} +declare module 'react-virtualized/dist/commonjs/Table/SortIndicator.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/SortIndicator'>; +} +declare module 'react-virtualized/dist/commonjs/Table/Table.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/Table.jest'>; +} +declare module 'react-virtualized/dist/commonjs/Table/Table.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/Table'>; +} +declare module 'react-virtualized/dist/commonjs/Table/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/Table/types'>; +} +declare module 'react-virtualized/dist/commonjs/TestUtils.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/TestUtils'>; +} +declare module 'react-virtualized/dist/commonjs/utils/animationFrame.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/utils/animationFrame'>; +} +declare module 'react-virtualized/dist/commonjs/utils/createCallbackMemoizer.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/utils/createCallbackMemoizer.jest'>; +} +declare module 'react-virtualized/dist/commonjs/utils/createCallbackMemoizer.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/utils/createCallbackMemoizer'>; +} +declare module 'react-virtualized/dist/commonjs/utils/getUpdatedOffsetForIndex.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/utils/getUpdatedOffsetForIndex.jest'>; +} +declare module 'react-virtualized/dist/commonjs/utils/getUpdatedOffsetForIndex.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/utils/getUpdatedOffsetForIndex'>; +} +declare module 'react-virtualized/dist/commonjs/utils/initCellMetadata.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/utils/initCellMetadata'>; +} +declare module 'react-virtualized/dist/commonjs/utils/requestAnimationTimeout.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/utils/requestAnimationTimeout'>; +} +declare module 'react-virtualized/dist/commonjs/utils/TestHelper.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/utils/TestHelper'>; +} +declare module 'react-virtualized/dist/commonjs/vendor/binarySearchBounds.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/vendor/binarySearchBounds'>; +} +declare module 'react-virtualized/dist/commonjs/vendor/detectElementResize.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/vendor/detectElementResize'>; +} +declare module 'react-virtualized/dist/commonjs/vendor/intervalTree.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/vendor/intervalTree'>; +} +declare module 'react-virtualized/dist/commonjs/WindowScroller/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/WindowScroller/index'>; +} +declare module 'react-virtualized/dist/commonjs/WindowScroller/utils/dimensions.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/WindowScroller/utils/dimensions'>; +} +declare module 'react-virtualized/dist/commonjs/WindowScroller/utils/onScroll.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/WindowScroller/utils/onScroll'>; +} +declare module 'react-virtualized/dist/commonjs/WindowScroller/WindowScroller.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/WindowScroller/WindowScroller.jest'>; +} +declare module 'react-virtualized/dist/commonjs/WindowScroller/WindowScroller.js' { + declare module.exports: $Exports<'react-virtualized/dist/commonjs/WindowScroller/WindowScroller'>; +} +declare module 'react-virtualized/dist/es/ArrowKeyStepper/ArrowKeyStepper.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ArrowKeyStepper/ArrowKeyStepper.jest'>; +} +declare module 'react-virtualized/dist/es/ArrowKeyStepper/ArrowKeyStepper.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ArrowKeyStepper/ArrowKeyStepper'>; +} +declare module 'react-virtualized/dist/es/ArrowKeyStepper/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ArrowKeyStepper/index'>; +} +declare module 'react-virtualized/dist/es/ArrowKeyStepper/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ArrowKeyStepper/types'>; +} +declare module 'react-virtualized/dist/es/AutoSizer/AutoSizer.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/AutoSizer/AutoSizer.jest'>; +} +declare module 'react-virtualized/dist/es/AutoSizer/AutoSizer.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/AutoSizer/AutoSizer'>; +} +declare module 'react-virtualized/dist/es/AutoSizer/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/AutoSizer/index'>; +} +declare module 'react-virtualized/dist/es/AutoSizer/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/AutoSizer/types'>; +} +declare module 'react-virtualized/dist/es/CellMeasurer/CellMeasurer.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/CellMeasurer/CellMeasurer.jest'>; +} +declare module 'react-virtualized/dist/es/CellMeasurer/CellMeasurer.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/CellMeasurer/CellMeasurer'>; +} +declare module 'react-virtualized/dist/es/CellMeasurer/CellMeasurerCache.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/CellMeasurer/CellMeasurerCache.jest'>; +} +declare module 'react-virtualized/dist/es/CellMeasurer/CellMeasurerCache.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/CellMeasurer/CellMeasurerCache'>; +} +declare module 'react-virtualized/dist/es/CellMeasurer/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/CellMeasurer/index'>; +} +declare module 'react-virtualized/dist/es/Collection/Collection.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/Collection.jest'>; +} +declare module 'react-virtualized/dist/es/Collection/Collection.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/Collection'>; +} +declare module 'react-virtualized/dist/es/Collection/CollectionView.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/CollectionView'>; +} +declare module 'react-virtualized/dist/es/Collection/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/index'>; +} +declare module 'react-virtualized/dist/es/Collection/Section.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/Section.jest'>; +} +declare module 'react-virtualized/dist/es/Collection/Section.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/Section'>; +} +declare module 'react-virtualized/dist/es/Collection/SectionManager.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/SectionManager.jest'>; +} +declare module 'react-virtualized/dist/es/Collection/SectionManager.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/SectionManager'>; +} +declare module 'react-virtualized/dist/es/Collection/TestData.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/TestData'>; +} +declare module 'react-virtualized/dist/es/Collection/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/types'>; +} +declare module 'react-virtualized/dist/es/Collection/utils/calculateSizeAndPositionData.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/utils/calculateSizeAndPositionData.jest'>; +} +declare module 'react-virtualized/dist/es/Collection/utils/calculateSizeAndPositionData.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Collection/utils/calculateSizeAndPositionData'>; +} +declare module 'react-virtualized/dist/es/ColumnSizer/ColumnSizer.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ColumnSizer/ColumnSizer.jest'>; +} +declare module 'react-virtualized/dist/es/ColumnSizer/ColumnSizer.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ColumnSizer/ColumnSizer'>; +} +declare module 'react-virtualized/dist/es/ColumnSizer/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ColumnSizer/index'>; +} +declare module 'react-virtualized/dist/es/Grid/accessibilityOverscanIndicesGetter.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/accessibilityOverscanIndicesGetter.jest'>; +} +declare module 'react-virtualized/dist/es/Grid/accessibilityOverscanIndicesGetter.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/accessibilityOverscanIndicesGetter'>; +} +declare module 'react-virtualized/dist/es/Grid/defaultCellRangeRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/defaultCellRangeRenderer'>; +} +declare module 'react-virtualized/dist/es/Grid/defaultOverscanIndicesGetter.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/defaultOverscanIndicesGetter.jest'>; +} +declare module 'react-virtualized/dist/es/Grid/defaultOverscanIndicesGetter.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/defaultOverscanIndicesGetter'>; +} +declare module 'react-virtualized/dist/es/Grid/Grid.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/Grid.jest'>; +} +declare module 'react-virtualized/dist/es/Grid/Grid.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/Grid'>; +} +declare module 'react-virtualized/dist/es/Grid/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/index'>; +} +declare module 'react-virtualized/dist/es/Grid/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/types'>; +} +declare module 'react-virtualized/dist/es/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset.jest'>; +} +declare module 'react-virtualized/dist/es/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/utils/calculateSizeAndPositionDataAndUpdateScrollOffset'>; +} +declare module 'react-virtualized/dist/es/Grid/utils/CellSizeAndPositionManager.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/utils/CellSizeAndPositionManager.jest'>; +} +declare module 'react-virtualized/dist/es/Grid/utils/CellSizeAndPositionManager.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/utils/CellSizeAndPositionManager'>; +} +declare module 'react-virtualized/dist/es/Grid/utils/ScalingCellSizeAndPositionManager.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/utils/ScalingCellSizeAndPositionManager.jest'>; +} +declare module 'react-virtualized/dist/es/Grid/utils/ScalingCellSizeAndPositionManager.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/utils/ScalingCellSizeAndPositionManager'>; +} +declare module 'react-virtualized/dist/es/Grid/utils/updateScrollIndexHelper.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/utils/updateScrollIndexHelper.jest'>; +} +declare module 'react-virtualized/dist/es/Grid/utils/updateScrollIndexHelper.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Grid/utils/updateScrollIndexHelper'>; +} +declare module 'react-virtualized/dist/es/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/index'>; +} +declare module 'react-virtualized/dist/es/InfiniteLoader/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/InfiniteLoader/index'>; +} +declare module 'react-virtualized/dist/es/InfiniteLoader/InfiniteLoader.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/InfiniteLoader/InfiniteLoader.jest'>; +} +declare module 'react-virtualized/dist/es/InfiniteLoader/InfiniteLoader.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/InfiniteLoader/InfiniteLoader'>; +} +declare module 'react-virtualized/dist/es/jest-setup.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/jest-setup'>; +} +declare module 'react-virtualized/dist/es/List/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/List/index'>; +} +declare module 'react-virtualized/dist/es/List/List.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/List/List.jest'>; +} +declare module 'react-virtualized/dist/es/List/List.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/List/List'>; +} +declare module 'react-virtualized/dist/es/List/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/List/types'>; +} +declare module 'react-virtualized/dist/es/Masonry/createCellPositioner.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Masonry/createCellPositioner'>; +} +declare module 'react-virtualized/dist/es/Masonry/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Masonry/index'>; +} +declare module 'react-virtualized/dist/es/Masonry/Masonry.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Masonry/Masonry.jest'>; +} +declare module 'react-virtualized/dist/es/Masonry/Masonry.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Masonry/Masonry'>; +} +declare module 'react-virtualized/dist/es/Masonry/PositionCache.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Masonry/PositionCache'>; +} +declare module 'react-virtualized/dist/es/MultiGrid/CellMeasurerCacheDecorator.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/MultiGrid/CellMeasurerCacheDecorator'>; +} +declare module 'react-virtualized/dist/es/MultiGrid/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/MultiGrid/index'>; +} +declare module 'react-virtualized/dist/es/MultiGrid/MultiGrid.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/MultiGrid/MultiGrid.jest'>; +} +declare module 'react-virtualized/dist/es/MultiGrid/MultiGrid.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/MultiGrid/MultiGrid'>; +} +declare module 'react-virtualized/dist/es/ScrollSync/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ScrollSync/index'>; +} +declare module 'react-virtualized/dist/es/ScrollSync/ScrollSync.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ScrollSync/ScrollSync.jest'>; +} +declare module 'react-virtualized/dist/es/ScrollSync/ScrollSync.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/ScrollSync/ScrollSync'>; +} +declare module 'react-virtualized/dist/es/Table/Column.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/Column.jest'>; +} +declare module 'react-virtualized/dist/es/Table/Column.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/Column'>; +} +declare module 'react-virtualized/dist/es/Table/defaultCellDataGetter.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/defaultCellDataGetter'>; +} +declare module 'react-virtualized/dist/es/Table/defaultCellRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/defaultCellRenderer'>; +} +declare module 'react-virtualized/dist/es/Table/defaultHeaderRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/defaultHeaderRenderer'>; +} +declare module 'react-virtualized/dist/es/Table/defaultHeaderRowRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/defaultHeaderRowRenderer'>; +} +declare module 'react-virtualized/dist/es/Table/defaultRowRenderer.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/defaultRowRenderer'>; +} +declare module 'react-virtualized/dist/es/Table/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/index'>; +} +declare module 'react-virtualized/dist/es/Table/SortDirection.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/SortDirection'>; +} +declare module 'react-virtualized/dist/es/Table/SortIndicator.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/SortIndicator'>; +} +declare module 'react-virtualized/dist/es/Table/Table.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/Table.jest'>; +} +declare module 'react-virtualized/dist/es/Table/Table.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/Table'>; +} +declare module 'react-virtualized/dist/es/Table/types.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/Table/types'>; +} +declare module 'react-virtualized/dist/es/TestUtils.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/TestUtils'>; +} +declare module 'react-virtualized/dist/es/utils/animationFrame.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/utils/animationFrame'>; +} +declare module 'react-virtualized/dist/es/utils/createCallbackMemoizer.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/utils/createCallbackMemoizer.jest'>; +} +declare module 'react-virtualized/dist/es/utils/createCallbackMemoizer.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/utils/createCallbackMemoizer'>; +} +declare module 'react-virtualized/dist/es/utils/getUpdatedOffsetForIndex.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/utils/getUpdatedOffsetForIndex.jest'>; +} +declare module 'react-virtualized/dist/es/utils/getUpdatedOffsetForIndex.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/utils/getUpdatedOffsetForIndex'>; +} +declare module 'react-virtualized/dist/es/utils/initCellMetadata.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/utils/initCellMetadata'>; +} +declare module 'react-virtualized/dist/es/utils/requestAnimationTimeout.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/utils/requestAnimationTimeout'>; +} +declare module 'react-virtualized/dist/es/utils/TestHelper.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/utils/TestHelper'>; +} +declare module 'react-virtualized/dist/es/vendor/binarySearchBounds.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/vendor/binarySearchBounds'>; +} +declare module 'react-virtualized/dist/es/vendor/detectElementResize.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/vendor/detectElementResize'>; +} +declare module 'react-virtualized/dist/es/vendor/intervalTree.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/vendor/intervalTree'>; +} +declare module 'react-virtualized/dist/es/WindowScroller/index.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/WindowScroller/index'>; +} +declare module 'react-virtualized/dist/es/WindowScroller/utils/dimensions.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/WindowScroller/utils/dimensions'>; +} +declare module 'react-virtualized/dist/es/WindowScroller/utils/onScroll.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/WindowScroller/utils/onScroll'>; +} +declare module 'react-virtualized/dist/es/WindowScroller/WindowScroller.jest.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/WindowScroller/WindowScroller.jest'>; +} +declare module 'react-virtualized/dist/es/WindowScroller/WindowScroller.js' { + declare module.exports: $Exports<'react-virtualized/dist/es/WindowScroller/WindowScroller'>; +} +declare module 'react-virtualized/dist/umd/react-virtualized.js' { + declare module.exports: $Exports<'react-virtualized/dist/umd/react-virtualized'>; +} diff --git a/flow-typed/npm/redux_v3.x.x.js b/flow-typed/npm/redux_v3.x.x.js new file mode 100644 index 000000000..7bc662368 --- /dev/null +++ b/flow-typed/npm/redux_v3.x.x.js @@ -0,0 +1,59 @@ +// flow-typed signature: cca4916b0213065533df8335c3285a4a +// flow-typed version: cab04034e7/redux_v3.x.x/flow_>=v0.55.x + +declare module 'redux' { + + /* + + S = State + A = Action + D = Dispatch + + */ + + declare export type DispatchAPI = (action: A) => A; + declare export type Dispatch }> = DispatchAPI; + + declare export type MiddlewareAPI> = { + dispatch: D; + getState(): S; + }; + + declare export type Store> = { + // rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages) + dispatch: D; + getState(): S; + subscribe(listener: () => void): () => void; + replaceReducer(nextReducer: Reducer): void + }; + + declare export type Reducer = (state: S | void, action: A) => S; + + declare export type CombinedReducer = (state: $Shape & {} | void, action: A) => S; + + declare export type Middleware> = + (api: MiddlewareAPI) => + (next: D) => D; + + declare export type StoreCreator> = { + (reducer: Reducer, enhancer?: StoreEnhancer): Store; + (reducer: Reducer, preloadedState: S, enhancer?: StoreEnhancer): Store; + }; + + declare export type StoreEnhancer> = (next: StoreCreator) => StoreCreator; + + declare export function createStore(reducer: Reducer, enhancer?: StoreEnhancer): Store; + declare export function createStore(reducer: Reducer, preloadedState?: S, enhancer?: StoreEnhancer): Store; + + declare export function applyMiddleware(...middlewares: Array>): StoreEnhancer; + + declare export type ActionCreator = (...args: Array) => A; + declare export type ActionCreators = { [key: K]: ActionCreator }; + + declare export function bindActionCreators, D: DispatchAPI>(actionCreator: C, dispatch: D): C; + declare export function bindActionCreators, D: DispatchAPI>(actionCreators: C, dispatch: D): C; + + declare export function combineReducers(reducers: O): CombinedReducer<$ObjMap(r: Reducer) => S>, A>; + + declare export var compose: $Compose; +} diff --git a/flow-typed/npm/rsocket.js b/flow-typed/npm/rsocket.js new file mode 100644 index 000000000..89ded1656 --- /dev/null +++ b/flow-typed/npm/rsocket.js @@ -0,0 +1,8 @@ + +declare module 'rsocket-core' { + declare module.exports: $Exports<'rsocket-core'>; +} + +declare module 'rsocket-tcp-server' { + declare module.exports: $Exports<'rsocket-tcp-server'>; +} diff --git a/flow-typed/npm/string-natural-compare_vx.x.x.js b/flow-typed/npm/string-natural-compare_vx.x.x.js new file mode 100644 index 000000000..d0edbb271 --- /dev/null +++ b/flow-typed/npm/string-natural-compare_vx.x.x.js @@ -0,0 +1,32 @@ +// flow-typed signature: 843db49f208e061075c2c057f6db5c4d +// flow-typed version: <>/string-natural-compare_v^2.0.2/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'string-natural-compare' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'string-natural-compare' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'string-natural-compare/natural-compare' { + declare module.exports: any; +} + +// Filename aliases +declare module 'string-natural-compare/natural-compare.js' { + declare module.exports: $Exports<'string-natural-compare/natural-compare'>; +} diff --git a/flow-typed/npm/vis_vx.x.x.js b/flow-typed/npm/vis_vx.x.x.js new file mode 100644 index 000000000..df2194a1e --- /dev/null +++ b/flow-typed/npm/vis_vx.x.x.js @@ -0,0 +1,1305 @@ +// flow-typed signature: ab12cb0bb6ca55414cab3e4d58ebf17e +// flow-typed version: <>/vis_v4.21.0/flow_v0.69.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'vis' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'vis' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'vis/dist/vis-graph3d.min' { + declare module.exports: any; +} + +declare module 'vis/dist/vis-network.min' { + declare module.exports: any; +} + +declare module 'vis/dist/vis-timeline-graph2d.min' { + declare module.exports: any; +} + +declare module 'vis/dist/vis' { + declare module.exports: any; +} + +declare module 'vis/dist/vis.min' { + declare module.exports: any; +} + +declare module 'vis/docs/js/bootstrap' { + declare module.exports: any; +} + +declare module 'vis/docs/js/bootstrap.min' { + declare module.exports: any; +} + +declare module 'vis/docs/js/docs.min' { + declare module.exports: any; +} + +declare module 'vis/docs/js/googleAnalytics' { + declare module.exports: any; +} + +declare module 'vis/docs/js/ie-emulation-modes-warning' { + declare module.exports: any; +} + +declare module 'vis/docs/js/ie10-viewport-bug-workaround' { + declare module.exports: any; +} + +declare module 'vis/docs/js/jquery.highlight' { + declare module.exports: any; +} + +declare module 'vis/docs/js/jquery.min' { + declare module.exports: any; +} + +declare module 'vis/docs/js/jquery.url.min' { + declare module.exports: any; +} + +declare module 'vis/docs/js/main' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-apollo' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-css' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-hs' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-lisp' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-lua' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-ml' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-proto' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-scala' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-sql' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-vb' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-vhdl' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-wiki' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/lang-yaml' { + declare module.exports: any; +} + +declare module 'vis/docs/js/prettify/prettify' { + declare module.exports: any; +} + +declare module 'vis/docs/js/smooth-scroll.min' { + declare module.exports: any; +} + +declare module 'vis/docs/js/tipuesearch.config' { + declare module.exports: any; +} + +declare module 'vis/docs/js/tipuesearch' { + declare module.exports: any; +} + +declare module 'vis/docs/js/tipuesearch.min' { + declare module.exports: any; +} + +declare module 'vis/docs/js/toggleTable' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/csv2array' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/playground' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-apollo' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-css' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-hs' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-lisp' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-lua' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-ml' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-proto' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-scala' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-sql' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-vb' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-vhdl' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-wiki' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/lang-yaml' { + declare module.exports: any; +} + +declare module 'vis/examples/graph3d/playground/prettify/prettify' { + declare module.exports: any; +} + +declare module 'vis/examples/network/datasources/largeHierarchicalDataset' { + declare module.exports: any; +} + +declare module 'vis/examples/network/datasources/WorldCup2014' { + declare module.exports: any; +} + +declare module 'vis/examples/network/exampleApplications/disassemblerExample' { + declare module.exports: any; +} + +declare module 'vis/examples/network/exampleUtil' { + declare module.exports: any; +} + +declare module 'vis/examples/timeline/other/requirejs/scripts/main' { + declare module.exports: any; +} + +declare module 'vis/examples/timeline/other/requirejs/scripts/require' { + declare module.exports: any; +} + +declare module 'vis/gulpfile' { + declare module.exports: any; +} + +declare module 'vis/index-graph3d' { + declare module.exports: any; +} + +declare module 'vis/index-network' { + declare module.exports: any; +} + +declare module 'vis/index-timeline-graph2d' { + declare module.exports: any; +} + +declare module 'vis/lib/DataSet' { + declare module.exports: any; +} + +declare module 'vis/lib/DataView' { + declare module.exports: any; +} + +declare module 'vis/lib/DOMutil' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/Camera' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/DataGroup' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/Filter' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/Graph3d' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/options' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/Point2d' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/Point3d' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/Range' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/Settings' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/Slider' { + declare module.exports: any; +} + +declare module 'vis/lib/graph3d/StepNumber' { + declare module.exports: any; +} + +declare module 'vis/lib/hammerUtil' { + declare module.exports: any; +} + +declare module 'vis/lib/header' { + declare module.exports: any; +} + +declare module 'vis/lib/module/hammer' { + declare module.exports: any; +} + +declare module 'vis/lib/module/moment' { + declare module.exports: any; +} + +declare module 'vis/lib/module/uuid' { + declare module.exports: any; +} + +declare module 'vis/lib/network/CachedImage' { + declare module.exports: any; +} + +declare module 'vis/lib/network/dotparser' { + declare module.exports: any; +} + +declare module 'vis/lib/network/gephiParser' { + declare module.exports: any; +} + +declare module 'vis/lib/network/Images' { + declare module.exports: any; +} + +declare module 'vis/lib/network/locales' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/Canvas' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/CanvasRenderer' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/Clustering' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/algorithms/FloydWarshall' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/DirectionStrategy' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/Edge' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/edges/BezierEdgeDynamic' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/edges/BezierEdgeStatic' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/edges/CubicBezierEdge' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/edges/StraightEdge' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/edges/util/BezierEdgeBase' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/edges/util/CubicBezierEdgeBase' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/edges/util/EdgeBase' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/edges/util/EndPoints' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/NavigationHandler' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/Node' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/Cluster' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Box' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Circle' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/CircularImage' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Database' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Diamond' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Dot' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Ellipse' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Hexagon' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Icon' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Image' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Square' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Star' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Text' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/Triangle' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/shapes/TriangleDown' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/util/CircleImageBase' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/util/NodeBase' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/nodes/util/ShapeBase' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/physics/BarnesHutSolver' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/physics/CentralGravitySolver' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/physics/FA2BasedCentralGravitySolver' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/physics/FA2BasedRepulsionSolver' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/physics/HierarchicalRepulsionSolver' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/physics/HierarchicalSpringSolver' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/physics/RepulsionSolver' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/physics/SpringSolver' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/shared/ComponentUtil' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/shared/Label' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/shared/LabelAccumulator' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/components/shared/LabelSplitter' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/EdgesHandler' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/Groups' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/InteractionHandler' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/KamadaKawai' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/LayoutEngine' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/ManipulationSystem' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/NodesHandler' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/PhysicsEngine' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/SelectionHandler' { + declare module.exports: any; +} + +declare module 'vis/lib/network/modules/View' { + declare module.exports: any; +} + +declare module 'vis/lib/network/Network' { + declare module.exports: any; +} + +declare module 'vis/lib/network/NetworkUtil' { + declare module.exports: any; +} + +declare module 'vis/lib/network/options' { + declare module.exports: any; +} + +declare module 'vis/lib/network/shapes' { + declare module.exports: any; +} + +declare module 'vis/lib/Queue' { + declare module.exports: any; +} + +declare module 'vis/lib/shared/Activator' { + declare module.exports: any; +} + +declare module 'vis/lib/shared/ColorPicker' { + declare module.exports: any; +} + +declare module 'vis/lib/shared/Configurator' { + declare module.exports: any; +} + +declare module 'vis/lib/shared/Popup' { + declare module.exports: any; +} + +declare module 'vis/lib/shared/Validator' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/BackgroundGroup' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/Component' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/CurrentTime' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/CustomTime' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/DataAxis' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/DataScale' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/graph2d_types/bar' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/graph2d_types/line' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/graph2d_types/points' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/GraphGroup' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/Group' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/item/BackgroundItem' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/item/BoxItem' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/item/Item' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/item/PointItem' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/item/RangeItem' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/ItemSet' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/Legend' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/LineGraph' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/component/TimeAxis' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/Core' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/DateUtil' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/Graph2d' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/locales' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/optionsGraph2d' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/optionsTimeline' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/Range' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/Stack' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/Timeline' { + declare module.exports: any; +} + +declare module 'vis/lib/timeline/TimeStep' { + declare module.exports: any; +} + +declare module 'vis/lib/util' { + declare module.exports: any; +} + +// Filename aliases +declare module 'vis/dist/vis-graph3d.min.js' { + declare module.exports: $Exports<'vis/dist/vis-graph3d.min'>; +} +declare module 'vis/dist/vis-network.min.js' { + declare module.exports: $Exports<'vis/dist/vis-network.min'>; +} +declare module 'vis/dist/vis-timeline-graph2d.min.js' { + declare module.exports: $Exports<'vis/dist/vis-timeline-graph2d.min'>; +} +declare module 'vis/dist/vis.js' { + declare module.exports: $Exports<'vis/dist/vis'>; +} +declare module 'vis/dist/vis.min.js' { + declare module.exports: $Exports<'vis/dist/vis.min'>; +} +declare module 'vis/docs/js/bootstrap.js' { + declare module.exports: $Exports<'vis/docs/js/bootstrap'>; +} +declare module 'vis/docs/js/bootstrap.min.js' { + declare module.exports: $Exports<'vis/docs/js/bootstrap.min'>; +} +declare module 'vis/docs/js/docs.min.js' { + declare module.exports: $Exports<'vis/docs/js/docs.min'>; +} +declare module 'vis/docs/js/googleAnalytics.js' { + declare module.exports: $Exports<'vis/docs/js/googleAnalytics'>; +} +declare module 'vis/docs/js/ie-emulation-modes-warning.js' { + declare module.exports: $Exports<'vis/docs/js/ie-emulation-modes-warning'>; +} +declare module 'vis/docs/js/ie10-viewport-bug-workaround.js' { + declare module.exports: $Exports<'vis/docs/js/ie10-viewport-bug-workaround'>; +} +declare module 'vis/docs/js/jquery.highlight.js' { + declare module.exports: $Exports<'vis/docs/js/jquery.highlight'>; +} +declare module 'vis/docs/js/jquery.min.js' { + declare module.exports: $Exports<'vis/docs/js/jquery.min'>; +} +declare module 'vis/docs/js/jquery.url.min.js' { + declare module.exports: $Exports<'vis/docs/js/jquery.url.min'>; +} +declare module 'vis/docs/js/main.js' { + declare module.exports: $Exports<'vis/docs/js/main'>; +} +declare module 'vis/docs/js/prettify/lang-apollo.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-apollo'>; +} +declare module 'vis/docs/js/prettify/lang-css.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-css'>; +} +declare module 'vis/docs/js/prettify/lang-hs.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-hs'>; +} +declare module 'vis/docs/js/prettify/lang-lisp.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-lisp'>; +} +declare module 'vis/docs/js/prettify/lang-lua.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-lua'>; +} +declare module 'vis/docs/js/prettify/lang-ml.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-ml'>; +} +declare module 'vis/docs/js/prettify/lang-proto.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-proto'>; +} +declare module 'vis/docs/js/prettify/lang-scala.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-scala'>; +} +declare module 'vis/docs/js/prettify/lang-sql.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-sql'>; +} +declare module 'vis/docs/js/prettify/lang-vb.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-vb'>; +} +declare module 'vis/docs/js/prettify/lang-vhdl.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-vhdl'>; +} +declare module 'vis/docs/js/prettify/lang-wiki.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-wiki'>; +} +declare module 'vis/docs/js/prettify/lang-yaml.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/lang-yaml'>; +} +declare module 'vis/docs/js/prettify/prettify.js' { + declare module.exports: $Exports<'vis/docs/js/prettify/prettify'>; +} +declare module 'vis/docs/js/smooth-scroll.min.js' { + declare module.exports: $Exports<'vis/docs/js/smooth-scroll.min'>; +} +declare module 'vis/docs/js/tipuesearch.config.js' { + declare module.exports: $Exports<'vis/docs/js/tipuesearch.config'>; +} +declare module 'vis/docs/js/tipuesearch.js' { + declare module.exports: $Exports<'vis/docs/js/tipuesearch'>; +} +declare module 'vis/docs/js/tipuesearch.min.js' { + declare module.exports: $Exports<'vis/docs/js/tipuesearch.min'>; +} +declare module 'vis/docs/js/toggleTable.js' { + declare module.exports: $Exports<'vis/docs/js/toggleTable'>; +} +declare module 'vis/examples/graph3d/playground/csv2array.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/csv2array'>; +} +declare module 'vis/examples/graph3d/playground/playground.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/playground'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-apollo.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-apollo'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-css.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-css'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-hs.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-hs'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-lisp.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-lisp'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-lua.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-lua'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-ml.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-ml'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-proto.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-proto'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-scala.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-scala'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-sql.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-sql'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-vb.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-vb'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-vhdl.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-vhdl'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-wiki.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-wiki'>; +} +declare module 'vis/examples/graph3d/playground/prettify/lang-yaml.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/lang-yaml'>; +} +declare module 'vis/examples/graph3d/playground/prettify/prettify.js' { + declare module.exports: $Exports<'vis/examples/graph3d/playground/prettify/prettify'>; +} +declare module 'vis/examples/network/datasources/largeHierarchicalDataset.js' { + declare module.exports: $Exports<'vis/examples/network/datasources/largeHierarchicalDataset'>; +} +declare module 'vis/examples/network/datasources/WorldCup2014.js' { + declare module.exports: $Exports<'vis/examples/network/datasources/WorldCup2014'>; +} +declare module 'vis/examples/network/exampleApplications/disassemblerExample.js' { + declare module.exports: $Exports<'vis/examples/network/exampleApplications/disassemblerExample'>; +} +declare module 'vis/examples/network/exampleUtil.js' { + declare module.exports: $Exports<'vis/examples/network/exampleUtil'>; +} +declare module 'vis/examples/timeline/other/requirejs/scripts/main.js' { + declare module.exports: $Exports<'vis/examples/timeline/other/requirejs/scripts/main'>; +} +declare module 'vis/examples/timeline/other/requirejs/scripts/require.js' { + declare module.exports: $Exports<'vis/examples/timeline/other/requirejs/scripts/require'>; +} +declare module 'vis/gulpfile.js' { + declare module.exports: $Exports<'vis/gulpfile'>; +} +declare module 'vis/index-graph3d.js' { + declare module.exports: $Exports<'vis/index-graph3d'>; +} +declare module 'vis/index-network.js' { + declare module.exports: $Exports<'vis/index-network'>; +} +declare module 'vis/index-timeline-graph2d.js' { + declare module.exports: $Exports<'vis/index-timeline-graph2d'>; +} +declare module 'vis/index' { + declare module.exports: $Exports<'vis'>; +} +declare module 'vis/index.js' { + declare module.exports: $Exports<'vis'>; +} +declare module 'vis/lib/DataSet.js' { + declare module.exports: $Exports<'vis/lib/DataSet'>; +} +declare module 'vis/lib/DataView.js' { + declare module.exports: $Exports<'vis/lib/DataView'>; +} +declare module 'vis/lib/DOMutil.js' { + declare module.exports: $Exports<'vis/lib/DOMutil'>; +} +declare module 'vis/lib/graph3d/Camera.js' { + declare module.exports: $Exports<'vis/lib/graph3d/Camera'>; +} +declare module 'vis/lib/graph3d/DataGroup.js' { + declare module.exports: $Exports<'vis/lib/graph3d/DataGroup'>; +} +declare module 'vis/lib/graph3d/Filter.js' { + declare module.exports: $Exports<'vis/lib/graph3d/Filter'>; +} +declare module 'vis/lib/graph3d/Graph3d.js' { + declare module.exports: $Exports<'vis/lib/graph3d/Graph3d'>; +} +declare module 'vis/lib/graph3d/options.js' { + declare module.exports: $Exports<'vis/lib/graph3d/options'>; +} +declare module 'vis/lib/graph3d/Point2d.js' { + declare module.exports: $Exports<'vis/lib/graph3d/Point2d'>; +} +declare module 'vis/lib/graph3d/Point3d.js' { + declare module.exports: $Exports<'vis/lib/graph3d/Point3d'>; +} +declare module 'vis/lib/graph3d/Range.js' { + declare module.exports: $Exports<'vis/lib/graph3d/Range'>; +} +declare module 'vis/lib/graph3d/Settings.js' { + declare module.exports: $Exports<'vis/lib/graph3d/Settings'>; +} +declare module 'vis/lib/graph3d/Slider.js' { + declare module.exports: $Exports<'vis/lib/graph3d/Slider'>; +} +declare module 'vis/lib/graph3d/StepNumber.js' { + declare module.exports: $Exports<'vis/lib/graph3d/StepNumber'>; +} +declare module 'vis/lib/hammerUtil.js' { + declare module.exports: $Exports<'vis/lib/hammerUtil'>; +} +declare module 'vis/lib/header.js' { + declare module.exports: $Exports<'vis/lib/header'>; +} +declare module 'vis/lib/module/hammer.js' { + declare module.exports: $Exports<'vis/lib/module/hammer'>; +} +declare module 'vis/lib/module/moment.js' { + declare module.exports: $Exports<'vis/lib/module/moment'>; +} +declare module 'vis/lib/module/uuid.js' { + declare module.exports: $Exports<'vis/lib/module/uuid'>; +} +declare module 'vis/lib/network/CachedImage.js' { + declare module.exports: $Exports<'vis/lib/network/CachedImage'>; +} +declare module 'vis/lib/network/dotparser.js' { + declare module.exports: $Exports<'vis/lib/network/dotparser'>; +} +declare module 'vis/lib/network/gephiParser.js' { + declare module.exports: $Exports<'vis/lib/network/gephiParser'>; +} +declare module 'vis/lib/network/Images.js' { + declare module.exports: $Exports<'vis/lib/network/Images'>; +} +declare module 'vis/lib/network/locales.js' { + declare module.exports: $Exports<'vis/lib/network/locales'>; +} +declare module 'vis/lib/network/modules/Canvas.js' { + declare module.exports: $Exports<'vis/lib/network/modules/Canvas'>; +} +declare module 'vis/lib/network/modules/CanvasRenderer.js' { + declare module.exports: $Exports<'vis/lib/network/modules/CanvasRenderer'>; +} +declare module 'vis/lib/network/modules/Clustering.js' { + declare module.exports: $Exports<'vis/lib/network/modules/Clustering'>; +} +declare module 'vis/lib/network/modules/components/algorithms/FloydWarshall.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/algorithms/FloydWarshall'>; +} +declare module 'vis/lib/network/modules/components/DirectionStrategy.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/DirectionStrategy'>; +} +declare module 'vis/lib/network/modules/components/Edge.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/Edge'>; +} +declare module 'vis/lib/network/modules/components/edges/BezierEdgeDynamic.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/edges/BezierEdgeDynamic'>; +} +declare module 'vis/lib/network/modules/components/edges/BezierEdgeStatic.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/edges/BezierEdgeStatic'>; +} +declare module 'vis/lib/network/modules/components/edges/CubicBezierEdge.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/edges/CubicBezierEdge'>; +} +declare module 'vis/lib/network/modules/components/edges/StraightEdge.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/edges/StraightEdge'>; +} +declare module 'vis/lib/network/modules/components/edges/util/BezierEdgeBase.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/edges/util/BezierEdgeBase'>; +} +declare module 'vis/lib/network/modules/components/edges/util/CubicBezierEdgeBase.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/edges/util/CubicBezierEdgeBase'>; +} +declare module 'vis/lib/network/modules/components/edges/util/EdgeBase.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/edges/util/EdgeBase'>; +} +declare module 'vis/lib/network/modules/components/edges/util/EndPoints.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/edges/util/EndPoints'>; +} +declare module 'vis/lib/network/modules/components/NavigationHandler.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/NavigationHandler'>; +} +declare module 'vis/lib/network/modules/components/Node.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/Node'>; +} +declare module 'vis/lib/network/modules/components/nodes/Cluster.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/Cluster'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Box.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Box'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Circle.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Circle'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/CircularImage.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/CircularImage'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Database.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Database'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Diamond.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Diamond'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Dot.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Dot'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Ellipse.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Ellipse'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Hexagon.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Hexagon'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Icon.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Icon'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Image.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Image'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Square.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Square'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Star.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Star'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Text.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Text'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/Triangle.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/Triangle'>; +} +declare module 'vis/lib/network/modules/components/nodes/shapes/TriangleDown.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/shapes/TriangleDown'>; +} +declare module 'vis/lib/network/modules/components/nodes/util/CircleImageBase.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/util/CircleImageBase'>; +} +declare module 'vis/lib/network/modules/components/nodes/util/NodeBase.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/util/NodeBase'>; +} +declare module 'vis/lib/network/modules/components/nodes/util/ShapeBase.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/nodes/util/ShapeBase'>; +} +declare module 'vis/lib/network/modules/components/physics/BarnesHutSolver.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/physics/BarnesHutSolver'>; +} +declare module 'vis/lib/network/modules/components/physics/CentralGravitySolver.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/physics/CentralGravitySolver'>; +} +declare module 'vis/lib/network/modules/components/physics/FA2BasedCentralGravitySolver.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/physics/FA2BasedCentralGravitySolver'>; +} +declare module 'vis/lib/network/modules/components/physics/FA2BasedRepulsionSolver.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/physics/FA2BasedRepulsionSolver'>; +} +declare module 'vis/lib/network/modules/components/physics/HierarchicalRepulsionSolver.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/physics/HierarchicalRepulsionSolver'>; +} +declare module 'vis/lib/network/modules/components/physics/HierarchicalSpringSolver.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/physics/HierarchicalSpringSolver'>; +} +declare module 'vis/lib/network/modules/components/physics/RepulsionSolver.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/physics/RepulsionSolver'>; +} +declare module 'vis/lib/network/modules/components/physics/SpringSolver.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/physics/SpringSolver'>; +} +declare module 'vis/lib/network/modules/components/shared/ComponentUtil.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/shared/ComponentUtil'>; +} +declare module 'vis/lib/network/modules/components/shared/Label.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/shared/Label'>; +} +declare module 'vis/lib/network/modules/components/shared/LabelAccumulator.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/shared/LabelAccumulator'>; +} +declare module 'vis/lib/network/modules/components/shared/LabelSplitter.js' { + declare module.exports: $Exports<'vis/lib/network/modules/components/shared/LabelSplitter'>; +} +declare module 'vis/lib/network/modules/EdgesHandler.js' { + declare module.exports: $Exports<'vis/lib/network/modules/EdgesHandler'>; +} +declare module 'vis/lib/network/modules/Groups.js' { + declare module.exports: $Exports<'vis/lib/network/modules/Groups'>; +} +declare module 'vis/lib/network/modules/InteractionHandler.js' { + declare module.exports: $Exports<'vis/lib/network/modules/InteractionHandler'>; +} +declare module 'vis/lib/network/modules/KamadaKawai.js' { + declare module.exports: $Exports<'vis/lib/network/modules/KamadaKawai'>; +} +declare module 'vis/lib/network/modules/LayoutEngine.js' { + declare module.exports: $Exports<'vis/lib/network/modules/LayoutEngine'>; +} +declare module 'vis/lib/network/modules/ManipulationSystem.js' { + declare module.exports: $Exports<'vis/lib/network/modules/ManipulationSystem'>; +} +declare module 'vis/lib/network/modules/NodesHandler.js' { + declare module.exports: $Exports<'vis/lib/network/modules/NodesHandler'>; +} +declare module 'vis/lib/network/modules/PhysicsEngine.js' { + declare module.exports: $Exports<'vis/lib/network/modules/PhysicsEngine'>; +} +declare module 'vis/lib/network/modules/SelectionHandler.js' { + declare module.exports: $Exports<'vis/lib/network/modules/SelectionHandler'>; +} +declare module 'vis/lib/network/modules/View.js' { + declare module.exports: $Exports<'vis/lib/network/modules/View'>; +} +declare module 'vis/lib/network/Network.js' { + declare module.exports: $Exports<'vis/lib/network/Network'>; +} +declare module 'vis/lib/network/NetworkUtil.js' { + declare module.exports: $Exports<'vis/lib/network/NetworkUtil'>; +} +declare module 'vis/lib/network/options.js' { + declare module.exports: $Exports<'vis/lib/network/options'>; +} +declare module 'vis/lib/network/shapes.js' { + declare module.exports: $Exports<'vis/lib/network/shapes'>; +} +declare module 'vis/lib/Queue.js' { + declare module.exports: $Exports<'vis/lib/Queue'>; +} +declare module 'vis/lib/shared/Activator.js' { + declare module.exports: $Exports<'vis/lib/shared/Activator'>; +} +declare module 'vis/lib/shared/ColorPicker.js' { + declare module.exports: $Exports<'vis/lib/shared/ColorPicker'>; +} +declare module 'vis/lib/shared/Configurator.js' { + declare module.exports: $Exports<'vis/lib/shared/Configurator'>; +} +declare module 'vis/lib/shared/Popup.js' { + declare module.exports: $Exports<'vis/lib/shared/Popup'>; +} +declare module 'vis/lib/shared/Validator.js' { + declare module.exports: $Exports<'vis/lib/shared/Validator'>; +} +declare module 'vis/lib/timeline/component/BackgroundGroup.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/BackgroundGroup'>; +} +declare module 'vis/lib/timeline/component/Component.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/Component'>; +} +declare module 'vis/lib/timeline/component/CurrentTime.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/CurrentTime'>; +} +declare module 'vis/lib/timeline/component/CustomTime.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/CustomTime'>; +} +declare module 'vis/lib/timeline/component/DataAxis.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/DataAxis'>; +} +declare module 'vis/lib/timeline/component/DataScale.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/DataScale'>; +} +declare module 'vis/lib/timeline/component/graph2d_types/bar.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/graph2d_types/bar'>; +} +declare module 'vis/lib/timeline/component/graph2d_types/line.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/graph2d_types/line'>; +} +declare module 'vis/lib/timeline/component/graph2d_types/points.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/graph2d_types/points'>; +} +declare module 'vis/lib/timeline/component/GraphGroup.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/GraphGroup'>; +} +declare module 'vis/lib/timeline/component/Group.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/Group'>; +} +declare module 'vis/lib/timeline/component/item/BackgroundItem.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/item/BackgroundItem'>; +} +declare module 'vis/lib/timeline/component/item/BoxItem.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/item/BoxItem'>; +} +declare module 'vis/lib/timeline/component/item/Item.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/item/Item'>; +} +declare module 'vis/lib/timeline/component/item/PointItem.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/item/PointItem'>; +} +declare module 'vis/lib/timeline/component/item/RangeItem.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/item/RangeItem'>; +} +declare module 'vis/lib/timeline/component/ItemSet.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/ItemSet'>; +} +declare module 'vis/lib/timeline/component/Legend.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/Legend'>; +} +declare module 'vis/lib/timeline/component/LineGraph.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/LineGraph'>; +} +declare module 'vis/lib/timeline/component/TimeAxis.js' { + declare module.exports: $Exports<'vis/lib/timeline/component/TimeAxis'>; +} +declare module 'vis/lib/timeline/Core.js' { + declare module.exports: $Exports<'vis/lib/timeline/Core'>; +} +declare module 'vis/lib/timeline/DateUtil.js' { + declare module.exports: $Exports<'vis/lib/timeline/DateUtil'>; +} +declare module 'vis/lib/timeline/Graph2d.js' { + declare module.exports: $Exports<'vis/lib/timeline/Graph2d'>; +} +declare module 'vis/lib/timeline/locales.js' { + declare module.exports: $Exports<'vis/lib/timeline/locales'>; +} +declare module 'vis/lib/timeline/optionsGraph2d.js' { + declare module.exports: $Exports<'vis/lib/timeline/optionsGraph2d'>; +} +declare module 'vis/lib/timeline/optionsTimeline.js' { + declare module.exports: $Exports<'vis/lib/timeline/optionsTimeline'>; +} +declare module 'vis/lib/timeline/Range.js' { + declare module.exports: $Exports<'vis/lib/timeline/Range'>; +} +declare module 'vis/lib/timeline/Stack.js' { + declare module.exports: $Exports<'vis/lib/timeline/Stack'>; +} +declare module 'vis/lib/timeline/Timeline.js' { + declare module.exports: $Exports<'vis/lib/timeline/Timeline'>; +} +declare module 'vis/lib/timeline/TimeStep.js' { + declare module.exports: $Exports<'vis/lib/timeline/TimeStep'>; +} +declare module 'vis/lib/util.js' { + declare module.exports: $Exports<'vis/lib/util'>; +} diff --git a/flow-typed/npm/websocket_vx.x.x.js b/flow-typed/npm/websocket_vx.x.x.js new file mode 100644 index 000000000..6d7ec8824 --- /dev/null +++ b/flow-typed/npm/websocket_vx.x.x.js @@ -0,0 +1,164 @@ +// flow-typed signature: a099122576a24c7a78325271cdd0048f +// flow-typed version: <>/websocket_v^1.0.24/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'websocket' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'websocket' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'websocket/gulpfile' { + declare module.exports: any; +} + +declare module 'websocket/lib/browser' { + declare module.exports: any; +} + +declare module 'websocket/lib/BufferUtil.fallback' { + declare module.exports: any; +} + +declare module 'websocket/lib/BufferUtil' { + declare module.exports: any; +} + +declare module 'websocket/lib/Deprecation' { + declare module.exports: any; +} + +declare module 'websocket/lib/utils' { + declare module.exports: any; +} + +declare module 'websocket/lib/Validation.fallback' { + declare module.exports: any; +} + +declare module 'websocket/lib/Validation' { + declare module.exports: any; +} + +declare module 'websocket/lib/version' { + declare module.exports: any; +} + +declare module 'websocket/lib/W3CWebSocket' { + declare module.exports: any; +} + +declare module 'websocket/lib/websocket' { + declare module.exports: any; +} + +declare module 'websocket/lib/WebSocketClient' { + declare module.exports: any; +} + +declare module 'websocket/lib/WebSocketConnection' { + declare module.exports: any; +} + +declare module 'websocket/lib/WebSocketFrame' { + declare module.exports: any; +} + +declare module 'websocket/lib/WebSocketRequest' { + declare module.exports: any; +} + +declare module 'websocket/lib/WebSocketRouter' { + declare module.exports: any; +} + +declare module 'websocket/lib/WebSocketRouterRequest' { + declare module.exports: any; +} + +declare module 'websocket/lib/WebSocketServer' { + declare module.exports: any; +} + +declare module 'websocket/vendor/FastBufferList' { + declare module.exports: any; +} + +// Filename aliases +declare module 'websocket/gulpfile.js' { + declare module.exports: $Exports<'websocket/gulpfile'>; +} +declare module 'websocket/index' { + declare module.exports: $Exports<'websocket'>; +} +declare module 'websocket/index.js' { + declare module.exports: $Exports<'websocket'>; +} +declare module 'websocket/lib/browser.js' { + declare module.exports: $Exports<'websocket/lib/browser'>; +} +declare module 'websocket/lib/BufferUtil.fallback.js' { + declare module.exports: $Exports<'websocket/lib/BufferUtil.fallback'>; +} +declare module 'websocket/lib/BufferUtil.js' { + declare module.exports: $Exports<'websocket/lib/BufferUtil'>; +} +declare module 'websocket/lib/Deprecation.js' { + declare module.exports: $Exports<'websocket/lib/Deprecation'>; +} +declare module 'websocket/lib/utils.js' { + declare module.exports: $Exports<'websocket/lib/utils'>; +} +declare module 'websocket/lib/Validation.fallback.js' { + declare module.exports: $Exports<'websocket/lib/Validation.fallback'>; +} +declare module 'websocket/lib/Validation.js' { + declare module.exports: $Exports<'websocket/lib/Validation'>; +} +declare module 'websocket/lib/version.js' { + declare module.exports: $Exports<'websocket/lib/version'>; +} +declare module 'websocket/lib/W3CWebSocket.js' { + declare module.exports: $Exports<'websocket/lib/W3CWebSocket'>; +} +declare module 'websocket/lib/websocket.js' { + declare module.exports: $Exports<'websocket/lib/websocket'>; +} +declare module 'websocket/lib/WebSocketClient.js' { + declare module.exports: $Exports<'websocket/lib/WebSocketClient'>; +} +declare module 'websocket/lib/WebSocketConnection.js' { + declare module.exports: $Exports<'websocket/lib/WebSocketConnection'>; +} +declare module 'websocket/lib/WebSocketFrame.js' { + declare module.exports: $Exports<'websocket/lib/WebSocketFrame'>; +} +declare module 'websocket/lib/WebSocketRequest.js' { + declare module.exports: $Exports<'websocket/lib/WebSocketRequest'>; +} +declare module 'websocket/lib/WebSocketRouter.js' { + declare module.exports: $Exports<'websocket/lib/WebSocketRouter'>; +} +declare module 'websocket/lib/WebSocketRouterRequest.js' { + declare module.exports: $Exports<'websocket/lib/WebSocketRouterRequest'>; +} +declare module 'websocket/lib/WebSocketServer.js' { + declare module.exports: $Exports<'websocket/lib/WebSocketServer'>; +} +declare module 'websocket/vendor/FastBufferList.js' { + declare module.exports: $Exports<'websocket/vendor/FastBufferList'>; +} diff --git a/flow-typed/npm/ws_vx.x.x.js b/flow-typed/npm/ws_vx.x.x.js new file mode 100644 index 000000000..24a07f25e --- /dev/null +++ b/flow-typed/npm/ws_vx.x.x.js @@ -0,0 +1,108 @@ +// flow-typed signature: efc8a980cb830128a9eaa7f9ee01567b +// flow-typed version: <>/ws_v^3.0.0/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'ws' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'ws' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'ws/lib/BufferUtil' { + declare module.exports: any; +} + +declare module 'ws/lib/Constants' { + declare module.exports: any; +} + +declare module 'ws/lib/ErrorCodes' { + declare module.exports: any; +} + +declare module 'ws/lib/EventTarget' { + declare module.exports: any; +} + +declare module 'ws/lib/Extensions' { + declare module.exports: any; +} + +declare module 'ws/lib/PerMessageDeflate' { + declare module.exports: any; +} + +declare module 'ws/lib/Receiver' { + declare module.exports: any; +} + +declare module 'ws/lib/Sender' { + declare module.exports: any; +} + +declare module 'ws/lib/Validation' { + declare module.exports: any; +} + +declare module 'ws/lib/WebSocket' { + declare module.exports: any; +} + +declare module 'ws/lib/WebSocketServer' { + declare module.exports: any; +} + +// Filename aliases +declare module 'ws/index' { + declare module.exports: $Exports<'ws'>; +} +declare module 'ws/index.js' { + declare module.exports: $Exports<'ws'>; +} +declare module 'ws/lib/BufferUtil.js' { + declare module.exports: $Exports<'ws/lib/BufferUtil'>; +} +declare module 'ws/lib/Constants.js' { + declare module.exports: $Exports<'ws/lib/Constants'>; +} +declare module 'ws/lib/ErrorCodes.js' { + declare module.exports: $Exports<'ws/lib/ErrorCodes'>; +} +declare module 'ws/lib/EventTarget.js' { + declare module.exports: $Exports<'ws/lib/EventTarget'>; +} +declare module 'ws/lib/Extensions.js' { + declare module.exports: $Exports<'ws/lib/Extensions'>; +} +declare module 'ws/lib/PerMessageDeflate.js' { + declare module.exports: $Exports<'ws/lib/PerMessageDeflate'>; +} +declare module 'ws/lib/Receiver.js' { + declare module.exports: $Exports<'ws/lib/Receiver'>; +} +declare module 'ws/lib/Sender.js' { + declare module.exports: $Exports<'ws/lib/Sender'>; +} +declare module 'ws/lib/Validation.js' { + declare module.exports: $Exports<'ws/lib/Validation'>; +} +declare module 'ws/lib/WebSocket.js' { + declare module.exports: $Exports<'ws/lib/WebSocket'>; +} +declare module 'ws/lib/WebSocketServer.js' { + declare module.exports: $Exports<'ws/lib/WebSocketServer'>; +} diff --git a/flow-typed/npm/yargs_vx.x.x.js b/flow-typed/npm/yargs_vx.x.x.js new file mode 100644 index 000000000..9dcec4252 --- /dev/null +++ b/flow-typed/npm/yargs_vx.x.x.js @@ -0,0 +1,101 @@ +// flow-typed signature: 3efe3623a87ff36c0bcfb0fbcdbdf729 +// flow-typed version: <>/yargs_v^10.0.3/flow_v0.59.0 + +/** + * This is an autogenerated libdef stub for: + * + * 'yargs' + * + * Fill this stub out by replacing all the `any` types. + * + * Once filled out, we encourage you to share your work with the + * community by sending a pull request to: + * https://github.com/flowtype/flow-typed + */ + +declare module 'yargs' { + declare module.exports: any; +} + +/** + * We include stubs for each file inside this npm package in case you need to + * require those files directly. Feel free to delete any files that aren't + * needed. + */ +declare module 'yargs/lib/apply-extends' { + declare module.exports: any; +} + +declare module 'yargs/lib/argsert' { + declare module.exports: any; +} + +declare module 'yargs/lib/command' { + declare module.exports: any; +} + +declare module 'yargs/lib/completion' { + declare module.exports: any; +} + +declare module 'yargs/lib/levenshtein' { + declare module.exports: any; +} + +declare module 'yargs/lib/obj-filter' { + declare module.exports: any; +} + +declare module 'yargs/lib/usage' { + declare module.exports: any; +} + +declare module 'yargs/lib/validation' { + declare module.exports: any; +} + +declare module 'yargs/lib/yerror' { + declare module.exports: any; +} + +declare module 'yargs/yargs' { + declare module.exports: any; +} + +// Filename aliases +declare module 'yargs/index' { + declare module.exports: $Exports<'yargs'>; +} +declare module 'yargs/index.js' { + declare module.exports: $Exports<'yargs'>; +} +declare module 'yargs/lib/apply-extends.js' { + declare module.exports: $Exports<'yargs/lib/apply-extends'>; +} +declare module 'yargs/lib/argsert.js' { + declare module.exports: $Exports<'yargs/lib/argsert'>; +} +declare module 'yargs/lib/command.js' { + declare module.exports: $Exports<'yargs/lib/command'>; +} +declare module 'yargs/lib/completion.js' { + declare module.exports: $Exports<'yargs/lib/completion'>; +} +declare module 'yargs/lib/levenshtein.js' { + declare module.exports: $Exports<'yargs/lib/levenshtein'>; +} +declare module 'yargs/lib/obj-filter.js' { + declare module.exports: $Exports<'yargs/lib/obj-filter'>; +} +declare module 'yargs/lib/usage.js' { + declare module.exports: $Exports<'yargs/lib/usage'>; +} +declare module 'yargs/lib/validation.js' { + declare module.exports: $Exports<'yargs/lib/validation'>; +} +declare module 'yargs/lib/yerror.js' { + declare module.exports: $Exports<'yargs/lib/yerror'>; +} +declare module 'yargs/yargs.js' { + declare module.exports: $Exports<'yargs/yargs'>; +} diff --git a/flow-typed/react.js b/flow-typed/react.js new file mode 100644 index 000000000..b34c9ab39 --- /dev/null +++ b/flow-typed/react.js @@ -0,0 +1,8 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +declare var React: $Exports<'react'>; diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..40cb82f06 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,7 @@ +GRADLE_VERSIONS_PLUGIN_VERSION=0.15.0 + +KOTLIN_VERSION=1.2.41 + +# Deps for publishing +GRADLE_BINTRAY_PLUGIN_VERSION=1.8.0 +ANDROID_MAVEN_GRADLE_PLUGIN_VERSION=2.1 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..91ca28c8b Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..16d28051c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKComponent+Sonar.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKComponent+Sonar.h new file mode 100644 index 000000000..8ebfd7151 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKComponent+Sonar.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import +#import +#import +#import + +FB_LINK_REQUIRE(CKComponent_Sonar) +@interface CKComponent (Sonar) + +- (NSArray *> *> *)sonar_getData; +- (NSDictionary *)sonar_getDataMutations; +- (NSString *)sonar_getName; +- (NSString *)sonar_getDecoration; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKComponent+Sonar.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKComponent+Sonar.mm new file mode 100644 index 000000000..22e34ab78 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKComponent+Sonar.mm @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "CKComponent+Sonar.h" + +#import +#import +#import +#import +#import +#import +#import + +/** This protocol isn't actually adopted anywhere, it just lets us use the SEL below */ +@protocol SonarKitLayoutComponentKitOverrideInformalProtocol +- (NSString *)sonar_componentNameOverride; +- (NSString *)sonar_componentDecorationOverride; +- (NSArray *> *> *)sonar_additionalDataOverride; +@end + +static BOOL AccessibilityContextIsDefault(CKComponentAccessibilityContext accessibilityContext) { + return accessibilityContext == CKComponentAccessibilityContext(); +} + +static NSDictionary *AccessibilityContextDict(CKComponentAccessibilityContext accessibilityContext) { + NSMutableDictionary *accessibilityDict = [NSMutableDictionary new]; + if (accessibilityContext.isAccessibilityElement != nil) { + accessibilityDict[@"isAccessibilityElement"] = SKObject(@([accessibilityContext.isAccessibilityElement boolValue])); + } + if (accessibilityContext.accessibilityLabel.hasText()) { + accessibilityDict[@"accessibilityLabel"] = SKObject(accessibilityContext.accessibilityLabel.value()); + } + if (accessibilityContext.accessibilityHint.hasText()) { + accessibilityDict[@"accessibilityHint"] = SKObject(accessibilityContext.accessibilityHint.value()); + } + if (accessibilityContext.accessibilityValue.hasText()) { + accessibilityDict[@"accessibilityValue"] = SKObject(accessibilityContext.accessibilityValue.value()); + } + if (accessibilityContext.accessibilityTraits != nil) { + accessibilityDict[@"accessibilityTraits"] = SKObject(@([accessibilityContext.accessibilityTraits integerValue])); + } + if (accessibilityContext.accessibilityComponentAction) { + accessibilityDict[@"accessibilityComponentAction.identifier"] = SKObject(@(accessibilityContext.accessibilityComponentAction.identifier().c_str())); + } + return accessibilityDict; +} + +FB_LINKABLE(CKComponent_Sonar) +@implementation CKComponent (Sonar) + +- (NSString *)sonar_getName +{ + if ([self respondsToSelector:@selector(sonar_componentNameOverride)]) { + return [(id)self sonar_componentNameOverride]; + } + return NSStringFromClass([self class]); +} + +- (NSString *)sonar_getDecoration +{ + if ([self respondsToSelector:@selector(sonar_componentDecorationOverride)]) { + return [(id)self sonar_componentDecorationOverride]; + } + return @"componentkit"; +} + +- (NSArray *> *> *)sonar_getData +{ + static NSDictionary *UIControlEventsEnumMap = @{ + @(UIControlEventTouchDown): @"UIControlEventTouchDown", + @(UIControlEventTouchDownRepeat): @"UIControlEventTouchDownRepeat", + @(UIControlEventTouchDragInside): @"UIControlEventTouchDragInside", + @(UIControlEventTouchDragOutside): @"UIControlEventTouchDragOutside", + @(UIControlEventTouchDragEnter): @"UIControlEventTouchDragEnter", + @(UIControlEventTouchDragExit): @"UIControlEventTouchDragExit", + @(UIControlEventTouchUpInside): @"UIControlEventTouchUpInside", + @(UIControlEventTouchUpOutside): @"UIControlEventTouchUpOutside", + @(UIControlEventTouchCancel): @"UIControlEventTouchTouchCancel", + + @(UIControlEventValueChanged): @"UIControlEventValueChanged", + @(UIControlEventPrimaryActionTriggered): @"UIControlEventPrimaryActionTriggered", + + @(UIControlEventEditingDidBegin): @"UIControlEventEditingDidBegin", + @(UIControlEventEditingChanged): @"UIControlEventEditingChanged", + @(UIControlEventEditingDidEnd): @"UIControlEventEditingDidEnd", + @(UIControlEventEditingDidEndOnExit): @"UIControlEventEditingDidEndOnExit", + }; + + + NSMutableArray *> *> *data = [NSMutableArray new]; + + [data addObject: [SKNamed newWithName: @"CKComponent" + withValue: @{ + @"frame": SKObject(self.viewContext.frame), + @"controller": SKObject(NSStringFromClass([self.controller class])), + }]]; + + if (self.viewContext.view) { + auto _actions = _CKComponentDebugControlActionsForComponent(self); + if (_actions.size() > 0) { + NSMutableDictionary *actions = [NSMutableDictionary new]; + + for (NSNumber *controlEvent : [UIControlEventsEnumMap allKeys]) { + NSMutableArray *> *responders = [NSMutableArray new]; + + for (const auto action : _actions) { + if ((action.first & [controlEvent integerValue]) == 0) { + continue; + } + + for (auto responder : action.second) { + id initialTarget = _CKTypedComponentDebugInitialTarget(responder).get(self); + const CKActionInfo actionInfo = CKActionFind(responder.selector(), initialTarget); + [responders addObject: @{ + @"initialTarget": SKObject(NSStringFromClass([initialTarget class])), + @"identifier": SKObject(@(responder.identifier().c_str())), + @"handler": SKObject(NSStringFromClass([actionInfo.responder class])), + @"selector": SKObject(NSStringFromSelector(responder.selector())), + }]; + } + } + + if (responders.count > 0) { + actions[UIControlEventsEnumMap[controlEvent]] = responders; + } + } + + [data addObject: [SKNamed newWithName: @"Actions" withValue: actions]]; + } + } + + // Only add accessibility panel if accessibilityContext is not default + CKComponentAccessibilityContext accessibilityContext = [self viewConfiguration].accessibilityContext(); + if (!AccessibilityContextIsDefault(accessibilityContext)) { + [data addObject: + [SKNamed newWithName: @"Accessibility" + withValue: @{ + @"accessibilityContext": AccessibilityContextDict(accessibilityContext), + @"accessibilityEnabled": SKMutableObject(@(CK::Component::Accessibility::IsAccessibilityEnabled())), + }]]; + } + + if ([self respondsToSelector:@selector(sonar_additionalDataOverride)]) { + [data addObjectsFromArray:[(id)self sonar_additionalDataOverride]]; + } + + return data; +} + +- (NSDictionary *)sonar_getDataMutations { + return @{ + @"CKComponentAccessibility.accessibilityEnabled": ^(NSNumber *value) { + CK::Component::Accessibility::SetForceAccessibilityEnabled([value boolValue]); + } + }; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKFlexboxComponent+Sonar.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKFlexboxComponent+Sonar.h new file mode 100644 index 000000000..c574a9c7c --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKFlexboxComponent+Sonar.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import +#import + +FB_LINK_REQUIRE(CKFlexboxComponent_Sonar) +@interface CKFlexboxComponent (Sonar) + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKFlexboxComponent+Sonar.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKFlexboxComponent+Sonar.mm new file mode 100644 index 000000000..dcbc5f36f --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKFlexboxComponent+Sonar.mm @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "CKFlexboxComponent+Sonar.h" + +#import +#import + +#import "CKComponent+Sonar.h" +#import "Utils.h" + +FB_LINKABLE(CKFlexboxComponent_Sonar) +@implementation CKFlexboxComponent (Sonar) + +- (NSArray *> *> *)sonar_additionalDataOverride +{ + static NSDictionary *CKFlexboxDirectionEnumMap = @{ + @(CKFlexboxDirectionColumn): @"vertical", + @(CKFlexboxDirectionRow): @"horizontal", + @(CKFlexboxDirectionColumnReverse): @"vertical-reverse", + @(CKFlexboxDirectionRowReverse): @"horizontal-reverse", + }; + + static NSDictionary *CKFlexboxJustifyContentEnumMap = @{ + @(CKFlexboxJustifyContentStart): @"start", + @(CKFlexboxJustifyContentCenter): @"center", + @(CKFlexboxJustifyContentEnd): @"end", + @(CKFlexboxJustifyContentSpaceBetween): @"space-between", + @(CKFlexboxJustifyContentSpaceAround): @"space-around", + }; + + static NSDictionary *CKFlexboxAlignItemsEnumMap = @{ + @(CKFlexboxAlignItemsStart): @"start", + @(CKFlexboxAlignItemsEnd): @"end", + @(CKFlexboxAlignItemsCenter): @"center", + @(CKFlexboxAlignItemsBaseline): @"baseline", + @(CKFlexboxAlignItemsStretch): @"stretch", + }; + + static NSDictionary *CKFlexboxAlignContentEnumMap = @{ + @(CKFlexboxAlignContentStart): @"start", + @(CKFlexboxAlignContentEnd): @"end", + @(CKFlexboxAlignContentCenter): @"center", + @(CKFlexboxAlignContentSpaceBetween): @"space-between", + @(CKFlexboxAlignContentSpaceAround): @"space-around", + @(CKFlexboxAlignContentStretch): @"stretch", + }; + + static NSDictionary *CKFlexboxWrapEnumMap = @{ + @(CKFlexboxWrapWrap): @"wrap", + @(CKFlexboxWrapNoWrap): @"no-wrap", + @(CKFlexboxWrapWrapReverse): @"wrap-reverse", + }; + + CKFlexboxComponentStyle style; + [[self valueForKey: @"_style"] getValue: &style]; + + return @[[SKNamed + newWithName:@"CKFlexboxComponent" + withValue:@{ + @"spacing": SKObject(@(style.spacing)), + @"direction": SKObject(CKFlexboxDirectionEnumMap[@(style.direction)]), + @"justifyContent": SKObject(CKFlexboxJustifyContentEnumMap[@(style.justifyContent)]), + @"alignItems": SKObject(CKFlexboxAlignItemsEnumMap[@(style.alignItems)]), + @"alignContent": SKObject(CKFlexboxAlignContentEnumMap[@(style.alignContent)]), + @"wrap": SKObject(CKFlexboxWrapEnumMap[@(style.wrap)]), + @"margin": SKObject(flexboxRect(style.margin)), + @"padding": SKObject(flexboxRect(style.padding)), + }]]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKInsetComponent+Sonar.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKInsetComponent+Sonar.h new file mode 100644 index 000000000..e6329223a --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKInsetComponent+Sonar.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import +#import + +FB_LINK_REQUIRE(CKInsetComponent_Sonar) +@interface CKInsetComponent (Sonar) + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKInsetComponent+Sonar.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKInsetComponent+Sonar.mm new file mode 100644 index 000000000..780aa1272 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/CKInsetComponent+Sonar.mm @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "CKInsetComponent+Sonar.h" + +#import +#import + +#import "CKComponent+Sonar.h" + +FB_LINKABLE(CKInsetComponent_Sonar) +@implementation CKInsetComponent (Sonar) + +- (NSArray *> *> *)sonar_additionalDataOverride +{ + return @[[SKNamed newWithName:@"CKInsetComponent" withValue:@{@"insets": SKObject([[self valueForKey: @"_insets"] UIEdgeInsetsValue])}]]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentHostingViewDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentHostingViewDescriptor.h new file mode 100644 index 000000000..f2f448177 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentHostingViewDescriptor.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@class CKComponentHostingView; + +@interface SKComponentHostingViewDescriptor : SKNodeDescriptor + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentHostingViewDescriptor.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentHostingViewDescriptor.mm new file mode 100644 index 000000000..87a8327fd --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentHostingViewDescriptor.mm @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKComponentHostingViewDescriptor.h" + +#import +#import +#import +#import +#import +#import + +#import + +#import "SKComponentLayoutWrapper.h" + +@implementation SKComponentHostingViewDescriptor + +- (NSString *)identifierForNode:(CKComponentHostingView *)node { + return [NSString stringWithFormat: @"%p", node]; +} + +- (NSUInteger)childCountForNode:(CKComponentHostingView *)node { + return node.mountedLayout.component ? 1 : 0; +} + +- (id)childForNode:(CKComponentHostingView *)node atIndex:(NSUInteger)index { + return [SKComponentLayoutWrapper newFromRoot:node]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(CKComponentHostingView *)node { + SKNodeDescriptor *viewDescriptor = [self descriptorForClass: [UIView class]]; + [viewDescriptor setHighlighted: highlighted forNode: node]; +} + +- (void)hitTest:(SKTouch *)touch forNode:(CKComponentHostingView *)node { + [touch continueWithChildIndex: 0 withOffset: (CGPoint){ 0, 0 }]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.h new file mode 100644 index 000000000..fb8ed237c --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@class SKComponentLayoutWrapper; + +@interface SKComponentLayoutDescriptor: SKNodeDescriptor + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.mm new file mode 100644 index 000000000..0a14f8398 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.mm @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKComponentLayoutDescriptor.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import +#import + +#import "SKComponentLayoutWrapper.h" +#import "CKComponent+Sonar.h" +#import "Utils.h" + +@implementation SKComponentLayoutDescriptor +{ + NSDictionary *CKFlexboxAlignSelfEnumMap; + NSDictionary *CKFlexboxPositionTypeEnumMap; +} + +- (void)setUp { + [super setUp]; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [self initEnumMaps]; + }); +} + +- (void)initEnumMaps { + CKFlexboxAlignSelfEnumMap = @{ + @(CKFlexboxAlignSelfAuto): @"auto", + @(CKFlexboxAlignSelfStart): @"start", + @(CKFlexboxAlignSelfEnd): @"end", + @(CKFlexboxAlignSelfCenter): @"center", + @(CKFlexboxAlignSelfBaseline): @"baseline", + @(CKFlexboxAlignSelfStretch): @"stretch", + }; + + CKFlexboxPositionTypeEnumMap = @{ + @(CKFlexboxPositionTypeRelative): @"relative", + @(CKFlexboxPositionTypeAbsolute): @"absolute", + }; +} + +- (NSString *)identifierForNode:(SKComponentLayoutWrapper *)node { + return node.identifier; +} + +- (NSString *)nameForNode:(SKComponentLayoutWrapper *)node { + return [node.component sonar_getName]; +} + +- (NSString *)decorationForNode:(SKComponentLayoutWrapper *)node { + return [node.component sonar_getDecoration]; +} + +- (NSUInteger)childCountForNode:(SKComponentLayoutWrapper *)node { + NSUInteger count = node.children.size(); + if (count == 0) { + count = node.component.viewContext.view ? 1 : 0; + } + return count; +} + +- (id)childForNode:(SKComponentLayoutWrapper *)node atIndex:(NSUInteger)index { + if (node.children.size() == 0) { + return node.component.viewContext.view; + } + return node.children[index]; +} + +- (NSArray *> *> *)dataForNode:(SKComponentLayoutWrapper *)node { + NSMutableArray *> *> *data = [NSMutableArray new]; + + if (node.isFlexboxChild) { + [data addObject: [SKNamed newWithName:@"Layout" withValue:[self propsForFlexboxChild:node.flexboxChild]]]; + } + + [data addObjectsFromArray:[node.component sonar_getData]]; + + return data; +} + +- (NSDictionary *)propsForFlexboxChild:(CKFlexboxComponentChild)child { + return @{ + @"spacingBefore": SKObject(@(child.spacingBefore)), + @"spacingAfter": SKObject(@(child.spacingAfter)), + @"flexGrow": SKObject(@(child.flexGrow)), + @"flexShrink": SKObject(@(child.flexShrink)), + @"zIndex": SKObject(@(child.zIndex)), + @"useTextRounding": SKObject(@(child.useTextRounding)), + @"margin": flexboxRect(child.margin), + @"flexBasis": relativeDimension(child.flexBasis), + @"padding": flexboxRect(child.padding), + @"alignSelf": CKFlexboxAlignSelfEnumMap[@(child.alignSelf)], + @"position": @{ + @"type": CKFlexboxPositionTypeEnumMap[@(child.position.type)], + @"start": relativeDimension(child.position.start), + @"top": relativeDimension(child.position.top), + @"end": relativeDimension(child.position.end), + @"bottom": relativeDimension(child.position.bottom), + @"left": relativeDimension(child.position.left), + @"right": relativeDimension(child.position.right), + }, + @"aspectRatio": @(child.aspectRatio.aspectRatio()), + }; +} + +- (NSDictionary *)dataMutationsForNode:(SKComponentLayoutWrapper *)node { + return [node.component sonar_getDataMutations]; +} + +- (NSArray *> *)attributesForNode:(SKComponentLayoutWrapper *)node { + return @[ + [SKNamed newWithName: @"responder" + withValue: SKObject(NSStringFromClass([node.component.nextResponder class]))] + ]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(SKComponentLayoutWrapper *)node { + SKHighlightOverlay *overlay = [SKHighlightOverlay sharedInstance]; + if (highlighted) { + CKComponentViewContext viewContext = node.component.viewContext; + [overlay mountInView: viewContext.view + withFrame: viewContext.frame]; + } else { + [overlay unmount]; + } +} + +- (void)hitTest:(SKTouch *)touch forNode:(SKComponentLayoutWrapper *)node { + if (node.children.size() == 0) { + UIView *componentView = node.component.viewContext.view; + if (componentView != nil) { + if ([touch containedIn: componentView.bounds]) { + [touch continueWithChildIndex: 0 withOffset: componentView.bounds.origin]; + return; + } + } + } + + NSInteger index = 0; + for (index = node.children.size() - 1; index >= 0; index--) { + const auto child = node.children[index]; + + CGRect frame = { + .origin = child.position, + .size = child.size + }; + + if ([touch containedIn: frame]) { + [touch continueWithChildIndex: index withOffset: child.position]; + return; + } + } + + [touch finish]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h new file mode 100644 index 000000000..d706c0b8a --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import +#import + +@protocol CKInspectableView; + +@interface SKComponentLayoutWrapper : NSObject + +@property (nonatomic, weak, readonly) CKComponent *component; +@property (nonatomic, readonly) NSString *identifier; +@property (nonatomic, readonly) CGSize size; +@property (nonatomic, readonly) CGPoint position; +@property (nonatomic, readonly) std::vector children; + +// Null for layouts which are not direct children of a CKFlexboxComponent +@property (nonatomic, readonly) BOOL isFlexboxChild; +@property (nonatomic, readonly) CKFlexboxComponentChild flexboxChild; + ++ (instancetype)newFromRoot:(id)root; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutWrapper.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutWrapper.mm new file mode 100644 index 000000000..adfe43442 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutWrapper.mm @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKComponentLayoutWrapper.h" + +#import +#import +#import +#import +#import + +static char const kLayoutWrapperKey = ' '; + +static CKFlexboxComponentChild findFlexboxLayoutParams(CKComponent *parent, CKComponent *child) { + if ([parent isKindOfClass:[CKFlexboxComponent class]]) { + static Ivar ivar = class_getInstanceVariable([CKFlexboxComponent class], "_children"); + static ptrdiff_t offset = ivar_getOffset(ivar); + + unsigned char *pComponent = (unsigned char*)(__bridge void*)parent; + auto children = (std::vector *)(pComponent + offset); + + if (children) { + for (auto it = children->begin(); it != children->end(); it++) { + if (it->component == child) { + return *it; + } + } + } + } + + return {}; +} + +@implementation SKComponentLayoutWrapper + ++ (instancetype)newFromRoot:(id)root { + const CKComponentLayout layout = [root mountedLayout]; + SKComponentLayoutWrapper *const wrapper = + [[SKComponentLayoutWrapper alloc] initWithLayout:layout + position:CGPointMake(0, 0) + parentKey:[NSString stringWithFormat: @"%p.", layout.component]]; + if (layout.component) + objc_setAssociatedObject(layout.component, &kLayoutWrapperKey, wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return wrapper; +} + +- (instancetype)initWithLayout:(const CKComponentLayout &)layout position:(CGPoint)position parentKey:(NSString *)parentKey { + if (self = [super init]) { + _component = layout.component; + _size = layout.size; + _position = position; + _identifier = [parentKey stringByAppendingString:layout.component ? NSStringFromClass([layout.component class]) : @"(null)"]; + + if (layout.children != nullptr) { + int index = 0; + for (const auto &child : *layout.children) { + if (child.layout.component == nil) { + continue; // nil children are allowed, ignore them + } + SKComponentLayoutWrapper *childWrapper = [[SKComponentLayoutWrapper alloc] initWithLayout:child.layout + position:child.position + parentKey:[_identifier stringByAppendingFormat:@"[%d].", index++]]; + childWrapper->_isFlexboxChild = [_component isKindOfClass:[CKFlexboxComponent class]]; + childWrapper->_flexboxChild = findFlexboxLayoutParams(_component, child.layout.component); + _children.push_back(childWrapper); + } + } + } + + return self; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentRootViewDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentRootViewDescriptor.h new file mode 100644 index 000000000..cc2995a81 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentRootViewDescriptor.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@class CKComponentRootView; + +@interface SKComponentRootViewDescriptor : SKNodeDescriptor + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentRootViewDescriptor.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentRootViewDescriptor.mm new file mode 100644 index 000000000..7769c82fb --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentRootViewDescriptor.mm @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKComponentRootViewDescriptor.h" + +#import +#import +#import +#import +#import +#import + +#import + +#import "SKComponentLayoutWrapper.h" + +@implementation SKComponentRootViewDescriptor + +- (NSString *)identifierForNode:(CKComponentRootView *)node { + return [NSString stringWithFormat: @"%p", node]; +} + +- (NSUInteger)childCountForNode:(CKComponentRootView *)node { + if ([node respondsToSelector:@selector(ck_attachState)]) { + CKComponentDataSourceAttachState *state = [node ck_attachState]; + return state == nil ? 0 : 1; + } + return 0; +} + +- (id)childForNode:(CKComponentRootView *)node atIndex:(NSUInteger)index { + return [SKComponentLayoutWrapper newFromRoot:node]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(CKComponentRootView *)node { + SKNodeDescriptor *viewDescriptor = [self descriptorForClass: [UIView class]]; + [viewDescriptor setHighlighted: highlighted forNode: node]; +} + +- (void)hitTest:(SKTouch *)touch forNode:(CKComponentRootView *)node { + [touch continueWithChildIndex: 0 withOffset: (CGPoint){ 0, 0 }]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SonarKitLayoutComponentKitSupport.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SonarKitLayoutComponentKitSupport.h new file mode 100644 index 000000000..8512169dc --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SonarKitLayoutComponentKitSupport.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import + +@interface SonarKitLayoutComponentKitSupport : NSObject + ++ (void)setUpWithDescriptorMapper:(SKDescriptorMapper *)mapper; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SonarKitLayoutComponentKitSupport.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SonarKitLayoutComponentKitSupport.mm new file mode 100644 index 000000000..894e7a098 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SonarKitLayoutComponentKitSupport.mm @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SonarKitLayoutComponentKitSupport.h" + +#import +#import + +#import + +#import "SKComponentHostingViewDescriptor.h" +#import "SKComponentRootViewDescriptor.h" +#import "SKComponentLayoutDescriptor.h" +#import "SKComponentLayoutWrapper.h" + +@implementation SonarKitLayoutComponentKitSupport + ++ (void)setUpWithDescriptorMapper:(SKDescriptorMapper *)mapper { + // What we really want here is "forProtocol:@protocol(CKInspectableView)" but no such luck. + [mapper registerDescriptor: [[SKComponentHostingViewDescriptor alloc] initWithDescriptorMapper: mapper] + forClass: [CKComponentHostingView class]]; + [mapper registerDescriptor: [[SKComponentRootViewDescriptor alloc] initWithDescriptorMapper: mapper] + forClass: [CKComponentRootView class]]; + [mapper registerDescriptor: [[SKComponentLayoutDescriptor alloc] initWithDescriptorMapper: mapper] + forClass: [SKComponentLayoutWrapper class]]; +} + + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/Utils.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/Utils.h new file mode 100644 index 000000000..15adcbe1a --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/Utils.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import +#import + +NSString *relativeDimension(CKRelativeDimension dimension); +NSDictionary *flexboxRect(CKFlexboxSpacing spacing); diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/Utils.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/Utils.mm new file mode 100644 index 000000000..a03b8c420 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/Utils.mm @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#include "Utils.h" + +NSString *relativeDimension(CKRelativeDimension dimension) { + switch(dimension.type()) { + case CKRelativeDimension::Type::PERCENT: + return [NSString stringWithFormat: @"%@%%", @(dimension.value())]; + case CKRelativeDimension::Type::POINTS: + return [NSString stringWithFormat: @"%@pt", @(dimension.value())]; + default: + return @"auto"; + } +} + +NSDictionary *flexboxRect(CKFlexboxSpacing spacing) { + return @{ + @"top": relativeDimension(spacing.top.dimension()), + @"bottom": relativeDimension(spacing.bottom.dimension()), + @"start": relativeDimension(spacing.start.dimension()), + @"end": relativeDimension(spacing.end.dimension()) + }; +} + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKDescriptorMapper.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKDescriptorMapper.h new file mode 100644 index 000000000..dd42e76fd --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKDescriptorMapper.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@class SKNodeDescriptor; + +@interface SKDescriptorMapper : NSObject + +- (instancetype)initWithDefaults; + +- (SKNodeDescriptor *)descriptorForClass:(Class)cls; + +- (void)registerDescriptor:(SKNodeDescriptor *)descriptor forClass:(Class)cls; + +- (NSArray *)allDescriptors; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKDescriptorMapper.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKDescriptorMapper.mm new file mode 100644 index 000000000..15114e529 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKDescriptorMapper.mm @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKDescriptorMapper.h" + +#import "SKApplicationDescriptor.h" +#import "SKButtonDescriptor.h" +#import "SKScrollViewDescriptor.h" +#import "SKViewControllerDescriptor.h" +#import "SKViewDescriptor.h" + +@implementation SKDescriptorMapper +{ + NSMutableDictionary *_descriptors; +} + +- (instancetype)initWithDefaults { + if (self = [super init]) { + _descriptors = [NSMutableDictionary new]; + + [self registerDescriptor: [[SKApplicationDescriptor alloc] initWithDescriptorMapper: self] + forClass: [UIApplication class]]; + [self registerDescriptor: [[SKViewControllerDescriptor alloc] initWithDescriptorMapper: self] + forClass: [UIViewController class]]; + [self registerDescriptor: [[SKScrollViewDescriptor alloc] initWithDescriptorMapper: self] + forClass: [UIScrollView class]]; + [self registerDescriptor: [[SKButtonDescriptor alloc] initWithDescriptorMapper: self] + forClass: [UIButton class]]; + [self registerDescriptor: [[SKViewDescriptor alloc] initWithDescriptorMapper: self] + forClass: [UIView class]]; + } + + return self; +} + +- (SKNodeDescriptor *)descriptorForClass:(Class)cls { + SKNodeDescriptor *classDescriptor = nil; + + while (classDescriptor == nil && cls != nil) { + classDescriptor = [_descriptors objectForKey: NSStringFromClass(cls)]; + cls = [cls superclass]; + } + + return classDescriptor; +} + +- (void)registerDescriptor:(SKNodeDescriptor *)descriptor forClass:(Class)cls { + NSString *className = NSStringFromClass(cls); + _descriptors[className] = descriptor; +} + +- (NSArray *)allDescriptors { + return [_descriptors allValues]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKHighlightOverlay.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKHighlightOverlay.h new file mode 100644 index 000000000..a3dfd7b5e --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKHighlightOverlay.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@interface SKHighlightOverlay : NSObject + ++ (instancetype)sharedInstance; ++ (UIColor*)overlayColor; + +- (void)mountInView:(UIView *)view withFrame:(CGRect)frame; +- (void)unmount; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKHighlightOverlay.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKHighlightOverlay.m new file mode 100644 index 000000000..e39726033 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKHighlightOverlay.m @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKHighlightOverlay.h" + +@implementation SKHighlightOverlay +{ + CALayer *_overlayLayer; +} + ++ (instancetype)sharedInstance { + static SKHighlightOverlay *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [self new]; + }); + return sharedInstance; +} + +- (instancetype)init { + if (self = [super init]) { + _overlayLayer = [CALayer layer]; + _overlayLayer.backgroundColor = [SKHighlightOverlay overlayColor].CGColor; + } + + return self; +} + +- (void)mountInView:(UIView *)view withFrame:(CGRect)frame { + [CATransaction begin]; + [CATransaction setValue:(id)kCFBooleanTrue + forKey:kCATransactionDisableActions]; + _overlayLayer.frame = frame; + [view.layer addSublayer: _overlayLayer]; + [CATransaction commit]; +} + +- (void)unmount { + [_overlayLayer removeFromSuperlayer]; +} + ++ (UIColor*)overlayColor { + return [UIColor colorWithRed:136.0 / 255.0 green:117.0 / 255.0 blue:197.0 / 255.0 alpha:0.6]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKInvalidation.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKInvalidation.h new file mode 100644 index 000000000..81543aa0b --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKInvalidation.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@protocol SKInvalidationDelegate + +- (void)invalidateNode:(id)node; + +- (void)updateNodeReference:(id)node; + +@end + +@interface SKInvalidation : NSObject + ++ (instancetype)sharedInstance; + ++ (void)enableInvalidations; + +@property (nonatomic, weak) id delegate; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKInvalidation.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKInvalidation.m new file mode 100644 index 000000000..33aa5c4fe --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKInvalidation.m @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import + +#import "SKInvalidation.h" +#import "UIView+SKInvalidation.h" +#import "UICollectionView+SKInvalidation.h" + +@implementation SKInvalidation + ++ (instancetype)sharedInstance { + static SKInvalidation *sInstance = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sInstance = [SKInvalidation new]; + }); + + return sInstance; +} + ++ (void)enableInvalidations { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [UIView enableInvalidation]; + [UICollectionView enableInvalidations]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowDidBecomeVisible:) + name:UIWindowDidBecomeVisibleNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowDidBecomeHidden:) + name:UIWindowDidBecomeHiddenNotification + object:nil]; + }); +} + ++ (void)windowDidBecomeVisible:(NSNotification*)notification { + [[SKInvalidation sharedInstance].delegate invalidateNode:[notification.object nextResponder]]; +} + ++ (void)windowDidBecomeHidden:(NSNotification*)notification { + [[SKInvalidation sharedInstance].delegate invalidateNode:[notification.object nextResponder]]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNamed.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNamed.h new file mode 100644 index 000000000..294fe98b6 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNamed.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@interface SKNamed<__covariant T> : NSObject + ++ (instancetype)newWithName:(NSString *)name withValue:(T)value; + +@property (nonatomic, readonly) NSString *name; +@property (nonatomic, readonly) T value; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNamed.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNamed.mm new file mode 100644 index 000000000..20fdfe0f7 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNamed.mm @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKNamed.h" + +@implementation SKNamed + ++ (instancetype)newWithName:(NSString *)name withValue:(id)value { + return [[SKNamed alloc] initWithName: name withValue: value]; +} + +- (instancetype)initWithName:(NSString *)name withValue:(id)value { + if (self = [super init]) { + _name = name; + _value = value; + } + + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"%@: %@", _name, _value]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNodeDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNodeDescriptor.h new file mode 100644 index 000000000..cd612de85 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNodeDescriptor.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import "SKDescriptorMapper.h" +#import "SKNamed.h" +#import "SKTouch.h" + +typedef void (^SKNodeUpdateData)(id value); + +/** + A SKNodeDescriptor is an object which know how to expose an Object of type T + to SonarKitLayoutPlugin. This class is the extension point for SonarKitLayoutPlugin and + is how custom objects or data can be exposed to Sonar. + */ +@interface SKNodeDescriptor<__covariant T> : NSObject + +/** + If the descriptor class is dependent on some set-up, use this. + This is invoked once Sonar connects. + */ +- (void)setUp; + +/** + Initializes the node-descriptor with a SKDescriptorMapper which contains mappings + between Class -> SKNodeDescriptor. + */ +- (instancetype)initWithDescriptorMapper:(SKDescriptorMapper *)mapper; + +/** + Gets the node-descriptor registered for a specific class. + */ +- (SKNodeDescriptor *)descriptorForClass:(Class)cls; + +/** + A globally unique ID used to identify a node in the hierarchy. This is used + in the communication between SonarKitLayoutPlugin and the Sonar desktop application + in order to identify nodes. + */ +- (NSString *)identifierForNode:(T)node; + +/** + The name used to identify this node in the Sonar desktop application. This is what + will be visible in the hierarchy. + */ +- (NSString *)nameForNode:(T)node; + +/** + The number of children this node exposes in the layout hierarchy. + */ +- (NSUInteger)childCountForNode:(T)node; + +/** + Get the child for a specific node at a specified index. + */ +- (id)childForNode:(T)node atIndex:(NSUInteger)index; + +/** + Get the data to show for this node in the sidebar of the Sonar application. The objects + will be shown in order by SKNamed.name as their header. + */ +- (NSArray *> *)dataForNode:(T)node; + +/** + Get the attributes for this node. Attributes will be showed in the Sonar application right + next to the name of the node. + */ +- (NSArray *> *)attributesForNode:(T)node; + +/** + A mapping of the path for a specific value, and a block responsible for updating + its corresponding value for a specific node. + + The paths (string) is dependent on what `dataForNode` returns (e.g "SKNodeDescriptor.name"). + */ +- (NSDictionary *)dataMutationsForNode:(T)node; + +/** + This is used in order to highlight any specific node which is currently + selected in the Sonar application. The plugin automatically takes care of de-selecting + the previously highlighted node. + */ +- (void)setHighlighted:(BOOL)highlighted forNode:(T)node; + +/** + Perform hit testing on the given node. Either continue the search in + one of the children of the node, or finish the hit testing on this + node. + */ +- (void)hitTest:(SKTouch *)point forNode:(T)node; + +/** + Invalidate a specific node. This is called once a node is removed or added + from or to the layout hierarchy. + */ +- (void)invalidateNode:(T)node; + +/** + The decoration for this node. Valid values are defined in the Sonar + applictation. + */ +- (NSString *)decorationForNode:(T)node; + +/** + Whether the node matches the given query. + Used for layout search. + */ +- (BOOL)matchesQuery:(NSString *)query forNode:(T)node; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNodeDescriptor.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNodeDescriptor.mm new file mode 100644 index 000000000..961dc5fa3 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNodeDescriptor.mm @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKNodeDescriptor.h" + +@implementation SKNodeDescriptor +{ + SKDescriptorMapper *_mapper; +} + +- (void)setUp { +} + +- (instancetype)initWithDescriptorMapper:(SKDescriptorMapper *)mapper { + if (self = [super init]) { + _mapper = mapper; + } + return self; +} + +- (SKNodeDescriptor *)descriptorForClass:(Class)cls { + return [_mapper descriptorForClass: cls]; +} + +- (NSString *)identifierForNode:(id)node { + @throw [NSString stringWithFormat:@"need to implement %@", NSStringFromSelector(_cmd)]; +} + +- (NSString *)nameForNode:(id)node { + return NSStringFromClass([node class]); +} + +- (NSUInteger)childCountForNode:(id)node { + @throw [NSString stringWithFormat:@"need to implement %@", NSStringFromSelector(_cmd)]; +} + +- (id)childForNode:(id)node atIndex:(NSUInteger)index { + @throw [NSString stringWithFormat:@"need to implement %@", NSStringFromSelector(_cmd)]; +} + +- (NSDictionary *)dataMutationsForNode:(id)node { + return @{}; +} + +- (NSArray *> *)dataForNode:(id)node { + return @[]; +} + +- (NSArray *> *)attributesForNode:(id)node { + return @[]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(id)node { +} + +- (void)hitTest:(SKTouch *)point forNode:(id)node { +} + +- (void)invalidateNode:(id)node { +} + +- (NSString *)decorationForNode:(id)node { + return @""; +} + +- (BOOL)matchesQuery:(NSString *)query forNode:(id)node { + NSString *name = [self nameForNode: node]; + return [self string:name contains:query] || [self string:[self identifierForNode: node] contains: query]; +} + +- (BOOL)string:(NSString *)string contains:(NSString *)substring { + return string != nil && substring != nil && [string rangeOfString: substring options: NSCaseInsensitiveSearch].location != NSNotFound; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKObject.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKObject.h new file mode 100644 index 000000000..24cbfe766 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKObject.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@protocol SKSonarValueCoder + ++ (instancetype)fromSonarValue:(id)sonarValue; + +- (NSDictionary> *)sonarValue; + +@end + +class SKObject { +public: + SKObject(CGRect rect); + SKObject(CGSize size); + SKObject(CGPoint point); + SKObject(UIEdgeInsets insets); + SKObject(CGAffineTransform transform); + SKObject(id value); + SKObject(id value); + + operator id () const noexcept { + return _actual ?: [NSNull null]; + } +protected: + id _actual; +}; + +class SKMutableObject : public SKObject { +public: + SKMutableObject(CGRect rect) : SKObject(rect) { } + SKMutableObject(CGSize size) : SKObject(size) { }; + SKMutableObject(CGPoint point) : SKObject(point) { }; + SKMutableObject(UIEdgeInsets insets) : SKObject(insets) { }; + SKMutableObject(CGAffineTransform transform) : SKObject(transform) { }; + SKMutableObject(id value) : SKObject(value) { }; + SKMutableObject(id value) : SKObject(value) { }; + + operator id () { + convertToMutable(); + return _actual; + } +protected: + BOOL _convertedToMutable = NO; + void convertToMutable(); +}; diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKObject.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKObject.mm new file mode 100644 index 000000000..1f596878f --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKObject.mm @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKObject.h" + +SKObject::SKObject(CGRect rect) { + _actual = @{ + @"origin": SKObject(rect.origin), + @"size": SKObject(rect.size) + }; +} + +SKObject::SKObject(CGSize size) { + _actual = @{ + @"height": @(size.height), + @"width": @(size.width) + }; +} + +SKObject::SKObject(CGPoint point) { + _actual = @{ + @"x": @(point.x), + @"y": @(point.y) + }; +} + +SKObject::SKObject(UIEdgeInsets insets) { + _actual = @{ + @"top": @(insets.top), + @"bottom": @(insets.bottom), + @"left": @(insets.left), + @"right": @(insets.right), + }; +} + +SKObject::SKObject(CGAffineTransform transform) { + _actual = @{ + @"a": @(transform.a), + @"b": @(transform.b), + @"c": @(transform.c), + @"d": @(transform.d), + @"tx": @(transform.tx), + @"ty": @(transform.ty), + }; +} + +SKObject::SKObject(id value) : _actual([value sonarValue]) { } + +SKObject::SKObject(id value) : _actual(value) { } + +static NSString *_objectType(id object) { + if ([object isKindOfClass: [NSDictionary class]]) { + return (NSString *)((NSDictionary *)object)[@"__type__"]; + } + + return nil; +} + +static id _objectValue(id object) { + if ([object isKindOfClass: [NSDictionary class]]) { + return ((NSDictionary *)object)[@"value"]; + } + + return object; +} + +static NSDictionary> *_SKValue(id object, BOOL isMutable) { + NSString *type = _objectType(object); + id value = _objectValue(object); + + return @{ + @"__type__": (type != nil ? type : @"auto"), + @"__mutable__": @(isMutable), + @"value": (value != nil ? value : [NSNull null]), + }; +} + +static NSDictionary *_SKMutable(const NSDictionary> *skObject) { + NSMutableDictionary *mutableObject = [NSMutableDictionary new]; + for (NSString *key: skObject) { + id value = skObject[key]; + + if (_objectType(value) != nil) { + mutableObject[key] = _SKValue(value, YES); + } else if ([value isKindOfClass: [NSDictionary class]]) { + auto objectValue = (NSDictionary>*) value; + mutableObject[key] = _SKMutable(objectValue); + } else { + mutableObject[key] = _SKValue(value, YES); + } + } + + return mutableObject; +} + +void SKMutableObject::convertToMutable() { + if (_convertedToMutable) { + return; + } + + if (_objectType(_actual) == nil && [_actual isKindOfClass: [NSDictionary class]]) { + auto object = (const NSDictionary> *)_actual; + _actual = _SKMutable(object); + } else { + _actual = _SKValue(_actual, YES); + } + + _convertedToMutable = YES; +} + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKSearchResultNode.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKSearchResultNode.h new file mode 100644 index 000000000..d156af6cc --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKSearchResultNode.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#ifndef SKSearchResultNode_h +#define SKSearchResultNode_h + +#import + +@interface SKSearchResultNode : NSObject + +@property (nonatomic, copy, readonly) NSString *nodeId; + +- (instancetype)initWithNode:(NSString *)nodeId + asMatch:(BOOL)isMatch + withElement:(NSDictionary *)element + andChildren:(NSArray *)children; + +- (NSDictionary *)toNSDictionary; + +@end +#endif /* SKSearchResultNode_h */ diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKSearchResultNode.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKSearchResultNode.m new file mode 100644 index 000000000..a1e99cc6c --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKSearchResultNode.m @@ -0,0 +1,50 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "SKSearchResultNode.h" + +@implementation SKSearchResultNode { + NSString *_nodeId; + BOOL _isMatch; + NSDictionary *_element; + NSArray *_children; +} + +- (instancetype)initWithNode:(NSString *)nodeId + asMatch:(BOOL)isMatch + withElement:(NSDictionary *)element + andChildren:(NSArray *)children { + self = [super init]; + if (self) { + _nodeId = nodeId; + _isMatch = isMatch; + _element = element; + _children = children; + } + return self; +} + +- (NSDictionary *)toNSDictionary { + if (_element == nil) { + return nil; + } + NSMutableArray *childArray; + if (_children) { + childArray = [NSMutableArray new]; + for (SKSearchResultNode *child in _children) { + NSDictionary *childDict = [child toNSDictionary]; + if (childDict) { + [childArray addObject:childDict]; + } + } + } else { + childArray = nil; + } + return @{ + @"id": _nodeId, + @"isMatch": @(_isMatch), + @"element": _element, + @"children": childArray ?: [NSNull null] + }; +} + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListener.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListener.h new file mode 100644 index 000000000..87ccb3d60 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListener.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +typedef void (^SKTapReceiver)(CGPoint touchPoint); + +@protocol SKTapListener + +@property (nonatomic, readonly) BOOL isMounted; + +- (void)mountWithFrame:(CGRect)frame; + +- (void)unmount; + +- (void)listenForTapWithBlock:(SKTapReceiver)receiver; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListenerImpl.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListenerImpl.h new file mode 100644 index 000000000..73d23722b --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListenerImpl.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "SKTapListener.h" + +@interface SKTapListenerImpl : NSObject + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListenerImpl.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListenerImpl.m new file mode 100644 index 000000000..08af8d5f5 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListenerImpl.m @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKTapListenerImpl.h" + +#import "SKHighlightOverlay.h" +#import "SKHiddenWindow.h" + +@implementation SKTapListenerImpl +{ + NSMutableArray *_receiversWaitingForInput; + UITapGestureRecognizer *_gestureRecognizer; + + SKHiddenWindow *_overlayWindow; +} + +@synthesize isMounted = _isMounted; + +- (instancetype)init { + if (self = [super init]) { + _receiversWaitingForInput = [NSMutableArray new]; + + _gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget: self action: nil]; + _gestureRecognizer.delegate = self; + + _isMounted = NO; + + _overlayWindow = [SKHiddenWindow new]; + _overlayWindow.hidden = YES; + _overlayWindow.windowLevel = UIWindowLevelAlert; + _overlayWindow.backgroundColor = [SKHighlightOverlay overlayColor]; + + [_overlayWindow addGestureRecognizer: _gestureRecognizer]; + } + + return self; +} + +- (void)mountWithFrame:(CGRect)frame { + if (_isMounted) { + return; + } + + [_overlayWindow setFrame: frame]; + [_overlayWindow makeKeyAndVisible]; + _overlayWindow.hidden = NO; + + _isMounted = YES; +} + +- (void)unmount { + if (!_isMounted) { + return; + } + + [_receiversWaitingForInput removeAllObjects]; + [_overlayWindow removeFromSuperview]; + _overlayWindow.hidden = YES; + + _isMounted = NO; +} + +- (void)listenForTapWithBlock:(SKTapReceiver)receiver { + [_receiversWaitingForInput addObject: receiver]; +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { + if ([_receiversWaitingForInput count] == 0) { + return YES; + } + + CGPoint touchPoint = [touch locationInView: _overlayWindow]; + + for (SKTapReceiver recv in _receiversWaitingForInput) { + recv(touchPoint); + } + + [_receiversWaitingForInput removeAllObjects]; + + return NO; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTouch.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTouch.h new file mode 100644 index 000000000..9b2633fdb --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTouch.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import "SKDescriptorMapper.h" + +typedef void (^SKTouchFinishDelegate)(NSArray *path); + +@interface SKTouch : NSObject + +- (instancetype)initWithTouchPoint:(CGPoint)touchPoint + withRootNode:(id)node + withDescriptorMapper:(SKDescriptorMapper *)mapper + finishWithBlock:(SKTouchFinishDelegate)d; + +- (void)continueWithChildIndex:(NSUInteger)childIndex + withOffset:(CGPoint)offset; + +- (void)finish; + +- (BOOL)containedIn:(CGRect)bounds; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTouch.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTouch.m new file mode 100644 index 000000000..72cab85bf --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTouch.m @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKTouch.h" +#import "SKNodeDescriptor.h" + +@implementation SKTouch +{ + SKTouchFinishDelegate _onFinish; + NSMutableArray *_path; + + CGPoint _currentTouchPoint; + id _currentNode; + + SKDescriptorMapper *_descriptorMapper; +} + +- (instancetype)initWithTouchPoint:(CGPoint)touchPoint + withRootNode:(id)node + withDescriptorMapper:(SKDescriptorMapper *)mapper + finishWithBlock:(SKTouchFinishDelegate)finishBlock { + if (self = [super init]) { + _onFinish = finishBlock; + _currentTouchPoint = touchPoint; + _currentNode = node; + _descriptorMapper = mapper; + _path = [NSMutableArray new]; + } + + return self; +} + +- (void)continueWithChildIndex:(NSUInteger)childIndex withOffset:(CGPoint)offset { + _currentTouchPoint.x -= offset.x; + _currentTouchPoint.y -= offset.y; + + SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [_currentNode class]]; + _currentNode = [descriptor childForNode: _currentNode atIndex: childIndex]; + + descriptor = [_descriptorMapper descriptorForClass: [_currentNode class]]; + [_path addObject: [descriptor identifierForNode: _currentNode]]; + + [descriptor hitTest: self forNode: _currentNode]; +} + +- (void)finish { + _onFinish(_path); +} + +- (BOOL)containedIn:(CGRect)bounds { + return CGRectContainsPoint(bounds, _currentTouchPoint); +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SonarKitLayoutPlugin.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SonarKitLayoutPlugin.h new file mode 100644 index 000000000..db1e31777 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SonarKitLayoutPlugin.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import + +#import + +#import "SKTapListener.h" +#import "SKInvalidation.h" +#import "SKDescriptorMapper.h" + +@interface SonarKitLayoutPlugin : NSObject + +- (instancetype)initWithRootNode:(id)rootNode + withDescriptorMapper:(SKDescriptorMapper *)mapper; + +- (instancetype)initWithRootNode:(id)rootNode + withTapListener:(id)tapListener + withDescriptorMapper:(SKDescriptorMapper *)mapper; + +@property (nonatomic, readonly, strong) SKDescriptorMapper *descriptorMapper; + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SonarKitLayoutPlugin.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SonarKitLayoutPlugin.mm new file mode 100644 index 000000000..2e75a97a8 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SonarKitLayoutPlugin.mm @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SonarKitLayoutPlugin.h" + +#import +#import +#import +#import +#import "SKDescriptorMapper.h" +#import "SKNodeDescriptor.h" +#import "SKTapListener.h" +#import "SKTapListenerImpl.h" +#import "SKSearchResultNode.h" +#import + +@implementation SonarKitLayoutPlugin +{ + + NSMapTable *_trackedObjects; + NSString *_lastHighlightedNode; + NSMutableSet *_invalidObjects; + Boolean _invalidateMessageQueued; + NSDate *_lastInvalidateMessage; + std::mutex invalidObjectsMutex; + + id _rootNode; + id _tapListener; + + id _connection; + + NSMutableSet *_registeredDelegates; +} + +- (instancetype)initWithRootNode:(id)rootNode + withDescriptorMapper:(SKDescriptorMapper *)mapper{ + return [self initWithRootNode: rootNode + withTapListener: [SKTapListenerImpl new] + withDescriptorMapper: mapper]; +} + +- (instancetype)initWithRootNode:(id)rootNode + withTapListener:(id)tapListener + withDescriptorMapper:(SKDescriptorMapper *)mapper { + if (self = [super init]) { + _descriptorMapper = mapper; + _trackedObjects = [NSMapTable strongToWeakObjectsMapTable]; + _lastHighlightedNode = nil; + _invalidObjects = [NSMutableSet new]; + _invalidateMessageQueued = false; + _lastInvalidateMessage = [NSDate date]; + _rootNode = rootNode; + _tapListener = tapListener; + + _registeredDelegates = [NSMutableSet new]; + [SKInvalidation sharedInstance].delegate = self; + } + + return self; +} + +- (NSString *)identifier +{ + return @"Inspector"; +} + +- (void)didConnect:(id)connection { + _connection = connection; + + [SKInvalidation enableInvalidations]; + + // Run setup logic for each descriptor + for (SKNodeDescriptor *descriptor in _descriptorMapper.allDescriptors) { + [descriptor setUp]; + } + + // In order to avoid a retain cycle (Connection -> Block -> SonarKitLayoutPlugin -> Connection ...) + __weak SonarKitLayoutPlugin *weakSelf = self; + + [connection receive:@"getRoot" withBlock:^(NSDictionary *params, id responder) { + SonarPerformBlockOnMainThread(^{ [weakSelf onCallGetRoot: responder]; }); + }]; + + [connection receive:@"getNodes" withBlock:^(NSDictionary *params, id responder) { + SonarPerformBlockOnMainThread(^{ [weakSelf onCallGetNodes: params[@"ids"] withResponder: responder]; }); + }]; + + [connection receive:@"setData" withBlock:^(NSDictionary *params, id responder) { + SonarPerformBlockOnMainThread(^{ + [weakSelf onCallSetData: params[@"id"] + withPath: params[@"path"] + toValue: params[@"value"] + withConnection: connection]; + }); + }]; + + [connection receive:@"setHighlighted" withBlock:^(NSDictionary *params, id responder) { + SonarPerformBlockOnMainThread(^{ [weakSelf onCallSetHighlighted: params[@"id"] withResponder: responder]; }); + }]; + + [connection receive:@"setSearchActive" withBlock:^(NSDictionary *params, id responder) { + SonarPerformBlockOnMainThread(^{ [weakSelf onCallSetSearchActive: [params[@"active"] boolValue] withConnection: connection]; }); + }]; + + [connection receive:@"isConsoleEnabled" withBlock:^(NSDictionary *params, id responder) { + SonarPerformBlockOnMainThread(^{ [responder success: @{@"isEnabled": @NO}];}); + }]; + + [connection receive:@"getSearchResults" withBlock:^(NSDictionary *params, id responder) { + SonarPerformBlockOnMainThread(^{ [weakSelf onCallGetSearchResults: params[@"query"] withResponder: responder]; }); + }]; +} + +- (void)didDisconnect { + // Clear the last highlight if there is any + [self onCallSetHighlighted: nil withResponder: nil]; + // Disable search if it is active + [self onCallSetSearchActive: NO withConnection: nil]; +} + +- (void)onCallGetRoot:(id)responder { + const auto rootNode= [self getNode: [self trackObject: _rootNode]]; + + [responder success: rootNode]; +} + +- (void)onCallGetNodes:(NSArray *)nodeIds withResponder:(id)responder { + NSMutableArray *elements = [NSMutableArray new]; + + for (id nodeId in nodeIds) { + const auto node = [self getNode: nodeId]; + if (node == nil) { + continue; + } + [elements addObject: node]; + } + + [responder success: @{ @"elements": elements }]; +} + +- (void)onCallSetData:(NSString *)objectId + withPath:(NSArray *)path + toValue:(id)value + withConnection:(id)connection { + id node = [_trackedObjects objectForKey: objectId]; + if (node == nil) { + SKLog(@"node is nil, trying to setData: \ + objectId: %@ \ + path: %@ \ + value: %@", + objectId, path, value); + return; + } + + // Sonar sends nil/NSNull on some values when the text-field + // is empty, disregard these changes otherwise we'll crash. + if (value == nil || [value isKindOfClass: [NSNull class]]) { + return; + } + + SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [node class]]; + + NSString *dotJoinedPath = [path componentsJoinedByString: @"."]; + SKNodeUpdateData updateDataForPath = [[descriptor dataMutationsForNode: node] objectForKey: dotJoinedPath]; + if (updateDataForPath != nil) { + updateDataForPath(value); + [connection send: @"invalidate" withParams: @{ @"id": [descriptor identifierForNode: node] }]; + } +} + +- (void)onCallGetSearchResults:(NSString *)query withResponder:(id)responder { + const auto alreadyAddedElements = [NSMutableSet new]; + SKSearchResultNode *matchTree = [self searchForQuery:(NSString *)[query lowercaseString] fromNode:(id)_rootNode withElementsAlreadyAdded: alreadyAddedElements]; + + [responder success: @{ + @"results": [matchTree toNSDictionary] ?: [NSNull null], + @"query": query + }]; + return; +} + +- (void)onCallSetHighlighted:(NSString *)objectId withResponder:(id)responder { + if (_lastHighlightedNode != nil) { + id lastHighlightedObject = [_trackedObjects objectForKey: _lastHighlightedNode]; + if (lastHighlightedObject == nil) { + [responder error: @{ @"error": @"unable to get last highlighted object" }]; + return; + } + + SKNodeDescriptor *descriptor = [self->_descriptorMapper descriptorForClass: [lastHighlightedObject class]]; + [descriptor setHighlighted: NO forNode: lastHighlightedObject]; + + _lastHighlightedNode = nil; + } + + if (objectId == nil || [objectId isKindOfClass:[NSNull class]]) { + return; + } + + id object = [_trackedObjects objectForKey: objectId]; + if (object == nil) { + SKLog(@"tried to setHighlighted for untracked id, objectId: %@", objectId); + return; + } + + SKNodeDescriptor *descriptor = [self->_descriptorMapper descriptorForClass: [object class]]; + [descriptor setHighlighted: YES forNode: object]; + + _lastHighlightedNode = objectId; +} + +- (void)onCallSetSearchActive:(BOOL)active withConnection:(id)connection { + if (active) { + [_tapListener mountWithFrame: [[UIScreen mainScreen] bounds]]; + __block id rootNode = _rootNode; + + [_tapListener listenForTapWithBlock:^(CGPoint touchPoint) { + SKTouch *touch = + [[SKTouch alloc] initWithTouchPoint: touchPoint + withRootNode: rootNode + withDescriptorMapper: self->_descriptorMapper + finishWithBlock:^(NSArray *path) { + [connection send: @"select" + withParams: @{ @"path": path }]; + }]; + + SKNodeDescriptor *descriptor = [self->_descriptorMapper descriptorForClass: [rootNode class]]; + [descriptor hitTest: touch forNode: rootNode]; + }]; + } else { + [_tapListener unmount]; + } +} + +- (void)invalidateNode:(id)node { + SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [node class]]; + if (descriptor == nil) { + return; + } + + NSString *nodeId = [descriptor identifierForNode: node]; + if (![_trackedObjects objectForKey: nodeId]) { + return; + } + [descriptor invalidateNode: node]; + + // Collect invalidate messages before sending in a batch + std::lock_guard lock(invalidObjectsMutex); + [_invalidObjects addObject:nodeId]; + if (_invalidateMessageQueued) { + return; + } + _invalidateMessageQueued = true; + + if (_lastInvalidateMessage.timeIntervalSinceNow < -1) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{ + [self reportInvalidatedObjects]; + }); + } +} + +- (void)reportInvalidatedObjects { + std::lock_guard lock(invalidObjectsMutex); + NSMutableArray *nodes = [NSMutableArray new]; + for (NSString *nodeId in self->_invalidObjects) { + [nodes addObject: [NSDictionary dictionaryWithObject: nodeId forKey: @"id"]]; + } + [self->_connection send: @"invalidate" withParams: [NSDictionary dictionaryWithObject: nodes forKey: @"nodes"]]; + self->_lastInvalidateMessage = [NSDate date]; + self->_invalidObjects = [NSMutableSet new]; + self->_invalidateMessageQueued = false; + return; +} + +- (void)updateNodeReference:(id)node { + SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [node class]]; + if (descriptor == nil) { + return; + } + + NSString *nodeId = [descriptor identifierForNode: node]; + [_trackedObjects setObject:node forKey:nodeId]; +} + +- (SKSearchResultNode *)searchForQuery:(NSString *)query fromNode:(id)node withElementsAlreadyAdded:(NSMutableSet *)alreadyAdded { + SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [node class]]; + if (node == nil || descriptor == nil) { + return nil; + } + + NSMutableArray *childTrees = nil; + BOOL isMatch = [descriptor matchesQuery: query forNode: node]; + + NSString *nodeId = [self trackObject: node]; + + for (auto i = 0; i < [descriptor childCountForNode: node]; i++) { + id child = [descriptor childForNode: node atIndex: i]; + if (child) { + SKSearchResultNode *childTree = [self searchForQuery: query fromNode: child withElementsAlreadyAdded:alreadyAdded]; + if (childTree != nil) { + if (childTrees == nil) { + childTrees = [NSMutableArray new]; + } + [childTrees addObject: childTree]; + } + } + } + + if (isMatch || childTrees != nil) { + + NSDictionary *element = [self getNode: nodeId]; + if (nodeId == nil || element == nil) { + return nil; + } + NSMutableArray *descriptorChildElements = [element objectForKey: @"children"]; + NSMutableDictionary *newElement = [element mutableCopy]; + + NSMutableArray *childElementsToReturn = [NSMutableArray new]; + for (NSString *child in descriptorChildElements) { + if (![alreadyAdded containsObject: child]) { + [alreadyAdded addObject: child]; //todo add all at end + [childElementsToReturn addObject: child]; + } + } + [newElement setObject: childElementsToReturn forKey: @"children"]; + return [[SKSearchResultNode alloc] initWithNode: nodeId + asMatch: isMatch + withElement: newElement + andChildren: childTrees]; + } + return nil; +} + +- (NSDictionary *)getNode:(NSString *)nodeId { + id node = [_trackedObjects objectForKey: nodeId]; + if (node == nil) { + SKLog(@"node is nil, no tracked node found for nodeId: %@", nodeId); + return nil; + } + + SKNodeDescriptor *nodeDescriptor = [_descriptorMapper descriptorForClass: [node class]]; + if (nodeDescriptor == nil) { + SKLog(@"No registered descriptor for class: %@", [node class]); + return nil; + } + + NSMutableArray *attributes = [NSMutableArray new]; + NSMutableDictionary *data = [NSMutableDictionary new]; + + const auto *nodeAttributes = [nodeDescriptor attributesForNode: node]; + for (const SKNamed *namedPair in nodeAttributes) { + const auto name = namedPair.name; + if (name) { + const NSDictionary *attribute = @{ + @"name": name, + @"value": namedPair.value ?: [NSNull null], + }; + [attributes addObject: attribute]; + } + } + + const auto *nodeData = [nodeDescriptor dataForNode: node]; + for (const SKNamed *namedPair in nodeData) { + data[namedPair.name] = namedPair.value; + } + + NSMutableArray *children = [NSMutableArray new]; + for (NSUInteger i = 0; i < [nodeDescriptor childCountForNode: node]; i++) { + id childNode = [nodeDescriptor childForNode: node atIndex: i]; + + NSString *childIdentifier = [self trackObject: childNode]; + if (childIdentifier) { + [children addObject: childIdentifier]; + } + } + + NSDictionary *nodeDic = + @{ + // We shouldn't get nil for id/name/decoration, but let's not crash if we do. + @"id": [nodeDescriptor identifierForNode: node] ?: @"(unknown)", + @"name": [nodeDescriptor nameForNode: node] ?: @"(unknown)", + @"children": children, + @"attributes": attributes, + @"data": data, + @"decoration": [nodeDescriptor decorationForNode: node] ?: @"(unknown)", + }; + + return nodeDic; +} + +- (NSString *)trackObject:(id)object { + const SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [object class]]; + NSString *objectIdentifier = [descriptor identifierForNode: object]; + + if (objectIdentifier == nil) { + return nil; + } + + if (! [_trackedObjects objectForKey: objectIdentifier]) { + [_trackedObjects setObject:object forKey:objectIdentifier]; + } + + return objectIdentifier; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UICollectionView+SKInvalidation.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UICollectionView+SKInvalidation.h new file mode 100644 index 000000000..f2b013d84 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UICollectionView+SKInvalidation.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import + +FB_LINK_REQUIRE(UICollectionView_SKInvalidation) +@interface UICollectionView (SKInvalidation) + ++ (void)enableInvalidations; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UICollectionView+SKInvalidation.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UICollectionView+SKInvalidation.mm new file mode 100644 index 000000000..b33e58094 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UICollectionView+SKInvalidation.mm @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "UICollectionView+SKInvalidation.h" + +#import "SKInvalidation.h" +#import "SKSwizzle.h" + +FB_LINKABLE(UICollectionView_SKInvalidation) +@implementation UICollectionView (SKInvalidation) + ++ (void)enableInvalidations { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + swizzleMethods([self class], @selector(cellForItemAtIndexPath:), @selector(swizzle_cellForItemAtIndexPath:)); + }); +} + +- (UICollectionViewCell *)swizzle_cellForItemAtIndexPath:(NSIndexPath *)indexPath { + dispatch_async(dispatch_get_main_queue(), ^{ + [[SKInvalidation sharedInstance].delegate invalidateNode: self]; + }); + + return [self swizzle_cellForItemAtIndexPath: indexPath]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIColor+SKSonarValueCoder.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIColor+SKSonarValueCoder.h new file mode 100644 index 000000000..811204077 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIColor+SKSonarValueCoder.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import + +#import "SKObject.h" + +FB_LINK_REQUIRE(UIColor_SonarValueCoder) +@interface UIColor (SonarValueCoder) + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIColor+SKSonarValueCoder.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIColor+SKSonarValueCoder.mm new file mode 100644 index 000000000..4540be2a3 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIColor+SKSonarValueCoder.mm @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "UIColor+SKSonarValueCoder.h" + +FB_LINKABLE(UIColor_SonarValueCoder) +@implementation UIColor (SonarValueCoder) + ++ (instancetype)fromSonarValue:(NSNumber *)sonarValue { + NSUInteger intColor = [sonarValue integerValue]; + + CGFloat r, g, b, a; + + b = CGFloat(intColor & 0xFF) / 255; + g = CGFloat((intColor >> 8) & 0xFF) / 255; + r = CGFloat((intColor >> 16) & 0xFF) / 255; + a = CGFloat((intColor >> 24) & 0xFF) / 255; + + return [[UIColor alloc] initWithRed: r green: g blue: b alpha: a]; +} + +- (NSDictionary> *)sonarValue { + CGColorSpaceRef colorSpace = CGColorGetColorSpace([self CGColor]); + CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace); + + NSUInteger red, green, blue, alpha; + + switch (colorSpaceModel) { + case kCGColorSpaceModelUnknown: + case kCGColorSpaceModelRGB: { + CGFloat r, g, b, a; + [self getRed: &r green: &g blue: &b alpha: &a]; + + red = (NSUInteger)(r * 255) & 0xFF; + green = (NSUInteger)(g * 255) & 0xFF; + blue = (NSUInteger)(b * 255) & 0xFF; + alpha = (NSUInteger)(a * 255) & 0xFF; + } break; + + case kCGColorSpaceModelMonochrome: { + CGFloat a, w; + [self getWhite: &w alpha: &a]; + + red = green = blue = (NSUInteger)(w * 255) & 0xFF; + alpha = (NSUInteger)(a * 255) & 0xFF; + } break; + + default: + red = green = blue = alpha = 0; + } + + NSUInteger intColor = (alpha << 24) | (red << 16) | (green << 8) | blue; + return @{ + @"__type__": @"color", + @"__mutable__": @NO, + @"value": @(intColor) + }; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIView+SKInvalidation.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIView+SKInvalidation.h new file mode 100644 index 000000000..ee0699925 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIView+SKInvalidation.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +FB_LINK_REQUIRE(UIView_SKInvalidation) +@interface UIView (SKInvalidation) + ++ (void)enableInvalidation; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIView+SKInvalidation.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIView+SKInvalidation.mm new file mode 100644 index 000000000..ca1e6acf3 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIView+SKInvalidation.mm @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import +#import + +#import "SKInvalidation.h" +#import "SKSwizzle.h" +#import "UIView+SKInvalidation.h" + +FB_LINKABLE(UIView_SKInvalidation) +@implementation UIView (SKInvalidation) + ++ (void)enableInvalidation { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + swizzleMethods([self class], @selector(setHidden:), @selector(swizzle_setHidden:)); + swizzleMethods([self class], @selector(addSubview:), @selector(swizzle_addSubview:)); + swizzleMethods([self class], @selector(removeFromSuperview), @selector(swizzle_removeFromSuperview)); + }); +} + +- (void)swizzle_setHidden:(BOOL)hidden { + [self swizzle_setHidden: hidden]; + + id delegate = [SKInvalidation sharedInstance].delegate; + if (delegate != nil) { + [delegate invalidateNode: self.superview]; + } +} + +- (void)swizzle_addSubview:(UIView *)view { + [self swizzle_addSubview: view]; + + id delegate = [SKInvalidation sharedInstance].delegate; + if (delegate != nil) { + [delegate invalidateNode: view]; + } +} + +- (void)swizzle_removeFromSuperview { + id delegate = [SKInvalidation sharedInstance].delegate; + if (delegate != nil && self.superview != nil) { + [delegate invalidateNode: self.superview]; + } + + [self swizzle_removeFromSuperview]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKApplicationDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKApplicationDescriptor.h new file mode 100644 index 000000000..8b4b7c4fa --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKApplicationDescriptor.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import "SKNodeDescriptor.h" + +@interface SKApplicationDescriptor : SKNodeDescriptor + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKApplicationDescriptor.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKApplicationDescriptor.m new file mode 100644 index 000000000..9221f112d --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKApplicationDescriptor.m @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKApplicationDescriptor.h" + +#import "SKDescriptorMapper.h" +#import "SKHiddenWindow.h" +#import + +@implementation SKApplicationDescriptor + +- (NSString *)identifierForNode:(UIApplication *)node { + return [NSString stringWithFormat: @"%p", node]; +} + +- (NSUInteger)childCountForNode:(UIApplication *)node { + return [[self visibleChildrenForNode: node] count]; +} + +- (id)childForNode:(UIApplication *)node atIndex:(NSUInteger)index { + return [self visibleChildrenForNode: node][index]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(UIApplication *)node { + SKNodeDescriptor *windowDescriptor = [self descriptorForClass: [UIWindow class]]; + [windowDescriptor setHighlighted: highlighted forNode: [node keyWindow]]; +} + +- (void)hitTest:(SKTouch *)touch forNode:(UIApplication *)node { + for (NSInteger index = [self childCountForNode: node] - 1; index >= 0; index--) { + UIWindow *child = [self childForNode: node atIndex: index]; + if (child.isHidden || child.alpha <= 0) { + continue; + } + + if ([touch containedIn: child.frame]) { + [touch continueWithChildIndex: index withOffset: child.frame.origin]; + return; + } + } + + [touch finish]; +} + +- (NSArray *)visibleChildrenForNode:(UIApplication *)node { + NSMutableArray *children = [NSMutableArray new]; + for (UIWindow *window in node.windows) { + if ([window isKindOfClass: [SKHiddenWindow class]] + || [window isKindOfClass:objc_lookUpClass("FBAccessibilityOverlayWindow")] + || [window isKindOfClass:objc_lookUpClass("UITextEffectsWindow")]) { + continue; + } + [children addObject: window]; + } + return children; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKButtonDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKButtonDescriptor.h new file mode 100644 index 000000000..cd76d94b0 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKButtonDescriptor.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import "SKNodeDescriptor.h" + +@class UIButton; + +@interface SKButtonDescriptor : SKNodeDescriptor + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKButtonDescriptor.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKButtonDescriptor.mm new file mode 100644 index 000000000..e486fbd21 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKButtonDescriptor.mm @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKButtonDescriptor.h" + +#import "SKDescriptorMapper.h" +#import "SKObject.h" +#import "UIColor+SKSonarValueCoder.h" + +@implementation SKButtonDescriptor + +- (NSString *)identifierForNode:(UIButton *)node { + return [NSString stringWithFormat: @"%p", node]; +} + +- (NSUInteger)childCountForNode:(UIButton *)node { + return 0; +} + +- (id)childForNode:(UIButton *)node atIndex:(NSUInteger)index { + return nil; +} + +- (NSArray *> *)dataForNode:(UIButton *)node { + SKNodeDescriptor *viewDescriptor = [self descriptorForClass: [UIView class]]; + auto *viewData = [viewDescriptor dataForNode: node]; + + NSMutableArray *data = [NSMutableArray new]; + [data addObjectsFromArray: viewData]; + [data addObject: + [SKNamed newWithName: @"UIButton" + withValue: @{ + @"focused": @(node.focused), + @"enabled": SKMutableObject(@(node.enabled)), + @"highlighted": SKMutableObject(@(node.highlighted)), + @"titleEdgeInsets": SKObject(node.titleEdgeInsets), + @"titleLabel": SKMutableObject(node.titleLabel.attributedText.string.stringByStandardizingPath), + @"currentTitleColor": SKMutableObject(node.currentTitleColor), + }] + ]; + + return data; +} + +- (NSDictionary *)dataMutationsForNode:(UIButton *)node { + NSDictionary *buttonMutations = @{ + @"UIButton.titleLabel": ^(NSString *newValue) { + [node setTitle: newValue forState: node.state]; + }, + @"UIButton.currentTitleColor": ^(NSNumber *newValue) { + [node setTitleColor: [UIColor fromSonarValue: newValue] forState: node.state]; + }, + @"UIButton.highlighted": ^(NSNumber *highlighted) { + [node setHighlighted: [highlighted boolValue]]; + }, + @"UIButton.enabled": ^(NSNumber *enabled) { + [node setEnabled: [enabled boolValue]]; + } + }; + + SKNodeDescriptor *viewDescriptor = [self descriptorForClass: [UIView class]]; + NSDictionary *viewMutations = [viewDescriptor dataMutationsForNode: node]; + + NSMutableDictionary *mutations = [NSMutableDictionary new]; + [mutations addEntriesFromDictionary: buttonMutations]; + [mutations addEntriesFromDictionary: viewMutations]; + + return mutations; +} + +- (NSArray *> *)attributesForNode:(UIScrollView *)node { + SKNodeDescriptor *descriptor = [self descriptorForClass: [UIView class]]; + return [descriptor attributesForNode: node]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(UIButton *)node { + SKNodeDescriptor *viewDescriptor = [self descriptorForClass: [UIView class]]; + [viewDescriptor setHighlighted: highlighted forNode: node]; +} + +- (void)hitTest:(SKTouch *)touch forNode:(UIButton *)node { + [touch finish]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKScrollViewDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKScrollViewDescriptor.h new file mode 100644 index 000000000..4a221d93c --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKScrollViewDescriptor.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import "SKNodeDescriptor.h" + +@interface SKScrollViewDescriptor : SKNodeDescriptor + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKScrollViewDescriptor.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKScrollViewDescriptor.m new file mode 100644 index 000000000..66e7991e4 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKScrollViewDescriptor.m @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKScrollViewDescriptor.h" + +#import "SKDescriptorMapper.h" + +@implementation SKScrollViewDescriptor + +- (NSString *)identifierForNode:(UIScrollView *)node { + SKNodeDescriptor *descriptor = [self descriptorForClass: [UIView class]]; + return [descriptor identifierForNode: node]; +} + +- (NSUInteger)childCountForNode:(UIScrollView *)node { + SKNodeDescriptor *descriptor = [self descriptorForClass: [UIView class]]; + return [descriptor childCountForNode: node]; +} + +- (id)childForNode:(UIScrollView *)node atIndex:(NSUInteger)index { + SKNodeDescriptor *descriptor = [self descriptorForClass: [UIView class]]; + return [descriptor childForNode: node atIndex: index]; +} + +- (id)dataForNode:(UIScrollView *)node { + SKNodeDescriptor *descriptor = [self descriptorForClass: [UIView class]]; + return [descriptor dataForNode:node]; +} + +- (id)dataMutationsForNode:(UIScrollView *)node { + SKNodeDescriptor *descriptor = [self descriptorForClass: [UIView class]]; + return [descriptor dataMutationsForNode:node]; +} + +- (NSArray *> *)attributesForNode:(UIScrollView *)node { + SKNodeDescriptor *descriptor = [self descriptorForClass: [UIView class]]; + return [descriptor attributesForNode: node]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(UIScrollView *)node { + SKNodeDescriptor *descriptor = [self descriptorForClass: [UIView class]]; + [descriptor setHighlighted: highlighted forNode: node]; +} + +- (void)hitTest:(SKTouch *)touch forNode:(UIScrollView *)node { + for (NSInteger index = [self childCountForNode: node] - 1; index >= 0; index--) { + id childNode = [self childForNode: node atIndex: index]; + CGRect frame; + + if ([childNode isKindOfClass: [UIViewController class]]) { + UIViewController *child = (UIViewController *)childNode; + if (child.view.isHidden) { + continue; + } + + frame = child.view.frame; + } else { + UIView *child = (UIView *)childNode; + if (child.isHidden) { + continue; + } + + frame = child.frame; + } + + frame.origin.x -= node.contentOffset.x; + frame.origin.y -= node.contentOffset.y; + + if ([touch containedIn: frame]) { + [touch continueWithChildIndex: index withOffset: frame.origin]; + return; + } + } + + [touch finish]; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewControllerDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewControllerDescriptor.h new file mode 100644 index 000000000..6e7280899 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewControllerDescriptor.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import "SKNodeDescriptor.h" + +@interface SKViewControllerDescriptor : SKNodeDescriptor + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewControllerDescriptor.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewControllerDescriptor.m new file mode 100644 index 000000000..f1fab8858 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewControllerDescriptor.m @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKViewControllerDescriptor.h" + +#import "SKDescriptorMapper.h" + +@implementation SKViewControllerDescriptor + +- (NSString *)identifierForNode:(UIViewController *)node { + return [NSString stringWithFormat: @"%p", node]; +} + +- (NSUInteger)childCountForNode:(UIViewController *)node { + return 1; +} + +- (id)childForNode:(UIViewController *)node atIndex:(NSUInteger)index { + return node.view; +} + +- (void)setHighlightedForNode:(UIViewController *)node { +} + +- (NSArray *> *)attributesForNode:(UIViewController *)node { + return @[ + [SKNamed newWithName: @"addr" + withValue: [NSString stringWithFormat: @"%p", node]] + ]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(UIViewController *)node { + SKNodeDescriptor *descriptor = [self descriptorForClass: [UIView class]]; + [descriptor setHighlighted: highlighted forNode: node.view]; +} + +- (void)hitTest:(SKTouch *)touch forNode:(UIViewController *)node { + [touch continueWithChildIndex: 0 withOffset: (CGPoint){ 0, 0}]; +} + + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewDescriptor.h new file mode 100644 index 000000000..410e83c79 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewDescriptor.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import + +#import "SKNodeDescriptor.h" + +@interface SKViewDescriptor : SKNodeDescriptor + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewDescriptor.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewDescriptor.mm new file mode 100644 index 000000000..c6b1be3f7 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/descriptors/SKViewDescriptor.mm @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKViewDescriptor.h" + +#import "SKDescriptorMapper.h" +#import "SKHighlightOverlay.h" +#import "SKNamed.h" +#import "SKObject.h" +#import "SKYogaKitHelper.h" +#import "UIColor+SKSonarValueCoder.h" +#import + +@implementation SKViewDescriptor + +static NSDictionary *YGDirectionEnumMap = nil; +static NSDictionary *YGFlexDirectionEnumMap = nil; +static NSDictionary *YGJustifyEnumMap = nil; +static NSDictionary *YGAlignEnumMap = nil; +static NSDictionary *YGPositionTypeEnumMap = nil; +static NSDictionary *YGWrapEnumMap = nil; +static NSDictionary *YGOverflowEnumMap = nil; +static NSDictionary *YGDisplayEnumMap = nil; +static NSDictionary *YGUnitEnumMap = nil; + +- (instancetype)initWithDescriptorMapper:(SKDescriptorMapper *)mapper { + if (self = [super initWithDescriptorMapper: mapper]) { + initEnumDictionaries(); + } + + return self; +} + +- (NSString *)identifierForNode:(UIView *)node { + return [NSString stringWithFormat: @"%p", node]; +} + +- (NSUInteger)childCountForNode:(UIView *)node { + return [[self validChildrenForNode: node] count]; +} + +- (id)childForNode:(UIView *)node atIndex:(NSUInteger)index { + return [[self validChildrenForNode:node] objectAtIndex: index]; +} + +- (NSArray *)validChildrenForNode:(UIView *)node { + NSMutableArray *validChildren = [NSMutableArray new]; + + // Use UIViewControllers for children which responds to a different + // viewController than their parent + for (UIView *child in node.subviews) { + BOOL responderIsUIViewController = [child.nextResponder isKindOfClass: [UIViewController class]]; + + if (!child.isHidden) { + if (responderIsUIViewController && child.nextResponder != node.nextResponder) { + [validChildren addObject: child.nextResponder]; + } else { + [validChildren addObject: child]; + } + } + } + + return validChildren; +} + +- (NSArray *> *)dataForNode:(UIView *)node { + return [NSArray arrayWithObjects: + [SKNamed newWithName: @"UIView" + withValue: @{ + @"frame": SKMutableObject(node.frame), + @"bounds": SKObject(node.bounds), + @"center": SKObject(node.center), + @"layoutMargins": SKObject(node.layoutMargins), + @"clipsToBounds": @(node.clipsToBounds), + @"alpha": SKMutableObject(@(node.alpha)), + @"tag": @(node.tag), + @"backgroundColor": SKMutableObject(node.backgroundColor) + }], + [SKNamed newWithName: @"CALayer" + withValue: @{ + @"shadowColor": SKMutableObject([UIColor colorWithCGColor:node.layer.shadowColor]), + @"shadowOpacity": SKMutableObject(@(node.layer.shadowOpacity)), + @"shadowRadius": SKMutableObject(@(node.layer.shadowRadius)), + @"shadowOffset": SKMutableObject(node.layer.shadowOffset), + @"backgroundColor": SKMutableObject([UIColor colorWithCGColor:node.layer.backgroundColor]), + @"borderColor": SKMutableObject([UIColor colorWithCGColor:node.layer.borderColor]), + @"borderWidth": SKMutableObject(@(node.layer.borderWidth)), + @"cornerRadius": SKMutableObject(@(node.layer.cornerRadius)), + @"masksToBounds": SKMutableObject(@(node.layer.masksToBounds)), + }], + [SKNamed newWithName: @"Accessibility" + withValue: @{ + @"isAccessibilityElement": SKMutableObject(@(node.isAccessibilityElement)), + @"accessibilityLabel": SKMutableObject(node.accessibilityLabel ?: @""), + @"accessibilityValue": SKMutableObject(node.accessibilityValue ?: @""), + @"accessibilityHint": SKMutableObject(node.accessibilityHint ?: @""), + @"accessibilityTraits": AccessibilityTraitsDict(node.accessibilityTraits), + @"accessibilityViewIsModal": SKMutableObject(@(node.accessibilityViewIsModal)), + @"shouldGroupAccessibilityChildren": SKMutableObject(@(node.shouldGroupAccessibilityChildren)), + }], + !node.isYogaEnabled ? nil : + [SKNamed newWithName: @"YGLayout" + withValue: @{ + @"direction": SKMutableObject(YGDirectionEnumMap[@(node.yoga.direction)]), + @"justifyContent": SKMutableObject(YGJustifyEnumMap[@(node.yoga.justifyContent)]), + @"aligns": @{ + @"alignContent": SKMutableObject(YGAlignEnumMap[@(node.yoga.alignContent)]), + @"alignItems": SKMutableObject(YGAlignEnumMap[@(node.yoga.alignItems)]), + @"alignSelf": SKMutableObject(YGAlignEnumMap[@(node.yoga.alignSelf)]), + }, + @"position": @{ + @"type": SKMutableObject(YGPositionTypeEnumMap[@(node.yoga.position)]), + @"left": SKYGValueObject(node.yoga.left), + @"top": SKYGValueObject(node.yoga.top), + @"right": SKYGValueObject(node.yoga.right), + @"bottom": SKYGValueObject(node.yoga.bottom), + @"start": SKYGValueObject(node.yoga.start), + @"end": SKYGValueObject(node.yoga.end), + }, + @"overflow": SKMutableObject(YGOverflowEnumMap[@(node.yoga.overflow)]), + @"display": SKMutableObject(YGDisplayEnumMap[@(node.yoga.display)]), + @"flex": @{ + @"flexDirection": SKMutableObject(YGFlexDirectionEnumMap[@(node.yoga.flexDirection)]), + @"flexWrap": SKMutableObject(YGWrapEnumMap[@(node.yoga.flexWrap)]), + @"flexGrow": SKMutableObject(@(node.yoga.flexGrow)), + @"flexShrink": SKMutableObject(@(node.yoga.flexShrink)), + @"flexBasis": SKYGValueObject(node.yoga.flexBasis), + }, + @"margin": @{ + @"left": SKYGValueObject(node.yoga.marginLeft), + @"top": SKYGValueObject(node.yoga.marginTop), + @"right": SKYGValueObject(node.yoga.marginRight), + @"bottom": SKYGValueObject(node.yoga.marginBottom), + @"start": SKYGValueObject(node.yoga.marginStart), + @"end": SKYGValueObject(node.yoga.marginEnd), + @"horizontal": SKYGValueObject(node.yoga.marginHorizontal), + @"vertical": SKYGValueObject(node.yoga.marginVertical), + @"all": SKYGValueObject(node.yoga.margin), + }, + @"padding": @{ + @"left": SKYGValueObject(node.yoga.paddingLeft), + @"top": SKYGValueObject(node.yoga.paddingTop), + @"right": SKYGValueObject(node.yoga.paddingRight), + @"bottom": SKYGValueObject(node.yoga.paddingBottom), + @"start": SKYGValueObject(node.yoga.paddingStart), + @"end": SKYGValueObject(node.yoga.paddingEnd), + @"horizontal": SKYGValueObject(node.yoga.paddingHorizontal), + @"vertical": SKYGValueObject(node.yoga.paddingVertical), + @"all": SKYGValueObject(node.yoga.padding), + }, + @"border": @{ + @"leftWidth": SKMutableObject(@(node.yoga.borderLeftWidth)), + @"topWidth": SKMutableObject(@(node.yoga.borderTopWidth)), + @"rightWidth": SKMutableObject(@(node.yoga.borderRightWidth)), + @"bottomWidth": SKMutableObject(@(node.yoga.borderBottomWidth)), + @"startWidth": SKMutableObject(@(node.yoga.borderStartWidth)), + @"endWidth": SKMutableObject(@(node.yoga.borderEndWidth)), + @"all": SKMutableObject(@(node.yoga.borderWidth)), + }, + @"dimensions": @{ + @"width": SKYGValueObject(node.yoga.width), + @"height": SKYGValueObject(node.yoga.height), + @"minWidth": SKYGValueObject(node.yoga.minWidth), + @"minHeight": SKYGValueObject(node.yoga.minHeight), + @"maxWidth": SKYGValueObject(node.yoga.maxWidth), + @"maxHeight": SKYGValueObject(node.yoga.maxHeight), + }, + @"aspectRatio": SKMutableObject(@(node.yoga.aspectRatio)), + @"resolvedDirection": SKObject(YGDirectionEnumMap[@(node.yoga.resolvedDirection)]), + }], + nil]; +} + +- (NSDictionary *)dataMutationsForNode:(UIView *)node { + return @{ + // UIView + @"UIView.alpha": ^(NSNumber *value) { + node.alpha = [value floatValue]; + }, + @"UIView.backgroundColor": ^(NSNumber *value) { + node.backgroundColor = [UIColor fromSonarValue: value]; + }, + @"UIView.frame.origin.y": ^(NSNumber *value) { + CGRect frame = node.frame; + frame.origin.y = [value floatValue]; + node.frame = frame; + }, + @"UIView.frame.origin.x": ^(NSNumber *value) { + CGRect frame = node.frame; + frame.origin.x = [value floatValue]; + node.frame = frame; + }, + @"UIView.frame.size.width": ^(NSNumber *value) { + CGRect frame = node.frame; + frame.size.width = [value floatValue]; + node.frame = frame; + }, + @"UIView.frame.size.height": ^(NSNumber *value) { + CGRect frame = node.frame; + frame.size.width = [value floatValue]; + node.frame = frame; + }, + // CALayer + @"CALayer.shadowColor": ^(NSNumber *value) { + node.layer.shadowColor = [UIColor fromSonarValue:value].CGColor; + }, + @"CALayer.shadowOpacity": ^(NSNumber *value) { + node.layer.shadowOpacity = [value floatValue]; + }, + @"CALayer.shadowRadius": ^(NSNumber *value) { + node.layer.shadowRadius = [value floatValue]; + }, + @"CALayer.shadowOffset.width": ^(NSNumber *value) { + CGSize offset = node.layer.shadowOffset; + offset.width = [value floatValue]; + node.layer.shadowOffset = offset; + }, + @"CALayer.shadowOffset.height": ^(NSNumber *value) { + CGSize offset = node.layer.shadowOffset; + offset.height = [value floatValue]; + node.layer.shadowOffset = offset; + }, + @"CALayer.backgroundColor": ^(NSNumber *value) { + node.layer.backgroundColor = [UIColor fromSonarValue:value].CGColor; + }, + @"CALayer.borderColor": ^(NSNumber *value) { + node.layer.borderColor = [UIColor fromSonarValue:value].CGColor; + }, + @"CALayer.borderWidth": ^(NSNumber *value) { + node.layer.borderWidth = [value floatValue]; + }, + @"CALayer.cornerRadius": ^(NSNumber *value) { + node.layer.cornerRadius = [value floatValue]; + }, + @"CALayer.masksToBounds": ^(NSNumber *value) { + node.layer.masksToBounds = [value boolValue]; + }, + // YGLayout + @"YGLayout.direction": APPLY_ENUM_TO_YOGA_PROPERTY(direction, YGDirection), + @"YGLayout.justifyContent": APPLY_ENUM_TO_YOGA_PROPERTY(justifyContent, YGJustify), + @"YGLayout.aligns.alignContent": APPLY_ENUM_TO_YOGA_PROPERTY(alignContent, YGAlign), + @"YGLayout.aligns.alignItems": APPLY_ENUM_TO_YOGA_PROPERTY(alignItems, YGAlign), + @"YGLayout.aligns.alignSelf": APPLY_ENUM_TO_YOGA_PROPERTY(alignSelf, YGAlign), + @"YGLayout.position.type": APPLY_ENUM_TO_YOGA_PROPERTY(position, YGPositionType), + @"YGLayout.position.left.value": APPLY_VALUE_TO_YGVALUE(left), + @"YGLayout.position.left.unit": APPLY_UNIT_TO_YGVALUE(left, YGUnit), + @"YGLayout.position.top.value": APPLY_VALUE_TO_YGVALUE(top), + @"YGLayout.position.top.unit": APPLY_UNIT_TO_YGVALUE(top, YGUnit), + @"YGLayout.position.right.value": APPLY_VALUE_TO_YGVALUE(right), + @"YGLayout.position.right.unit": APPLY_UNIT_TO_YGVALUE(right, YGUnit), + @"YGLayout.position.bottom.value": APPLY_VALUE_TO_YGVALUE(bottom), + @"YGLayout.position.bottom.unit": APPLY_UNIT_TO_YGVALUE(bottom, YGUnit), + @"YGLayout.position.start.value": APPLY_VALUE_TO_YGVALUE(start), + @"YGLayout.position.start.unit": APPLY_UNIT_TO_YGVALUE(start, YGUnit), + @"YGLayout.position.end.value": APPLY_VALUE_TO_YGVALUE(end), + @"YGLayout.position.end.unit": APPLY_UNIT_TO_YGVALUE(end, YGUnit), + @"YGLayout.overflow": APPLY_ENUM_TO_YOGA_PROPERTY(overflow, YGOverflow), + @"YGLayout.display": APPLY_ENUM_TO_YOGA_PROPERTY(display, YGDisplay), + @"YGLayout.flex.flexDirection": APPLY_ENUM_TO_YOGA_PROPERTY(flexDirection, YGFlexDirection), + @"YGLayout.flex.flexWrap": APPLY_ENUM_TO_YOGA_PROPERTY(flexWrap, YGWrap), + @"YGLayout.flex.flexGrow": ^(NSNumber *value) { + node.yoga.flexGrow = [value floatValue]; + }, + @"YGLayout.flex.flexShrink": ^(NSNumber *value) { + node.yoga.flexShrink = [value floatValue]; + }, + @"YGLayout.flex.flexBasis.value": APPLY_VALUE_TO_YGVALUE(flexBasis), + @"YGLayout.flex.flexBasis.unit": APPLY_UNIT_TO_YGVALUE(flexBasis, YGUnit), + @"YGLayout.margin.left.value": APPLY_VALUE_TO_YGVALUE(marginLeft), + @"YGLayout.margin.left.unit": APPLY_UNIT_TO_YGVALUE(marginLeft, YGUnit), + @"YGLayout.margin.top.value": APPLY_VALUE_TO_YGVALUE(marginTop), + @"YGLayout.margin.top.unit": APPLY_UNIT_TO_YGVALUE(marginTop, YGUnit), + @"YGLayout.margin.right.value": APPLY_VALUE_TO_YGVALUE(marginRight), + @"YGLayout.margin.right.unit": APPLY_UNIT_TO_YGVALUE(marginRight, YGUnit), + @"YGLayout.margin.bottom.value": APPLY_VALUE_TO_YGVALUE(marginBottom), + @"YGLayout.margin.bottom.unit": APPLY_UNIT_TO_YGVALUE(marginBottom, YGUnit), + @"YGLayout.margin.start.value": APPLY_VALUE_TO_YGVALUE(marginStart), + @"YGLayout.margin.start.unit": APPLY_UNIT_TO_YGVALUE(marginStart, YGUnit), + @"YGLayout.margin.end.value": APPLY_VALUE_TO_YGVALUE(marginEnd), + @"YGLayout.margin.end.unit": APPLY_UNIT_TO_YGVALUE(marginEnd, YGUnit), + @"YGLayout.margin.horizontal.value": APPLY_VALUE_TO_YGVALUE(marginHorizontal), + @"YGLayout.margin.horizontal.unit": APPLY_UNIT_TO_YGVALUE(marginHorizontal, YGUnit), + @"YGLayout.margin.vertical.value": APPLY_VALUE_TO_YGVALUE(marginVertical), + @"YGLayout.margin.vertical.unit": APPLY_UNIT_TO_YGVALUE(marginVertical, YGUnit), + @"YGLayout.margin.all.value": APPLY_VALUE_TO_YGVALUE(margin), + @"YGLayout.margin.all.unit": APPLY_UNIT_TO_YGVALUE(margin, YGUnit), + @"YGLayout.padding.left.value": APPLY_VALUE_TO_YGVALUE(paddingLeft), + @"YGLayout.padding.left.unit": APPLY_UNIT_TO_YGVALUE(paddingLeft, YGUnit), + @"YGLayout.padding.top.value": APPLY_VALUE_TO_YGVALUE(paddingTop), + @"YGLayout.padding.top.unit": APPLY_UNIT_TO_YGVALUE(paddingTop, YGUnit), + @"YGLayout.padding.right.value": APPLY_VALUE_TO_YGVALUE(paddingRight), + @"YGLayout.padding.right.unit": APPLY_UNIT_TO_YGVALUE(paddingRight, YGUnit), + @"YGLayout.padding.bottom.value": APPLY_VALUE_TO_YGVALUE(paddingBottom), + @"YGLayout.padding.bottom.unit": APPLY_UNIT_TO_YGVALUE(paddingBottom, YGUnit), + @"YGLayout.padding.start.value": APPLY_VALUE_TO_YGVALUE(paddingStart), + @"YGLayout.padding.start.unit": APPLY_UNIT_TO_YGVALUE(paddingStart, YGUnit), + @"YGLayout.padding.end.value": APPLY_VALUE_TO_YGVALUE(paddingEnd), + @"YGLayout.padding.end.unit": APPLY_UNIT_TO_YGVALUE(paddingEnd, YGUnit), + @"YGLayout.padding.horizontal.value": APPLY_VALUE_TO_YGVALUE(paddingHorizontal), + @"YGLayout.padding.horizontal.unit": APPLY_UNIT_TO_YGVALUE(paddingHorizontal, YGUnit), + @"YGLayout.padding.vertical.value": APPLY_VALUE_TO_YGVALUE(paddingVertical), + @"YGLayout.padding.vertical.unit": APPLY_UNIT_TO_YGVALUE(paddingVertical, YGUnit), + @"YGLayout.padding.all.value": APPLY_VALUE_TO_YGVALUE(padding), + @"YGLayout.padding.all.unit": APPLY_UNIT_TO_YGVALUE(padding, YGUnit), + @"YGLayout.border.leftWidth": ^(NSNumber *value) { + node.yoga.borderLeftWidth = [value floatValue]; + }, + @"YGLayout.border.topWidth": ^(NSNumber *value) { + node.yoga.borderTopWidth = [value floatValue]; + }, + @"YGLayout.border.rightWidth": ^(NSNumber *value) { + node.yoga.borderRightWidth = [value floatValue]; + }, + @"YGLayout.border.bottomWidth": ^(NSNumber *value) { + node.yoga.borderBottomWidth = [value floatValue]; + }, + @"YGLayout.border.startWidth": ^(NSNumber *value) { + node.yoga.borderStartWidth = [value floatValue]; + }, + @"YGLayout.border.endWidth": ^(NSNumber *value) { + node.yoga.borderEndWidth = [value floatValue]; + }, + @"YGLayout.border.all": ^(NSNumber *value) { + node.yoga.borderWidth = [value floatValue]; + }, + @"YGLayout.dimensions.width.value": APPLY_VALUE_TO_YGVALUE(width), + @"YGLayout.dimensions.width.unit": APPLY_UNIT_TO_YGVALUE(width, YGUnit), + @"YGLayout.dimensions.height.value": APPLY_VALUE_TO_YGVALUE(height), + @"YGLayout.dimensions.height.unit": APPLY_UNIT_TO_YGVALUE(height, YGUnit), + @"YGLayout.dimensions.minWidth.value": APPLY_VALUE_TO_YGVALUE(minWidth), + @"YGLayout.dimensions.minWidth.unit": APPLY_UNIT_TO_YGVALUE(minWidth, YGUnit), + @"YGLayout.dimensions.minHeight.value": APPLY_VALUE_TO_YGVALUE(minHeight), + @"YGLayout.dimensions.minHeight.unit": APPLY_UNIT_TO_YGVALUE(minHeight, YGUnit), + @"YGLayout.dimensions.maxWidth.value": APPLY_VALUE_TO_YGVALUE(maxWidth), + @"YGLayout.dimensions.maxWidth.unit": APPLY_UNIT_TO_YGVALUE(maxWidth, YGUnit), + @"YGLayout.dimensions.maxHeight.value": APPLY_VALUE_TO_YGVALUE(maxHeight), + @"YGLayout.dimensions.maxHeight.unit": APPLY_UNIT_TO_YGVALUE(maxHeight, YGUnit), + @"YGLayout.aspectRatio": ^(NSNumber *value) { + node.yoga.aspectRatio = [value floatValue]; + }, + // Accessibility + @"Accessibility.isAccessibilityElement": ^(NSNumber *value) { + node.isAccessibilityElement = [value boolValue]; + }, + @"Accessibility.accessibilityLabel": ^(NSString *value) { + node.accessibilityLabel = value; + }, + @"Accessibility.accessibilityValue": ^(NSString *value) { + node.accessibilityValue = value; + }, + @"Accessibility.accessibilityHint": ^(NSString *value) { + node.accessibilityHint = value; + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitButton": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitButton, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitLink": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitLink, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitHeader": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitHeader, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitSearchField": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitSearchField, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitImage": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitImage, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitSelected": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitSelected, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitPlaysSound": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitPlaysSound, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitKeyboardKey": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitKeyboardKey, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitStaticText": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitStaticText, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitSummaryElement": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitSummaryElement, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitNotEnabled": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitNotEnabled, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitUpdatesFrequently": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitUpdatesFrequently, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitStartsMediaSession": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitStartsMediaSession, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitAdjustable": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitAdjustable, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitAllowsDirectInteraction": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitAllowsDirectInteraction, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitCausesPageTurn": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitCausesPageTurn, [value boolValue]); + }, + @"Accessibility.accessibilityTraits.UIAccessibilityTraitTabBar": ^(NSNumber *value) { + node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitTabBar, [value boolValue]); + }, + @"Accessibility.accessibilityViewIsModal": ^(NSNumber *value) { + node.accessibilityViewIsModal = [value boolValue]; + }, + @"Accessibility.shouldGroupAccessibilityChildren": ^(NSNumber *value) { + node.shouldGroupAccessibilityChildren = [value boolValue]; + }, + }; +} + +- (NSArray *> *)attributesForNode:(UIView *)node { + return @[ + [SKNamed newWithName: @"addr" + withValue: [NSString stringWithFormat: @"%p", node]] + ]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(UIView *)node { + SKHighlightOverlay *overlay = [SKHighlightOverlay sharedInstance]; + if (highlighted == YES) { + [overlay mountInView: node withFrame: node.bounds]; + } else { + [overlay unmount]; + } +} + +- (void)hitTest:(SKTouch *)touch forNode:(UIView *)node { + for (NSInteger index = [self childCountForNode: node] - 1; index >= 0; index--) { + id childNode = [self childForNode: node atIndex: index]; + UIView *viewForNode = nil; + + if ([childNode isKindOfClass: [UIViewController class]]) { + UIViewController *child = (UIViewController *)childNode; + viewForNode = child.view; + } else { + viewForNode = (UIView *)childNode; + } + + if (viewForNode.isHidden || viewForNode.alpha <= 0) { + continue; + } + + if ([touch containedIn: viewForNode.frame]) { + [touch continueWithChildIndex: index withOffset: viewForNode.frame.origin ]; + return; + } + } + + [touch finish]; +} + +static void initEnumDictionaries() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + YGDirectionEnumMap = @{ + @(YGDirectionInherit): @"inherit", + @(YGDirectionLTR): @"LTR", + @(YGDirectionRTL): @"RTL", + }; + + YGFlexDirectionEnumMap = @{ + @(YGFlexDirectionColumn): @"column", + @(YGFlexDirectionColumnReverse): @"column-reverse", + @(YGFlexDirectionRow): @"row", + @(YGFlexDirectionRowReverse): @"row-reverse", + }; + + YGJustifyEnumMap = @{ + @(YGJustifyFlexStart): @"flex-start", + @(YGJustifyCenter): @"center", + @(YGJustifyFlexEnd): @"flex-end", + @(YGJustifySpaceBetween): @"space-between", + @(YGJustifySpaceAround): @"space-around", + }; + + YGAlignEnumMap = @{ + @(YGAlignAuto): @"auto", + @(YGAlignFlexStart): @"flex-start", + @(YGAlignCenter): @"end", + @(YGAlignFlexEnd): @"flex-end", + @(YGAlignStretch): @"stretch", + @(YGAlignBaseline): @"baseline", + @(YGAlignSpaceBetween): @"space-between", + @(YGAlignSpaceAround): @"space-around", + }; + + YGPositionTypeEnumMap = @{ + @(YGPositionTypeRelative): @"relative", + @(YGPositionTypeAbsolute): @"absolute", + }; + + YGWrapEnumMap = @{ + @(YGWrapNoWrap): @"no-wrap", + @(YGWrapWrap): @"wrap", + @(YGWrapWrapReverse): @"wrap-reverse", + }; + + YGOverflowEnumMap = @{ + @(YGOverflowVisible): @"visible", + @(YGOverflowHidden): @"hidden", + @(YGOverflowScroll): @"scroll", + }; + + YGDisplayEnumMap = @{ + @(YGDisplayFlex): @"flex", + @(YGDisplayNone): @"none", + }; + + YGUnitEnumMap = @{ + @(YGUnitUndefined): @"undefined", + @(YGUnitPoint): @"point", + @(YGUnitPercent): @"percent", + @(YGUnitAuto): @"auto", + }; + }); +} + +static NSDictionary *SKYGValueObject(YGValue value) { + return @{ + @"value": SKMutableObject(@(value.value)), + @"unit": SKMutableObject(YGUnitEnumMap[@(value.unit)]), + }; +} + +/* + Takes the originalTraits, and set all bits from toggleTraits to the toggleValue + e.g. originalTraits = UIAccessibilityTraitButton | UIAccessibilityTraitSelected + toggleTraits = UIAccessibilityTraitImage + toggleValue = YES + return value = UIAccessibilityTraitButton | UIAccessibilityTraitSelected | UIAccessibilityTraitImage + */ +static UIAccessibilityTraits AccessibilityTraitsToggle(UIAccessibilityTraits originalTraits, UIAccessibilityTraits toggleTraits, BOOL toggleValue) { + // NEGATE all bits of toggleTraits from originalTraits and OR it against either toggleTraits or 0 (UIAccessibilityTraitNone) based on toggleValue + UIAccessibilityTraits bitsValue = toggleValue ? toggleTraits : UIAccessibilityTraitNone; + return (originalTraits & ~(toggleTraits)) | bitsValue; +} + +static NSDictionary *AccessibilityTraitsDict(UIAccessibilityTraits accessibilityTraits) { + NSMutableDictionary *traitsDict = [NSMutableDictionary new]; + [traitsDict addEntriesFromDictionary:@{ + @"UIAccessibilityTraitButton": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitButton))), + @"UIAccessibilityTraitLink": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitLink))), + @"UIAccessibilityTraitHeader": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitHeader))), + @"UIAccessibilityTraitSearchField": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitSearchField))), + @"UIAccessibilityTraitImage": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitImage))), + @"UIAccessibilityTraitSelected": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitSelected))), + @"UIAccessibilityTraitPlaysSound": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitPlaysSound))), + @"UIAccessibilityTraitKeyboardKey": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitKeyboardKey))), + @"UIAccessibilityTraitStaticText": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitStaticText))), + @"UIAccessibilityTraitSummaryElement": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitSummaryElement))), + @"UIAccessibilityTraitNotEnabled": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitNotEnabled))), + @"UIAccessibilityTraitUpdatesFrequently": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitUpdatesFrequently))), + @"UIAccessibilityTraitStartsMediaSession": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitStartsMediaSession))), + @"UIAccessibilityTraitAdjustable": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitAdjustable))), + @"UIAccessibilityTraitAllowsDirectInteraction": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitAllowsDirectInteraction))), + @"UIAccessibilityTraitCausesPageTurn": SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitCausesPageTurn))), + }]; + if (@available(iOS 10.0, *)) { + traitsDict[@"UIAccessibilityTraitTabBar"] = SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitTabBar))); + } + return traitsDict; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKHiddenWindow.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKHiddenWindow.h new file mode 100644 index 000000000..44eb12649 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKHiddenWindow.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@interface SKHiddenWindow : UIWindow +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKHiddenWindow.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKHiddenWindow.m new file mode 100644 index 000000000..bbd91c033 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKHiddenWindow.m @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKHiddenWindow.h" + +@implementation SKHiddenWindow + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKObjectHash.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKObjectHash.h new file mode 100644 index 000000000..ba3852d64 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKObjectHash.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +/* + A hash function is needed in order to use NSObject classes + as keys in C++ STL + */ +class SKObjectHash { +public: + size_t operator()(const NSObject *x) const { + return (size_t)[x hash]; + } +}; diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKSwizzle.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKSwizzle.h new file mode 100644 index 000000000..baf88df9a --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKSwizzle.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +void swizzleMethods(Class cls, SEL original, SEL swissled); diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKSwizzle.mm b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKSwizzle.mm new file mode 100644 index 000000000..0b5d29291 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKSwizzle.mm @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKSwizzle.h" + +#import +#import + +void swizzleMethods(Class cls, SEL original, SEL swissled) { + Method originalMethod = class_getInstanceMethod(cls, original); + Method swissledMethod = class_getInstanceMethod(cls, swissled); + + BOOL didAddMethod = class_addMethod(cls, original, + method_getImplementation(swissledMethod), + method_getTypeEncoding(swissledMethod)); + + if (didAddMethod) { + class_replaceMethod(cls, swissled, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + method_exchangeImplementations(originalMethod, swissledMethod); + } +} + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKYogaKitHelper.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKYogaKitHelper.h new file mode 100644 index 000000000..a0ded21ac --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKYogaKitHelper.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#define APPLY_ENUM_TO_YOGA_PROPERTY(varName, enumName) \ +^(NSString *newValue) { \ +NSNumber *varName = [[enumName##EnumMap allKeysForObject:newValue] lastObject]; \ +if (varName == nil) { return; } \ +node.yoga.varName = (enumName)[varName unsignedIntegerValue]; \ +} + +#define APPLY_VALUE_TO_YGVALUE(varName) \ +^(NSNumber *value) { \ +YGValue newValue = node.yoga.varName; \ +newValue.value = [value floatValue]; \ +node.yoga.varName = newValue; \ +} + +#define APPLY_UNIT_TO_YGVALUE(varName, enumName) \ +^(NSString *value) { \ +NSNumber *varName = [[enumName##EnumMap allKeysForObject:value] lastObject]; \ +if (varName == nil) { return; } \ +YGValue newValue = node.yoga.varName; \ +newValue.unit = (enumName)[varName unsignedIntegerValue]; \ +node.yoga.varName = newValue; \ +} diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/SKTapListenerMock.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/SKTapListenerMock.h new file mode 100644 index 000000000..cabd0e95c --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/SKTapListenerMock.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@interface SKTapListenerMock : NSObject + +- (void)tapAt:(CGPoint)point; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/SKTapListenerMock.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/SKTapListenerMock.m new file mode 100644 index 000000000..e3bd05f01 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/SKTapListenerMock.m @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "SKTapListenerMock.h" + +@implementation SKTapListenerMock +{ + NSMutableArray *_tapReceivers; +} + +@synthesize isMounted; + +- (instancetype)init { + if (self = [super init]) { + _tapReceivers = [NSMutableArray new]; + } + + return self; +} + +- (void)listenForTapWithBlock:(SKTapReceiver)receiver { + [_tapReceivers addObject: receiver]; +} + +- (void)tapAt:(CGPoint)point { + for (SKTapReceiver recv in _tapReceivers) { + recv(point); + } + + [_tapReceivers removeAllObjects]; +} + +- (void)mountWithFrame:(CGRect)frame { + isMounted = YES; +} + +- (void)unmount { + isMounted = NO; +} + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/SonarKitLayoutPluginTests.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/SonarKitLayoutPluginTests.m new file mode 100644 index 000000000..b7e4d4dd7 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/SonarKitLayoutPluginTests.m @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#if FB_SONARKIT_ENABLED + +#import +#import +#import +#import +#import + +#import "SKTapListenerMock.h" +#import "TestNode.h" +#import "TestNodeDescriptor.h" + +@interface SonarKitLayoutPluginTests : XCTestCase +@end + +@implementation SonarKitLayoutPluginTests +{ + SKDescriptorMapper *_descriptorMapper; +} + +- (void)setUp { + [super setUp]; + + _descriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; + + [_descriptorMapper registerDescriptor: [TestNodeDescriptor new] + forClass: [TestNode class]]; +} + +- (void)testGetRoot { + SonarKitLayoutPlugin *plugin = [[SonarKitLayoutPlugin alloc] + initWithRootNode: [[TestNode alloc] initWithName: @"rootNode"] + withTapListener: nil + withDescriptorMapper: _descriptorMapper]; + + SonarConnectionMock *connection = [SonarConnectionMock new]; + SonarResponderMock *responder = [SonarResponderMock new]; + [plugin didConnect:connection]; + + SonarReceiver receiver = connection.receivers[@"getRoot"]; + receiver(@{}, responder); + + XCTAssertTrue(([responder.successes containsObject: @{ + @"id": @"rootNode", + @"name": @"TestNode", + @"children": @[], + @"attributes": @[], + @"data": @{}, + @"decoration": @"", + }])); +} + +- (void)testGetEmptyNodes { + SonarKitLayoutPlugin *plugin = [SonarKitLayoutPlugin new]; + SonarConnectionMock *connection = [SonarConnectionMock new]; + SonarResponderMock *responder = [SonarResponderMock new]; + [plugin didConnect:connection]; + + SonarReceiver receiver = connection.receivers[@"getNodes"]; + receiver(@{@"ids": @[]}, responder); + + XCTAssertTrue(([responder.successes containsObject:@{@"elements": @[]}])); +} + +- (void)testGetNodes { + TestNode *rootNode = [[TestNode alloc] initWithName: @"rootNode"]; + NSArray *childNodes = @[ + [[TestNode alloc] initWithName: @"testNode1"], + [[TestNode alloc] initWithName: @"testNode2"], + [[TestNode alloc] initWithName: @"testNode3"], + ]; + + rootNode.children = childNodes; + + SonarKitLayoutPlugin *plugin = [[SonarKitLayoutPlugin alloc] initWithRootNode: rootNode + withTapListener: nil + withDescriptorMapper: _descriptorMapper]; + + SonarConnectionMock *connection = [SonarConnectionMock new]; + SonarResponderMock *responder = [SonarResponderMock new]; + [plugin didConnect:connection]; + + // Ensure that nodes are tracked + connection.receivers[@"getRoot"](@{}, responder); + + SonarReceiver receiver = connection.receivers[@"getNodes"]; + receiver(@{@"ids": @[ @"testNode1", @"testNode2", @"testNode3" ]}, responder); + + XCTAssertTrue(([responder.successes containsObject:@{@"elements": @[ + @{ + @"id": @"testNode1", + @"name": @"TestNode", + @"children": @[], + @"attributes": @[], + @"data": @{}, + @"decoration": @"", + }, + @{ + @"id": @"testNode2", + @"name": @"TestNode", + @"children": @[], + @"attributes": @[], + @"data": @{}, + @"decoration": @"", + }, + @{ + @"id": @"testNode3", + @"name": @"TestNode", + @"children": @[], + @"attributes": @[], + @"data": @{}, + @"decoration": @"", + }, + ]}])); +} + +- (void)testSetHighlighted { + TestNode *rootNode = [[TestNode alloc] initWithName: @"rootNode"]; + + TestNode *testNode1 = [[TestNode alloc] initWithName: @"testNode1"]; + TestNode *testNode2 = [[TestNode alloc] initWithName: @"testNode2"]; + NSArray *childNodes = @[ testNode1, testNode2 ]; + + rootNode.children = childNodes; + + SonarKitLayoutPlugin *plugin = [[SonarKitLayoutPlugin alloc] initWithRootNode: rootNode + withTapListener: nil + withDescriptorMapper: _descriptorMapper]; + + SonarConnectionMock *connection = [SonarConnectionMock new]; + SonarResponderMock *responder = [SonarResponderMock new]; + [plugin didConnect:connection]; + + // Setup in order to track nodes successfully + connection.receivers[@"getRoot"](@{}, responder); + SonarReceiver getNodesCall = connection.receivers[@"getNodes"]; + getNodesCall(@{@"ids": @[ @"testNode1", @"testNode2" ]}, responder); + + SonarReceiver setHighlighted = connection.receivers[@"setHighlighted"]; + setHighlighted(@{@"id":@"testNode2"}, responder); + + XCTAssertTrue(testNode2.highlighted); + + setHighlighted(@{@"id":@"testNode1"}, responder); + + // Ensure that old highlight was removed + XCTAssertTrue(testNode1.highlighted && !testNode2.highlighted); +} + +- (void)testSetSearchActive { + TestNode *rootNode = [[TestNode alloc] initWithName: @"rootNode" + withFrame: CGRectMake(0, 0, 20, 60)]; + + TestNode *testNode1 = [[TestNode alloc] initWithName: @"testNode1" + withFrame: CGRectMake(20, 20, 20, 20)]; + TestNode *testNode2 = [[TestNode alloc] initWithName: @"testNode2" + withFrame: CGRectMake(20, 40, 20, 20)]; + TestNode *testNode3 = [[TestNode alloc] initWithName: @"testNode3" + withFrame: CGRectMake(25, 42, 5, 5)]; + + rootNode.children = @[ testNode1, testNode2 ]; + testNode2.children = @[ testNode3 ]; + + SKTapListenerMock *tapListener = [SKTapListenerMock new]; + SonarKitLayoutPlugin *plugin = [[SonarKitLayoutPlugin alloc] initWithRootNode: rootNode + withTapListener: tapListener + withDescriptorMapper: _descriptorMapper]; + + SonarConnectionMock *connection = [SonarConnectionMock new]; + SonarResponderMock *responder = [SonarResponderMock new]; + [plugin didConnect:connection]; + + connection.receivers[@"setSearchActive"](@{@"active":@YES}, responder); + + // Fake a tap at `testNode3` + [tapListener tapAt: (CGPoint){ 26, 43 }]; + + XCTAssertTrue(([connection.sent[@"select"] containsObject: @{ @"path": @[ @"testNode2", @"testNode3" ] }])); +} + +- (void)testSetSearchActiveMountAndUnmount { + TestNode *rootNode = [[TestNode alloc] initWithName: @"rootNode"]; + + SKTapListenerMock *tapListener = [SKTapListenerMock new]; + SonarKitLayoutPlugin *plugin = [[SonarKitLayoutPlugin alloc] initWithRootNode: rootNode + withTapListener: tapListener + withDescriptorMapper: _descriptorMapper]; + + SonarConnectionMock *connection = [SonarConnectionMock new]; + SonarResponderMock *responder = [SonarResponderMock new]; + [plugin didConnect:connection]; + + SonarReceiver setSearchActive = connection.receivers[@"setSearchActive"]; + setSearchActive(@{@"active":@YES}, responder); + + XCTAssertTrue(tapListener.isMounted); + + setSearchActive(@{@"active":@NO}, responder); + XCTAssertTrue(! tapListener.isMounted); +} + +- (void)testSetData { + TestNode *rootNode = [[TestNode alloc] initWithName: @"rootNode" + withFrame: CGRectMake(0, 0, 20, 60)]; + + TestNode *testNode1 = [[TestNode alloc] initWithName: @"testNode1" + withFrame: CGRectMake(20, 20, 20, 20)]; + TestNode *testNode2 = [[TestNode alloc] initWithName: @"testNode2" + withFrame: CGRectMake(20, 40, 20, 20)]; + TestNode *testNode3 = [[TestNode alloc] initWithName: @"testNode3" + withFrame: CGRectMake(25, 42, 5, 5)]; + + rootNode.children = @[ testNode1, testNode2 ]; + testNode2.children = @[ testNode3 ]; + + SKTapListenerMock *tapListener = [SKTapListenerMock new]; + SonarKitLayoutPlugin *plugin = [[SonarKitLayoutPlugin alloc] initWithRootNode: rootNode + withTapListener: tapListener + withDescriptorMapper: _descriptorMapper]; + + SonarConnectionMock *connection = [SonarConnectionMock new]; + SonarResponderMock *responder = [SonarResponderMock new]; + [plugin didConnect:connection]; + + // Setup in order to track nodes successfully + connection.receivers[@"getRoot"](@{}, responder); + connection.receivers[@"getNodes"](@{ @"ids": @[ @"testNode2" ] }, responder); + + // Modify the name of testNode3 + connection.receivers[@"setData"](@{ + @"id": @"testNode3", + @"path": @[ @"TestNode", @"name" ], + @"value": @"changedNameForTestNode3", + }, responder); + + XCTAssertTrue([testNode3.nodeName isEqualToString: @"changedNameForTestNode3"]); +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNode.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNode.h new file mode 100644 index 000000000..51a286a1b --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNode.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@interface TestNode : NSObject + +@property (nonatomic, copy) NSString *nodeName; +@property (nonatomic, copy) NSArray *children; + +@property (nonatomic, assign) BOOL highlighted; +@property (nonatomic, assign) CGRect frame; + +- (instancetype)initWithName:(NSString *)name; +- (instancetype)initWithName:(NSString *)name withFrame:(CGRect)frame; + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNode.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNode.m new file mode 100644 index 000000000..810761939 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNode.m @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "TestNode.h" + +@implementation TestNode + +- (instancetype)initWithName:(NSString *)name { + return [self initWithName: name + withFrame: CGRectZero]; +} + +- (instancetype)initWithName:(NSString *)name withFrame:(CGRect)frame { + if (self = [super init]) { + _nodeName = name; + _frame = frame; + } + + return self; +} + +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNodeDescriptor.h b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNodeDescriptor.h new file mode 100644 index 000000000..8e0f58763 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNodeDescriptor.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import "TestNode.h" + +@interface TestNodeDescriptor : SKNodeDescriptor +@end diff --git a/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNodeDescriptor.m b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNodeDescriptor.m new file mode 100644 index 000000000..aeb3d9bc1 --- /dev/null +++ b/iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPluginTests/TestNodeDescriptor.m @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "TestNodeDescriptor.h" + +@implementation TestNodeDescriptor + +- (NSString *)identifierForNode:(TestNode *)node { + return node.nodeName; +} + +- (NSUInteger)childCountForNode:(TestNode *)node { + return [node.children count]; +} + +- (id)childForNode:(TestNode *)node atIndex:(NSUInteger)index { + return [node.children objectAtIndex: index]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(TestNode *)node { + node.highlighted = highlighted; +} + +- (void)hitTest:(SKTouch *)touch forNode:(TestNode *)node { + NSUInteger index = [node.children count] - 1; + for (TestNode *childNode in [node.children reverseObjectEnumerator]) { + if ([touch containedIn: childNode.frame]) { + [touch continueWithChildIndex: index withOffset: node.frame.origin]; + return; + } + + index = index - 1; + } + + [touch finish]; +} + +- (NSDictionary *)dataMutationsForNode:(TestNode *)node { + return @{ + @"TestNode.name": ^(NSString *newName) { + node.nodeName = newName; + } + }; +} + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkObserver.h b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkObserver.h new file mode 100755 index 000000000..2262805ee --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkObserver.h @@ -0,0 +1,27 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +// +// FLEXNetworkObserver.h +// Derived from: +// +// PDAFNetworkDomainController.h +// PonyDebugger +// +// Created by Mike Lewis on 2/27/12. +// +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. +// + +#import + +FOUNDATION_EXTERN NSString *const kFLEXNetworkObserverEnabledStateChangedNotification; + +/// This class swizzles NSURLConnection and NSURLSession delegate methods to observe events in the URL loading system. +/// High level network events are sent to the default FLEXNetworkRecorder instance which maintains the request history and caches response bodies. +@interface FLEXNetworkObserver : NSObject + ++ (void)start; + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkObserver.mm b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkObserver.mm new file mode 100755 index 000000000..723345b4b --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkObserver.mm @@ -0,0 +1,1088 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +// +// FLEXNetworkObserver.m +// Derived from: +// +// PDAFNetworkDomainController.m +// PonyDebugger +// +// Created by Mike Lewis on 2/27/12. +// +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. +// + +#import "FLEXNetworkObserver.h" + +#import +#import + +#import + +#import "FLEXNetworkRecorder.h" +#import "FLEXUtility.h" + +NSString *const kFLEXNetworkObserverEnabledStateChangedNotification = @"kFLEXNetworkObserverEnabledStateChangedNotification"; +static NSString *const kFLEXNetworkObserverEnabledDefaultsKey = @"com.flex.FLEXNetworkObserver.enableOnLaunch"; + +typedef void (^NSURLSessionAsyncCompletion)(id fileURLOrData, NSURLResponse *response, NSError *error); + +@interface FLEXInternalRequestState : NSObject + +@property (nonatomic, copy) NSURLRequest *request; +@property (nonatomic, strong) NSMutableData *dataAccumulator; + +@end + +@implementation FLEXInternalRequestState + +@end + +@interface FLEXNetworkObserver (NSURLConnectionHelpers) + +- (void)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response delegate:(id)delegate; +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response delegate:(id)delegate; + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data delegate:(id)delegate; + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection delegate:(id)delegate; +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error delegate:(id)delegate; + +- (void)connectionWillCancel:(NSURLConnection *)connection; + +@end + + +@interface FLEXNetworkObserver (NSURLSessionTaskHelpers) + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler delegate:(id)delegate; +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler delegate:(id)delegate; +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data delegate:(id)delegate; +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask +didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id)delegate; +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error delegate:(id)delegate; +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite delegate:(id)delegate; +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location data:(NSData *)data delegate:(id)delegate; + +- (void)URLSessionTaskWillResume:(NSURLSessionTask *)task; + +@end + +@interface FLEXNetworkObserver () + +@property (nonatomic, strong) NSMutableDictionary *requestStatesForRequestIDs; +@property (nonatomic, strong) dispatch_queue_t queue; + +@end + +@implementation FLEXNetworkObserver + +#pragma mark - Public Methods + ++ (void)start { + [self injectIntoAllNSURLConnectionDelegateClasses]; + +} + +#pragma mark - Statics + ++ (instancetype)sharedObserver +{ + static FLEXNetworkObserver *sharedObserver = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedObserver = [[[self class] alloc] init]; + }); + return sharedObserver; +} + ++ (NSString *)nextRequestID +{ + return [[NSUUID UUID] UUIDString]; +} + +#pragma mark Delegate Injection Convenience Methods + +/// All swizzled delegate methods should make use of this guard. +/// This will prevent duplicated sniffing when the original implementation calls up to a superclass implementation which we've also swizzled. +/// The superclass implementation (and implementations in classes above that) will be executed without inteference if called from the original implementation. ++ (void)sniffWithoutDuplicationForObject:(NSObject *)object selector:(SEL)selector sniffingBlock:(void (^)(void))sniffingBlock originalImplementationBlock:(void (^)(void))originalImplementationBlock +{ + // If we don't have an object to detect nested calls on, just run the original implmentation and bail. + // This case can happen if someone besides the URL loading system calls the delegate methods directly. + // See https://github.com/Flipboard/FLEX/issues/61 for an example. + if (!object) { + originalImplementationBlock(); + return; + } + + const void *key = selector; + + // Don't run the sniffing block if we're inside a nested call + if (!objc_getAssociatedObject(object, key)) { + sniffingBlock(); + } + + // Mark that we're calling through to the original so we can detect nested calls + objc_setAssociatedObject(object, key, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + originalImplementationBlock(); + objc_setAssociatedObject(object, key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +#pragma mark - Delegate Injection + ++ (void)injectIntoAllNSURLConnectionDelegateClasses +{ + // Only allow swizzling once. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Swizzle any classes that implement one of these selectors. + const SEL selectors[] = { + @selector(connectionDidFinishLoading:), + @selector(connection:willSendRequest:redirectResponse:), + @selector(connection:didReceiveResponse:), + @selector(connection:didReceiveData:), + @selector(connection:didFailWithError:), + @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:), + @selector(URLSession:dataTask:didReceiveData:), + @selector(URLSession:dataTask:didReceiveResponse:completionHandler:), + @selector(URLSession:task:didCompleteWithError:), + @selector(URLSession:dataTask:didBecomeDownloadTask:), + @selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:), + @selector(URLSession:downloadTask:didFinishDownloadingToURL:) + }; + + const int numSelectors = sizeof(selectors) / sizeof(SEL); + + Class *classes = NULL; + int numClasses = objc_getClassList(NULL, 0); + + if (numClasses > 0) { + classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses); + numClasses = objc_getClassList(classes, numClasses); + for (NSInteger classIndex = 0; classIndex < numClasses; ++classIndex) { + Class className = classes[classIndex]; + + if (className == [FLEXNetworkObserver class]) { + continue; + } + + // Use the runtime API rather than the methods on NSObject to avoid sending messages to + // classes we're not interested in swizzling. Otherwise we hit +initialize on all classes. + // NOTE: calling class_getInstanceMethod() DOES send +initialize to the class. That's why we iterate through the method list. + unsigned int methodCount = 0; + Method *methods = class_copyMethodList(className, &methodCount); + BOOL matchingSelectorFound = NO; + for (unsigned int methodIndex = 0; methodIndex < methodCount; methodIndex++) { + for (int selectorIndex = 0; selectorIndex < numSelectors; ++selectorIndex) { + if (method_getName(methods[methodIndex]) == selectors[selectorIndex]) { + [self injectIntoDelegateClass:className]; + matchingSelectorFound = YES; + break; + } + } + if (matchingSelectorFound) { + break; + } + } + free(methods); + } + + free(classes); + } + + [self injectIntoNSURLConnectionCancel]; + [self injectIntoNSURLSessionTaskResume]; + + [self injectIntoNSURLConnectionAsynchronousClassMethod]; + [self injectIntoNSURLConnectionSynchronousClassMethod]; + + [self injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods]; + [self injectIntoNSURLSessionAsyncUploadTaskMethods]; + }); +} + ++ (void)injectIntoDelegateClass:(Class)cls +{ + // Connections + [self injectWillSendRequestIntoDelegateClass:cls]; + [self injectDidReceiveDataIntoDelegateClass:cls]; + [self injectDidReceiveResponseIntoDelegateClass:cls]; + [self injectDidFinishLoadingIntoDelegateClass:cls]; + [self injectDidFailWithErrorIntoDelegateClass:cls]; + + // Sessions + [self injectTaskWillPerformHTTPRedirectionIntoDelegateClass:cls]; + [self injectTaskDidReceiveDataIntoDelegateClass:cls]; + [self injectTaskDidReceiveResponseIntoDelegateClass:cls]; + [self injectTaskDidCompleteWithErrorIntoDelegateClass:cls]; + [self injectRespondsToSelectorIntoDelegateClass:cls]; + + // Data tasks + [self injectDataTaskDidBecomeDownloadTaskIntoDelegateClass:cls]; + + // Download tasks + [self injectDownloadTaskDidWriteDataIntoDelegateClass:cls]; + [self injectDownloadTaskDidFinishDownloadingIntoDelegateClass:cls]; +} + ++ (void)injectIntoNSURLConnectionCancel +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class className = [NSURLConnection class]; + SEL selector = @selector(cancel); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + Method originalCancel = class_getInstanceMethod(className, selector); + + void (^swizzleBlock)(NSURLConnection *) = ^(NSURLConnection *slf) { + [[FLEXNetworkObserver sharedObserver] connectionWillCancel:slf]; + ((void(*)(id, SEL))objc_msgSend)(slf, swizzledSelector); + }; + + IMP implementation = imp_implementationWithBlock(swizzleBlock); + class_addMethod(className, swizzledSelector, implementation, method_getTypeEncoding(originalCancel)); + Method newCancel = class_getInstanceMethod(className, swizzledSelector); + method_exchangeImplementations(originalCancel, newCancel); + }); +} + ++ (void)injectIntoNSURLSessionTaskResume +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // In iOS 7 resume lives in __NSCFLocalSessionTask + // In iOS 8 resume lives in NSURLSessionTask + // In iOS 9 resume lives in __NSCFURLSessionTask + Class className = Nil; + if (![[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) { + className = NSClassFromString([@[@"__", @"NSC", @"FLocalS", @"ession", @"Task"] componentsJoinedByString:@""]); + } else if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion < 9) { + className = [NSURLSessionTask class]; + } else { + className = NSClassFromString([@[@"__", @"NSC", @"FURLS", @"ession", @"Task"] componentsJoinedByString:@""]); + } + SEL selector = @selector(resume); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Method originalResume = class_getInstanceMethod(className, selector); + + void (^swizzleBlock)(NSURLSessionTask *) = ^(NSURLSessionTask *slf) { + [[FLEXNetworkObserver sharedObserver] URLSessionTaskWillResume:slf]; + ((void(*)(id, SEL))objc_msgSend)(slf, swizzledSelector); + }; + + IMP implementation = imp_implementationWithBlock(swizzleBlock); + class_addMethod(className, swizzledSelector, implementation, method_getTypeEncoding(originalResume)); + Method newResume = class_getInstanceMethod(className, swizzledSelector); + method_exchangeImplementations(originalResume, newResume); + }); +} + ++ (void)injectIntoNSURLConnectionAsynchronousClassMethod +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class className = objc_getMetaClass(class_getName([NSURLConnection class])); + SEL selector = @selector(sendAsynchronousRequest:queue:completionHandler:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + typedef void (^NSURLConnectionAsyncCompletion)(NSURLResponse *response, NSData *data, NSError *connectionError); + + void (^asyncSwizzleBlock)(Class, NSURLRequest *, NSOperationQueue *, NSURLConnectionAsyncCompletion) = ^(Class slf, NSURLRequest *request, NSOperationQueue *queue, NSURLConnectionAsyncCompletion completion) { + NSString *requestID = [self nextRequestID]; + [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil]; + NSString *mechanism = [self mechanismFromClassMethod:selector onClass:className]; + [[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID]; + NSURLConnectionAsyncCompletion completionWrapper = ^(NSURLResponse *response, NSData *data, NSError *connectionError) { + [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response]; + [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:[data length]]; + if (connectionError) { + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:connectionError]; + } else { + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:data]; + } + + // Call through to the original completion handler + if (completion) { + completion(response, data, connectionError); + } + }; + ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, queue, completionWrapper); + }; + + [FLEXUtility replaceImplementationOfKnownSelector:selector onClass:className withBlock:asyncSwizzleBlock swizzledSelector:swizzledSelector]; + }); +} + ++ (void)injectIntoNSURLConnectionSynchronousClassMethod +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class className = objc_getMetaClass(class_getName([NSURLConnection class])); + SEL selector = @selector(sendSynchronousRequest:returningResponse:error:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + NSData *(^syncSwizzleBlock)(Class, NSURLRequest *, NSURLResponse **, NSError **) = ^NSData *(Class slf, NSURLRequest *request, NSURLResponse **response, NSError **error) { + NSData *data = nil; + NSString *requestID = [self nextRequestID]; + [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:nil]; + NSString *mechanism = [self mechanismFromClassMethod:selector onClass:className]; + [[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID]; + NSError *temporaryError = nil; + NSURLResponse *temporaryResponse = nil; + data = ((id(*)(id, SEL, id, NSURLResponse **, NSError **))objc_msgSend)(slf, swizzledSelector, request, &temporaryResponse, &temporaryError); + [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:temporaryResponse]; + [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:[data length]]; + if (temporaryError) { + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:temporaryError]; + } else { + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:data]; + } + if (error) { + *error = temporaryError; + } + if (response) { + *response = temporaryResponse; + } + return data; + }; + + [FLEXUtility replaceImplementationOfKnownSelector:selector onClass:className withBlock:syncSwizzleBlock swizzledSelector:swizzledSelector]; + }); +} + ++ (void)injectIntoNSURLSessionAsyncDataAndDownloadTaskMethods +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class className = [NSURLSession class]; + + // The method signatures here are close enough that we can use the same logic to inject into all of them. + const SEL selectors[] = { + @selector(dataTaskWithRequest:completionHandler:), + @selector(dataTaskWithURL:completionHandler:), + @selector(downloadTaskWithRequest:completionHandler:), + @selector(downloadTaskWithResumeData:completionHandler:), + @selector(downloadTaskWithURL:completionHandler:) + }; + + const int numSelectors = sizeof(selectors) / sizeof(SEL); + + for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) { + SEL selector = selectors[selectorIndex]; + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + if ([FLEXUtility instanceRespondsButDoesNotImplementSelector:selector class:className]) { + // iOS 7 does not implement these methods on NSURLSession. We actually want to + // swizzle __NSCFURLSession, which we can get from the class of the shared session + className = [[NSURLSession sharedSession] class]; + } + + NSURLSessionTask *(^asyncDataOrDownloadSwizzleBlock)(Class, id, NSURLSessionAsyncCompletion) = ^NSURLSessionTask *(Class slf, id argument, NSURLSessionAsyncCompletion completion) { + NSURLSessionTask *task = nil; + // If completion block was not provided sender expect to receive delegated methods or does not + // interested in callback at all. In this case we should just call original method implementation + // with nil completion block. + if (completion) { + NSString *requestID = [self nextRequestID]; + NSString *mechanism = [self mechanismFromClassMethod:selector onClass:className]; + NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion]; + task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completionWrapper); + [self setRequestID:requestID forConnectionOrTask:task]; + } else { + task = ((id(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, argument, completion); + } + return task; + }; + + [FLEXUtility replaceImplementationOfKnownSelector:selector onClass:className withBlock:asyncDataOrDownloadSwizzleBlock swizzledSelector:swizzledSelector]; + } + }); +} + ++ (void)injectIntoNSURLSessionAsyncUploadTaskMethods +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class className = [NSURLSession class]; + + // The method signatures here are close enough that we can use the same logic to inject into both of them. + // Note that they have 3 arguments, so we can't easily combine with the data and download method above. + const SEL selectors[] = { + @selector(uploadTaskWithRequest:fromData:completionHandler:), + @selector(uploadTaskWithRequest:fromFile:completionHandler:) + }; + + const int numSelectors = sizeof(selectors) / sizeof(SEL); + + for (int selectorIndex = 0; selectorIndex < numSelectors; selectorIndex++) { + SEL selector = selectors[selectorIndex]; + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + if ([FLEXUtility instanceRespondsButDoesNotImplementSelector:selector class:className]) { + // iOS 7 does not implement these methods on NSURLSession. We actually want to + // swizzle __NSCFURLSession, which we can get from the class of the shared session + className = [[NSURLSession sharedSession] class]; + } + + NSURLSessionUploadTask *(^asyncUploadTaskSwizzleBlock)(Class, NSURLRequest *, id, NSURLSessionAsyncCompletion) = ^NSURLSessionUploadTask *(Class slf, NSURLRequest *request, id argument, NSURLSessionAsyncCompletion completion) { + NSURLSessionUploadTask *task = nil; + if (completion) { + NSString *requestID = [self nextRequestID]; + NSString *mechanism = [self mechanismFromClassMethod:selector onClass:className]; + NSURLSessionAsyncCompletion completionWrapper = [self asyncCompletionWrapperForRequestID:requestID mechanism:mechanism completion:completion]; + task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completionWrapper); + [self setRequestID:requestID forConnectionOrTask:task]; + } else { + task = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, request, argument, completion); + } + return task; + }; + + [FLEXUtility replaceImplementationOfKnownSelector:selector onClass:className withBlock:asyncUploadTaskSwizzleBlock swizzledSelector:swizzledSelector]; + } + }); +} + ++ (NSString *)mechanismFromClassMethod:(SEL)selector onClass:(Class)className +{ + return [NSString stringWithFormat:@"+[%@ %@]", NSStringFromClass(className), NSStringFromSelector(selector)]; +} + ++ (NSURLSessionAsyncCompletion)asyncCompletionWrapperForRequestID:(NSString *)requestID mechanism:(NSString *)mechanism completion:(NSURLSessionAsyncCompletion)completion +{ + NSURLSessionAsyncCompletion completionWrapper = ^(id fileURLOrData, NSURLResponse *response, NSError *error) { + [[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID]; + [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response]; + NSData *data = nil; + if ([fileURLOrData isKindOfClass:[NSURL class]]) { + data = [NSData dataWithContentsOfURL:fileURLOrData]; + } else if ([fileURLOrData isKindOfClass:[NSData class]]) { + data = fileURLOrData; + } + [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:[data length]]; + if (error) { + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:error]; + } else { + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:data]; + } + + // Call through to the original completion handler + if (completion) { + completion(fileURLOrData, response, error); + } + }; + return completionWrapper; +} + ++ (void)injectWillSendRequestIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(connection:willSendRequest:redirectResponse:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLConnectionDataDelegate); + if (!protocol) { + protocol = @protocol(NSURLConnectionDelegate); + } + + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef NSURLRequest *(^NSURLConnectionWillSendRequestBlock)(id slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response); + + NSURLConnectionWillSendRequestBlock undefinedBlock = ^NSURLRequest *(id slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response) { + [[FLEXNetworkObserver sharedObserver] connection:connection willSendRequest:request redirectResponse:response delegate:slf]; + return request; + }; + + NSURLConnectionWillSendRequestBlock implementationBlock = ^NSURLRequest *(id slf, NSURLConnection *connection, NSURLRequest *request, NSURLResponse *response) { + __block NSURLRequest *returnValue = nil; + [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ + undefinedBlock(slf, connection, request, response); + } originalImplementationBlock:^{ + returnValue = ((id(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, connection, request, response); + }]; + return returnValue; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; +} + ++ (void)injectDidReceiveResponseIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(connection:didReceiveResponse:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLConnectionDataDelegate); + if (!protocol) { + protocol = @protocol(NSURLConnectionDelegate); + } + + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLConnectionDidReceiveResponseBlock)(id slf, NSURLConnection *connection, NSURLResponse *response); + + NSURLConnectionDidReceiveResponseBlock undefinedBlock = ^(id slf, NSURLConnection *connection, NSURLResponse *response) { + [[FLEXNetworkObserver sharedObserver] connection:connection didReceiveResponse:response delegate:slf]; + }; + + NSURLConnectionDidReceiveResponseBlock implementationBlock = ^(id slf, NSURLConnection *connection, NSURLResponse *response) { + [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ + undefinedBlock(slf, connection, response); + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, connection, response); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; +} + ++ (void)injectDidReceiveDataIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(connection:didReceiveData:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLConnectionDataDelegate); + if (!protocol) { + protocol = @protocol(NSURLConnectionDelegate); + } + + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLConnectionDidReceiveDataBlock)(id slf, NSURLConnection *connection, NSData *data); + + NSURLConnectionDidReceiveDataBlock undefinedBlock = ^(id slf, NSURLConnection *connection, NSData *data) { + [[FLEXNetworkObserver sharedObserver] connection:connection didReceiveData:data delegate:slf]; + }; + + NSURLConnectionDidReceiveDataBlock implementationBlock = ^(id slf, NSURLConnection *connection, NSData *data) { + [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ + undefinedBlock(slf, connection, data); + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, connection, data); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; +} + ++ (void)injectDidFinishLoadingIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(connectionDidFinishLoading:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLConnectionDataDelegate); + if (!protocol) { + protocol = @protocol(NSURLConnectionDelegate); + } + + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLConnectionDidFinishLoadingBlock)(id slf, NSURLConnection *connection); + + NSURLConnectionDidFinishLoadingBlock undefinedBlock = ^(id slf, NSURLConnection *connection) { + [[FLEXNetworkObserver sharedObserver] connectionDidFinishLoading:connection delegate:slf]; + }; + + NSURLConnectionDidFinishLoadingBlock implementationBlock = ^(id slf, NSURLConnection *connection) { + [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ + undefinedBlock(slf, connection); + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id))objc_msgSend)(slf, swizzledSelector, connection); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; +} + ++ (void)injectDidFailWithErrorIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(connection:didFailWithError:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLConnectionDelegate); + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLConnectionDidFailWithErrorBlock)(id slf, NSURLConnection *connection, NSError *error); + + NSURLConnectionDidFailWithErrorBlock undefinedBlock = ^(id slf, NSURLConnection *connection, NSError *error) { + [[FLEXNetworkObserver sharedObserver] connection:connection didFailWithError:error delegate:slf]; + }; + + NSURLConnectionDidFailWithErrorBlock implementationBlock = ^(id slf, NSURLConnection *connection, NSError *error) { + [self sniffWithoutDuplicationForObject:connection selector:selector sniffingBlock:^{ + undefinedBlock(slf, connection, error); + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id, id))objc_msgSend)(slf, swizzledSelector, connection, error); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; +} + ++ (void)injectTaskWillPerformHTTPRedirectionIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLSessionTaskDelegate); + + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLSessionWillPerformHTTPRedirectionBlock)(id slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)); + + NSURLSessionWillPerformHTTPRedirectionBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)) { + [[FLEXNetworkObserver sharedObserver] URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler delegate:slf]; + completionHandler(newRequest); + }; + + NSURLSessionWillPerformHTTPRedirectionBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionTask *task, NSHTTPURLResponse *response, NSURLRequest *newRequest, void(^completionHandler)(NSURLRequest *)) { + [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ + [[FLEXNetworkObserver sharedObserver] URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler delegate:slf]; + } originalImplementationBlock:^{ + ((id(*)(id, SEL, id, id, id, id, void(^)(NSURLRequest *)))objc_msgSend)(slf, swizzledSelector, session, task, response, newRequest, completionHandler); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; + +} + ++ (void)injectTaskDidReceiveDataIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(URLSession:dataTask:didReceiveData:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLSessionDataDelegate); + + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLSessionDidReceiveDataBlock)(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data); + + NSURLSessionDidReceiveDataBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data) { + [[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveData:data delegate:slf]; + }; + + NSURLSessionDidReceiveDataBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data) { + [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ + undefinedBlock(slf, session, dataTask, data); + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, session, dataTask, data); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; + +} + ++ (void)injectDataTaskDidBecomeDownloadTaskIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(URLSession:dataTask:didBecomeDownloadTask:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLSessionDataDelegate); + + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLSessionDidBecomeDownloadTaskBlock)(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask); + + NSURLSessionDidBecomeDownloadTaskBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask) { + [[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask delegate:slf]; + }; + + NSURLSessionDidBecomeDownloadTaskBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask) { + [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ + undefinedBlock(slf, session, dataTask, downloadTask); + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, session, dataTask, downloadTask); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; +} + ++ (void)injectTaskDidReceiveResponseIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(URLSession:dataTask:didReceiveResponse:completionHandler:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLSessionDataDelegate); + + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLSessionDidReceiveResponseBlock)(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)); + + NSURLSessionDidReceiveResponseBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)) { + [[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler delegate:slf]; + completionHandler(NSURLSessionResponseAllow); + }; + + NSURLSessionDidReceiveResponseBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response, void(^completionHandler)(NSURLSessionResponseDisposition disposition)) { + [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ + [[FLEXNetworkObserver sharedObserver] URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler delegate:slf]; + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id, id, id, void(^)(NSURLSessionResponseDisposition)))objc_msgSend)(slf, swizzledSelector, session, dataTask, response, completionHandler); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; + +} + ++ (void)injectTaskDidCompleteWithErrorIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(URLSession:task:didCompleteWithError:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLSessionTaskDelegate); + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLSessionTaskDidCompleteWithErrorBlock)(id slf, NSURLSession *session, NSURLSessionTask *task, NSError *error); + + NSURLSessionTaskDidCompleteWithErrorBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionTask *task, NSError *error) { + [[FLEXNetworkObserver sharedObserver] URLSession:session task:task didCompleteWithError:error delegate:slf]; + }; + + NSURLSessionTaskDidCompleteWithErrorBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionTask *task, NSError *error) { + [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ + undefinedBlock(slf, session, task, error); + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, session, task, error); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; +} + +// Used for overriding AFNetworking behavior ++ (void)injectRespondsToSelectorIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(respondsToSelector:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + //Protocol *protocol = @protocol(NSURLSessionTaskDelegate); + Method method = class_getInstanceMethod(cls, selector); + struct objc_method_description methodDescription = *method_getDescription(method); + + BOOL (^undefinedBlock)(id , SEL) = ^(id slf, SEL sel) { + return YES; + }; + + BOOL (^implementationBlock)(id , SEL) = ^(id slf, SEL sel) { + if (sel == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) { + return undefinedBlock(slf, sel); + } + return ((BOOL(*)(id, SEL, SEL))objc_msgSend)(slf, swizzledSelector, sel); + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; +} + + ++ (void)injectDownloadTaskDidFinishDownloadingIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(URLSession:downloadTask:didFinishDownloadingToURL:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLSessionDownloadDelegate); + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLSessionDownloadTaskDidFinishDownloadingBlock)(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, NSURL *location); + + NSURLSessionDownloadTaskDidFinishDownloadingBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, NSURL *location) { + NSData *data = [NSData dataWithContentsOfFile:location.relativePath]; + [[FLEXNetworkObserver sharedObserver] URLSession:session task:task didFinishDownloadingToURL:location data:data delegate:slf]; + }; + + NSURLSessionDownloadTaskDidFinishDownloadingBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, NSURL *location) { + [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ + undefinedBlock(slf, session, task, location); + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id, id, id))objc_msgSend)(slf, swizzledSelector, session, task, location); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; +} + ++ (void)injectDownloadTaskDidWriteDataIntoDelegateClass:(Class)cls +{ + SEL selector = @selector(URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:); + SEL swizzledSelector = [FLEXUtility swizzledSelectorForSelector:selector]; + + Protocol *protocol = @protocol(NSURLSessionDownloadDelegate); + struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES); + + typedef void (^NSURLSessionDownloadTaskDidWriteDataBlock)(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite); + + NSURLSessionDownloadTaskDidWriteDataBlock undefinedBlock = ^(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { + [[FLEXNetworkObserver sharedObserver] URLSession:session downloadTask:task didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite delegate:slf]; + }; + + NSURLSessionDownloadTaskDidWriteDataBlock implementationBlock = ^(id slf, NSURLSession *session, NSURLSessionDownloadTask *task, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { + [self sniffWithoutDuplicationForObject:session selector:selector sniffingBlock:^{ + undefinedBlock(slf, session, task, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + } originalImplementationBlock:^{ + ((void(*)(id, SEL, id, id, int64_t, int64_t, int64_t))objc_msgSend)(slf, swizzledSelector, session, task, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + }]; + }; + + [FLEXUtility replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:cls withMethodDescription:methodDescription implementationBlock:implementationBlock undefinedBlock:undefinedBlock]; + +} + +static char const *const kFLEXRequestIDKey = "kFLEXRequestIDKey"; + ++ (NSString *)requestIDForConnectionOrTask:(id)connectionOrTask +{ + NSString *requestID = objc_getAssociatedObject(connectionOrTask, kFLEXRequestIDKey); + if (!requestID) { + requestID = [self nextRequestID]; + [self setRequestID:requestID forConnectionOrTask:connectionOrTask]; + } + return requestID; +} + ++ (void)setRequestID:(NSString *)requestID forConnectionOrTask:(id)connectionOrTask +{ + objc_setAssociatedObject(connectionOrTask, kFLEXRequestIDKey, requestID, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +#pragma mark - Initialization + +- (id)init +{ + self = [super init]; + if (self) { + self.requestStatesForRequestIDs = [NSMutableDictionary new]; + self.queue = dispatch_queue_create("com.flex.FLEXNetworkObserver", DISPATCH_QUEUE_SERIAL); + } + return self; +} + +#pragma mark - Private Methods + +- (void)performBlock:(dispatch_block_t)block +{ + dispatch_async(_queue, block); +} + +- (FLEXInternalRequestState *)requestStateForRequestID:(NSString *)requestID +{ + FLEXInternalRequestState *requestState = self.requestStatesForRequestIDs[requestID]; + if (!requestState) { + requestState = [FLEXInternalRequestState new]; + [self.requestStatesForRequestIDs setObject:requestState forKey:requestID]; + } + return requestState; +} + +- (void)removeRequestStateForRequestID:(NSString *)requestID +{ + [self.requestStatesForRequestIDs removeObjectForKey:requestID]; +} + +@end + + +@implementation FLEXNetworkObserver (NSURLConnectionHelpers) + +- (void)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response delegate:(id)delegate +{ + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + requestState.request = request; + [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:response]; + NSString *mechanism = [NSString stringWithFormat:@"NSURLConnection (delegate: %@)", [delegate class]]; + [[FLEXNetworkRecorder defaultRecorder] recordMechanism:mechanism forRequestID:requestID]; + }]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response delegate:(id)delegate +{ + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + + NSMutableData *dataAccumulator = nil; + if (response.expectedContentLength < 0) { + dataAccumulator = [NSMutableData new]; + } else { + dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)response.expectedContentLength]; + } + requestState.dataAccumulator = dataAccumulator; + + [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response]; + }]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data delegate:(id)delegate +{ + // Just to be safe since we're doing this async + data = [data copy]; + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + [requestState.dataAccumulator appendData:data]; + [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:data.length]; + }]; +} + +- (void)connectionDidFinishLoading:(NSURLConnection *)connection delegate:(id)delegate +{ + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:requestState.dataAccumulator]; + [self removeRequestStateForRequestID:requestID]; + }]; +} + +- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error delegate:(id)delegate +{ + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:connection]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + + // Cancellations can occur prior to the willSendRequest:... NSURLConnection delegate call. + // These are pretty common and clutter up the logs. Only record the failure if the recorder already knows about the request through willSendRequest:... + if (requestState.request) { + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:error]; + } + + [self removeRequestStateForRequestID:requestID]; + }]; +} + +- (void)connectionWillCancel:(NSURLConnection *)connection +{ + [self performBlock:^{ + // Mimic the behavior of NSURLSession which is to create an error on cancellation. + NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"cancelled" }; + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]; + [self connection:connection didFailWithError:error delegate:nil]; + }]; +} + +@end + + +@implementation FLEXNetworkObserver (NSURLSessionTaskHelpers) + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler delegate:(id)delegate +{ + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:task]; + [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:request redirectResponse:response]; + }]; +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler delegate:(id)delegate +{ + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + + NSMutableData *dataAccumulator = nil; + if (response.expectedContentLength < 0) { + dataAccumulator = [NSMutableData new]; + } else { + dataAccumulator = [[NSMutableData alloc] initWithCapacity:(NSUInteger)response.expectedContentLength]; + } + requestState.dataAccumulator = dataAccumulator; + + NSString *requestMechanism = [NSString stringWithFormat:@"NSURLSessionDataTask (delegate: %@)", [delegate class]]; + [[FLEXNetworkRecorder defaultRecorder] recordMechanism:requestMechanism forRequestID:requestID]; + + [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:response]; + }]; +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask delegate:(id)delegate +{ + [self performBlock:^{ + // By setting the request ID of the download task to match the data task, + // it can pick up where the data task left off. + NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask]; + [[self class] setRequestID:requestID forConnectionOrTask:downloadTask]; + }]; +} + +- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data delegate:(id)delegate +{ + // Just to be safe since we're doing this async + data = [data copy]; + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:dataTask]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + + [requestState.dataAccumulator appendData:data]; + + [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:data.length]; + }]; +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error delegate:(id)delegate +{ + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:task]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + + if (error) { + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFailedWithRequestID:requestID error:error]; + } else { + [[FLEXNetworkRecorder defaultRecorder] recordLoadingFinishedWithRequestID:requestID responseBody:requestState.dataAccumulator]; + } + + [self removeRequestStateForRequestID:requestID]; + }]; +} + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite delegate:(id)delegate +{ + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:downloadTask]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + + if (!requestState.dataAccumulator) { + NSUInteger unsignedBytesExpectedToWrite = totalBytesExpectedToWrite > 0 ? (NSUInteger)totalBytesExpectedToWrite : 0; + requestState.dataAccumulator = [[NSMutableData alloc] initWithCapacity:unsignedBytesExpectedToWrite]; + [[FLEXNetworkRecorder defaultRecorder] recordResponseReceivedWithRequestID:requestID response:downloadTask.response]; + + NSString *requestMechanism = [NSString stringWithFormat:@"NSURLSessionDownloadTask (delegate: %@)", [delegate class]]; + [[FLEXNetworkRecorder defaultRecorder] recordMechanism:requestMechanism forRequestID:requestID]; + } + + [[FLEXNetworkRecorder defaultRecorder] recordDataReceivedWithRequestID:requestID dataLength:bytesWritten]; + }]; +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location data:(NSData *)data delegate:(id)delegate +{ + data = [data copy]; + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:downloadTask]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + [requestState.dataAccumulator appendData:data]; + }]; +} + +- (void)URLSessionTaskWillResume:(NSURLSessionTask *)task +{ + // Since resume can be called multiple times on the same task, only treat the first resume as + // the equivalent to connection:willSendRequest:... + [self performBlock:^{ + NSString *requestID = [[self class] requestIDForConnectionOrTask:task]; + FLEXInternalRequestState *requestState = [self requestStateForRequestID:requestID]; + if (!requestState.request) { + requestState.request = task.currentRequest; + + [[FLEXNetworkRecorder defaultRecorder] recordRequestWillBeSentWithRequestID:requestID request:task.currentRequest redirectResponse:nil]; + } + }]; +} + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkRecorder.h b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkRecorder.h new file mode 100755 index 000000000..518e2cf7d --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkRecorder.h @@ -0,0 +1,70 @@ +// +// FLEXNetworkRecorder.h +// Flipboard +// +// Created by Ryan Olson on 2/4/15. +// Copyright 2004-present Facebook. All Rights Reserved. +// + +#import + +#import + +// Notifications posted when the record is updated +extern NSString *const kFLEXNetworkRecorderNewTransactionNotification; +extern NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification; +extern NSString *const kFLEXNetworkRecorderUserInfoTransactionKey; +extern NSString *const kFLEXNetworkRecorderTransactionsClearedNotification; + +@class FLEXNetworkTransaction; + +@interface FLEXNetworkRecorder : NSObject + +/// In general, it only makes sense to have one recorder for the entire application. ++ (instancetype)defaultRecorder; + +@property (nonatomic, weak) id delegate; + +/// Defaults to 25 MB if never set. Values set here are presisted across launches of the app. +@property (nonatomic, assign) NSUInteger responseCacheByteLimit; + +/// If NO, the recorder not cache will not cache response for content types with an "image", "video", or "audio" prefix. +@property (nonatomic, assign) BOOL shouldCacheMediaResponses; + +@property (nonatomic, copy) NSArray *hostBlacklist; + + +// Accessing recorded network activity + +/// Array of FLEXNetworkTransaction objects ordered by start time with the newest first. +- (NSArray *)networkTransactions; + +/// The full response data IFF it hasn't been purged due to memory pressure. +- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction; + +/// Dumps all network transactions and cached response bodies. +- (void)clearRecordedActivity; + + +// Recording network activity + +/// Call when app is about to send HTTP request. +- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse; + +/// Call when HTTP response is available. +- (void)recordResponseReceivedWithRequestID:(NSString *)requestID response:(NSURLResponse *)response; + +/// Call when data chunk is received over the network. +- (void)recordDataReceivedWithRequestID:(NSString *)requestID dataLength:(int64_t)dataLength; + +/// Call when HTTP request has finished loading. +- (void)recordLoadingFinishedWithRequestID:(NSString *)requestID responseBody:(NSData *)responseBody; + +/// Call when HTTP request has failed to load. +- (void)recordLoadingFailedWithRequestID:(NSString *)requestID error:(NSError *)error; + +/// Call to set the request mechanism anytime after recordRequestWillBeSent... has been called. +/// This string can be set to anything useful about the API used to make the request. +- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID; + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkRecorder.mm b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkRecorder.mm new file mode 100755 index 000000000..b9d5004c1 --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkRecorder.mm @@ -0,0 +1,235 @@ +// +// FLEXNetworkRecorder.m +// Flipboard +// +// Created by Ryan Olson on 2/4/15. +// Copyright 2004-present Facebook. All Rights Reserved. +// + +#import "FLEXNetworkRecorder.h" + +#import "FLEXNetworkTransaction.h" +#import "FLEXUtility.h" + +NSString *const kFLEXNetworkRecorderNewTransactionNotification = @"kFLEXNetworkRecorderNewTransactionNotification"; +NSString *const kFLEXNetworkRecorderTransactionUpdatedNotification = @"kFLEXNetworkRecorderTransactionUpdatedNotification"; +NSString *const kFLEXNetworkRecorderUserInfoTransactionKey = @"transaction"; +NSString *const kFLEXNetworkRecorderTransactionsClearedNotification = @"kFLEXNetworkRecorderTransactionsClearedNotification"; + +NSString *const kFLEXNetworkRecorderResponseCacheLimitDefaultsKey = @"com.flex.responseCacheLimit"; + +@interface FLEXNetworkRecorder () + +@property (nonatomic, strong) NSCache *responseCache; +@property (nonatomic, strong) NSMutableArray *orderedTransactions; +@property (nonatomic, strong) NSMutableDictionary *networkTransactionsForRequestIdentifiers; +@property (nonatomic, strong) dispatch_queue_t queue; +@property (nonatomic, strong) NSMutableDictionary *identifierDict; +@end + +@implementation FLEXNetworkRecorder + +- (instancetype)init +{ + self = [super init]; + if (self) { + _responseCache = [NSCache new]; + NSUInteger responseCacheLimit = [[[NSUserDefaults standardUserDefaults] objectForKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey] unsignedIntegerValue]; + if (responseCacheLimit) { + [_responseCache setTotalCostLimit:responseCacheLimit]; + } else { + // Default to 25 MB max. The cache will purge earlier if there is memory pressure. + [_responseCache setTotalCostLimit:25 * 1024 * 1024]; + } + _orderedTransactions = [NSMutableArray array]; + _networkTransactionsForRequestIdentifiers = [NSMutableDictionary dictionary]; + + // Serial queue used because we use mutable objects that are not thread safe + _queue = dispatch_queue_create("com.flex.FLEXNetworkRecorder", DISPATCH_QUEUE_SERIAL); + _identifierDict = [NSMutableDictionary dictionary]; + } + return self; +} + ++ (instancetype)defaultRecorder +{ + static FLEXNetworkRecorder *defaultRecorder = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + defaultRecorder = [[[self class] alloc] init]; + }); + return defaultRecorder; +} + +#pragma mark - Public Data Access + +- (void)setDelegate:(id)delegate { + _delegate = delegate; +} + +- (NSUInteger)responseCacheByteLimit +{ + return [self.responseCache totalCostLimit]; +} + +- (void)setResponseCacheByteLimit:(NSUInteger)responseCacheByteLimit +{ + [self.responseCache setTotalCostLimit:responseCacheByteLimit]; + [[NSUserDefaults standardUserDefaults] setObject:@(responseCacheByteLimit) forKey:kFLEXNetworkRecorderResponseCacheLimitDefaultsKey]; +} + +- (NSArray *)networkTransactions +{ + __block NSArray *transactions = nil; + dispatch_sync(self.queue, ^{ + transactions = [self.orderedTransactions copy]; + }); + return transactions; +} + +- (NSData *)cachedResponseBodyForTransaction:(FLEXNetworkTransaction *)transaction +{ + return [self.responseCache objectForKey:transaction.requestID]; +} + +- (void)clearRecordedActivity +{ + dispatch_async(self.queue, ^{ + [self.responseCache removeAllObjects]; + [self.orderedTransactions removeAllObjects]; + [self.networkTransactionsForRequestIdentifiers removeAllObjects]; + }); +} + +#pragma mark - Network Events + +- (void)recordRequestWillBeSentWithRequestID:(NSString *)requestID request:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse +{ + if (![self.identifierDict objectForKey:requestID]) { + self.identifierDict[requestID] = [NSNumber random]; + } + NSDate *startDate = [NSDate date]; + + if (redirectResponse) { + [self recordResponseReceivedWithRequestID:requestID response:redirectResponse]; + [self recordLoadingFinishedWithRequestID:requestID responseBody:nil]; + } + + dispatch_async(self.queue, ^{ + RequestInfo info = { + .identifier = self.identifierDict[requestID].longLongValue, + .timestamp = [NSDate timestamp], + .request = request, + }; + + info.setBody(request.HTTPBody); + [self.delegate didObserveRequest:info]; + + FLEXNetworkTransaction *transaction = [FLEXNetworkTransaction new]; + transaction.requestID = requestID; + transaction.request = request; + transaction.startTime = startDate; + + [self.orderedTransactions insertObject:transaction atIndex:0]; + [self.networkTransactionsForRequestIdentifiers setObject:transaction forKey:requestID]; + transaction.transactionState = FLEXNetworkTransactionStateAwaitingResponse; + }); +} + +/// Call when HTTP response is available. +- (void)recordResponseReceivedWithRequestID:(NSString *)requestID response:(NSURLResponse *)response +{ + NSDate *responseDate = [NSDate date]; + + dispatch_async(self.queue, ^{ + FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID]; + if (!transaction) { + return; + } + transaction.response = response; + transaction.transactionState = FLEXNetworkTransactionStateReceivingData; + transaction.latency = -[transaction.startTime timeIntervalSinceDate:responseDate]; + }); +} + +/// Call when data chunk is received over the network. +- (void)recordDataReceivedWithRequestID:(NSString *)requestID dataLength:(int64_t)dataLength +{ + dispatch_async(self.queue, ^{ + FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID]; + if (!transaction) { + return; + } + transaction.receivedDataLength += dataLength; + }); +} + +/// Call when HTTP request has finished loading. +- (void)recordLoadingFinishedWithRequestID:(NSString *)requestID responseBody:(NSData *)responseBody +{ + NSDate *finishedDate = [NSDate date]; + dispatch_async(self.queue, ^{ + + FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID]; + if (!transaction) { + return; + } + transaction.transactionState = FLEXNetworkTransactionStateFinished; + transaction.duration = -[transaction.startTime timeIntervalSinceDate:finishedDate]; + ResponseInfo responseInfo = { + .identifier = self.identifierDict[requestID].longLongValue, + .timestamp = [NSDate timestamp], + .response = transaction.response, + .body = nil, + }; + responseInfo.setBody(responseBody); + self.identifierDict[requestID] = nil; //Clear the entry + [self.delegate didObserveResponse:responseInfo]; + + BOOL shouldCache = [responseBody length] > 0; + if (!self.shouldCacheMediaResponses) { + NSArray *ignoredMIMETypePrefixes = @[ @"audio", @"image", @"video" ]; + for (NSString *ignoredPrefix in ignoredMIMETypePrefixes) { + shouldCache = shouldCache && ![transaction.response.MIMEType hasPrefix:ignoredPrefix]; + } + } + + if (shouldCache) { + [self.responseCache setObject:responseBody forKey:requestID cost:[responseBody length]]; + } + }); +} + +- (void)recordLoadingFailedWithRequestID:(NSString *)requestID error:(NSError *)error +{ + dispatch_async(self.queue, ^{ + FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID]; + if (!transaction) { + return; + } + ResponseInfo responseInfo = { + .identifier = self.identifierDict[requestID].longLongValue, + .timestamp = [NSDate timestamp], + .response = transaction.response, + .body = nil, + }; + self.identifierDict[requestID] = nil; //Clear the entry + [self.delegate didObserveResponse:responseInfo]; + transaction.transactionState = FLEXNetworkTransactionStateFailed; + transaction.duration = -[transaction.startTime timeIntervalSinceNow]; + transaction.error = error; + }); +} + +- (void)recordMechanism:(NSString *)mechanism forRequestID:(NSString *)requestID +{ + dispatch_async(self.queue, ^{ + FLEXNetworkTransaction *transaction = self.networkTransactionsForRequestIdentifiers[requestID]; + if (!transaction) { + return; + } + transaction.requestMechanism = mechanism; + }); +} + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkTransaction.h b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkTransaction.h new file mode 100755 index 000000000..b6026bbb1 --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkTransaction.h @@ -0,0 +1,45 @@ +// +// FLEXNetworkTransaction.h +// Flipboard +// +// Created by Ryan Olson on 2/8/15. +// Copyright 2004-present Facebook. All Rights Reserved. +// + +#import + +#import "UIKit/UIKit.h" + +typedef NS_ENUM(NSInteger, FLEXNetworkTransactionState) { + FLEXNetworkTransactionStateUnstarted, + FLEXNetworkTransactionStateAwaitingResponse, + FLEXNetworkTransactionStateReceivingData, + FLEXNetworkTransactionStateFinished, + FLEXNetworkTransactionStateFailed +}; + +@interface FLEXNetworkTransaction : NSObject + +@property (nonatomic, copy) NSString *requestID; + +@property (nonatomic, strong) NSURLRequest *request; +@property (nonatomic, strong) NSURLResponse *response; +@property (nonatomic, copy) NSString *requestMechanism; +@property (nonatomic, assign) FLEXNetworkTransactionState transactionState; +@property (nonatomic, strong) NSError *error; + +@property (nonatomic, strong) NSDate *startTime; +@property (nonatomic, assign) NSTimeInterval latency; +@property (nonatomic, assign) NSTimeInterval duration; + +@property (nonatomic, assign) int64_t receivedDataLength; + +/// Only applicable for image downloads. A small thumbnail to preview the full response. +@property (nonatomic, strong) UIImage *responseThumbnail; + +/// Populated lazily. Handles both normal HTTPBody data and HTTPBodyStreams. +@property (nonatomic, strong, readonly) NSData *cachedRequestBody; + ++ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state; + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkTransaction.m b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkTransaction.m new file mode 100755 index 000000000..455e1f56f --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXNetworkTransaction.m @@ -0,0 +1,74 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import "FLEXNetworkTransaction.h" + +@interface FLEXNetworkTransaction () + +@property (nonatomic, strong, readwrite) NSData *cachedRequestBody; + +@end + +@implementation FLEXNetworkTransaction + +- (NSString *)description +{ + NSString *description = [super description]; + + description = [description stringByAppendingFormat:@" id = %@;", self.requestID]; + description = [description stringByAppendingFormat:@" url = %@;", self.request.URL]; + description = [description stringByAppendingFormat:@" duration = %f;", self.duration]; + description = [description stringByAppendingFormat:@" receivedDataLength = %lld", self.receivedDataLength]; + + return description; +} + +- (NSData *)cachedRequestBody { + if (!_cachedRequestBody) { + if (self.request.HTTPBody != nil) { + _cachedRequestBody = self.request.HTTPBody; + } else if ([self.request.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) { + NSInputStream *bodyStream = [self.request.HTTPBodyStream copy]; + const NSUInteger bufferSize = 1024; + uint8_t buffer[bufferSize]; + NSMutableData *data = [NSMutableData data]; + [bodyStream open]; + NSInteger readBytes = 0; + do { + readBytes = [bodyStream read:buffer maxLength:bufferSize]; + [data appendBytes:buffer length:readBytes]; + } while (readBytes > 0); + [bodyStream close]; + _cachedRequestBody = data; + } + } + return _cachedRequestBody; +} + ++ (NSString *)readableStringFromTransactionState:(FLEXNetworkTransactionState)state +{ + NSString *readableString = nil; + switch (state) { + case FLEXNetworkTransactionStateUnstarted: + readableString = @"Unstarted"; + break; + + case FLEXNetworkTransactionStateAwaitingResponse: + readableString = @"Awaiting Response"; + break; + + case FLEXNetworkTransactionStateReceivingData: + readableString = @"Receiving Data"; + break; + + case FLEXNetworkTransactionStateFinished: + readableString = @"Finished"; + break; + + case FLEXNetworkTransactionStateFailed: + readableString = @"Failed"; + break; + } + return readableString; +} + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXUtility.h b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXUtility.h new file mode 100755 index 000000000..aca2b5a99 --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXUtility.h @@ -0,0 +1,34 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#import +#import +#import + +#import +#import + +#define FLEXFloor(x) (floor([[UIScreen mainScreen] scale] * (x)) / [[UIScreen mainScreen] scale]) + +#define FLEX_AT_LEAST_IOS11_SDK defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0) + +@interface NSNumber (SonarUtility) + ++ (NSNumber *)random; + +@end + +@interface NSDate (SonarUtility) + ++ (uint64_t)timestamp; +@end + +@interface FLEXUtility : NSObject + +// Swizzling utilities + ++ (SEL)swizzledSelectorForSelector:(SEL)selector; ++ (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls; ++ (void)replaceImplementationOfKnownSelector:(SEL)originalSelector onClass:(Class)className withBlock:(id)block swizzledSelector:(SEL)swizzledSelector; ++ (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock; + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXUtility.mm b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXUtility.mm new file mode 100755 index 000000000..c287d90c8 --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/FLEXNetworkLib/FLEXUtility.mm @@ -0,0 +1,117 @@ +// +// FLEXUtility.m +// Flipboard +// +// Created by Ryan Olson on 4/18/14. +// Copyright 2004-present Facebook. All Rights Reserved. +// + +#import "FLEXUtility.h" + +#import +#include +#import +#include +#include + +#import + +@implementation FLEXUtility + ++ (SEL)swizzledSelectorForSelector:(SEL)selector +{ + return NSSelectorFromString([NSString stringWithFormat:@"_flex_swizzle_%x_%@", arc4random(), NSStringFromSelector(selector)]); +} + ++ (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls +{ + if ([cls instancesRespondToSelector:selector]) { + unsigned int numMethods = 0; + Method *methods = class_copyMethodList(cls, &numMethods); + + BOOL implementsSelector = NO; + for (int index = 0; index < numMethods; index++) { + SEL methodSelector = method_getName(methods[index]); + if (selector == methodSelector) { + implementsSelector = YES; + break; + } + } + + free(methods); + + if (!implementsSelector) { + return YES; + } + } + + return NO; +} + ++ (void)replaceImplementationOfKnownSelector:(SEL)originalSelector onClass:(Class)className withBlock:(id)block swizzledSelector:(SEL)swizzledSelector +{ + // This method is only intended for swizzling methods that are know to exist on the class. + // Bail if that isn't the case. + Method originalMethod = class_getInstanceMethod(className, originalSelector); + if (!originalMethod) { + return; + } + + IMP implementation = imp_implementationWithBlock(block); + class_addMethod(className, swizzledSelector, implementation, method_getTypeEncoding(originalMethod)); + Method newMethod = class_getInstanceMethod(className, swizzledSelector); + method_exchangeImplementations(originalMethod, newMethod); +} + ++ (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock +{ + if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) { + return; + } + + IMP implementation = imp_implementationWithBlock((id)([cls instancesRespondToSelector:selector] ? implementationBlock : undefinedBlock)); + + Method oldMethod = class_getInstanceMethod(cls, selector); + if (oldMethod) { + class_addMethod(cls, swizzledSelector, implementation, methodDescription.types); + + Method newMethod = class_getInstanceMethod(cls, swizzledSelector); + + method_exchangeImplementations(oldMethod, newMethod); + } else { + class_addMethod(cls, selector, implementation, methodDescription.types); + } +} + +@end + +@implementation NSNumber (SonarUtility) + ++ (NSNumber *)random { + int64_t identifier; + arc4random_buf(&identifier, sizeof(int64_t)); + return @(identifier); +} + +@end + +@implementation NSDate (SonarUtility) + ++ (uint64_t)getTimeNanoseconds +{ + static struct mach_timebase_info tb_info = {0}; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + __unused int ret = mach_timebase_info(&tb_info); + assert(0 == ret); + }); + + return (mach_absolute_time() * tb_info.numer) / tb_info.denom; +} + ++ (uint64_t)timestamp { + const uint64_t nowNanoSeconds = [self getTimeNanoseconds]; + return nowNanoSeconds / 1000000; +} + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/SKIOSNetworkAdapter.h b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/SKIOSNetworkAdapter.h new file mode 100644 index 000000000..6bb5e8858 --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/SKIOSNetworkAdapter.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import +#import + +@interface SKIOSNetworkAdapter : NSObject +- (instancetype)init NS_DESIGNATED_INITIALIZER; +@property (weak, nonatomic) id delegate; + +@end + +#endif diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/SKIOSNetworkAdapter.mm b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/SKIOSNetworkAdapter.mm new file mode 100644 index 000000000..e96098d1b --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/SKIOSNetworkAdapter.mm @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SKIOSNetworkAdapter.h" +#import "FLEXNetworkObserver.h" +#import "FLEXNetworkRecorder.h" + +@implementation SKIOSNetworkAdapter +@synthesize delegate = _delegate; +- (instancetype)init{ + if (self=[super init]){ + _delegate = nil; + } + return self; +} + +- (void)setDelegate:(id)delegate { + _delegate = delegate; + [FLEXNetworkObserver start]; + [FLEXNetworkRecorder defaultRecorder].delegate = _delegate; +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKBufferingPlugin.h b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKBufferingPlugin.h new file mode 100644 index 000000000..45ec23df4 --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKBufferingPlugin.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import + +#import + +#import + +#import "SKDispatchQueue.h" + +@interface SKBufferingPlugin : NSObject + +- (instancetype)initWithQueue:(const std::shared_ptr &)queue NS_DESIGNATED_INITIALIZER; + +- (void)send:(NSString *)method sonarObject:(NSDictionary *)sonarObject; + +@end + +#endif diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKBufferingPlugin.mm b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKBufferingPlugin.mm new file mode 100644 index 000000000..0e8ae8512 --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKBufferingPlugin.mm @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import + +#import "SKBufferingPlugin.h" +#import + +struct CachedEvent { + NSString *method; + NSDictionary *sonarObject; +}; + +static const NSUInteger bufferSize = 500; + +@implementation SKBufferingPlugin +{ + std::vector _ringBuffer; + std::shared_ptr _connectionAccessQueue; + + id _connection; +} + +- (instancetype)initWithQueue:(const std::shared_ptr &)queue { + if (self = [super init]) { + _ringBuffer.reserve(bufferSize); + _connectionAccessQueue = queue; + } + return self; +} + +- (NSString *)identifier { + // Note: This must match with the javascript pulgin identifier!! + return @"Network"; +} + +- (void)didConnect:(id)connection { + _connectionAccessQueue->async(^{ + self->_connection = connection; + [self sendBufferedEvents]; + }); +} + +- (void)didDisconnect { + _connectionAccessQueue->async(^{ + self->_connection = nil; + }); +} + +- (void)send:(NSString *)method + sonarObject:(NSDictionary *)sonarObject { + _connectionAccessQueue->async(^{ + if (self->_connection) { + [self->_connection send:method withParams:sonarObject]; + } else { + if (self->_ringBuffer.size() == bufferSize) { + return; + } + self->_ringBuffer.push_back({ + .method = method, + .sonarObject = sonarObject + }); + } + }); +} + +- (void)sendBufferedEvents { + NSAssert(_connection, @"connection object cannot be nil"); + for (const auto &event : _ringBuffer) { + [_connection send:event.method withParams:event.sonarObject]; + } + _ringBuffer.clear(); +} + +@end + +#endif diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKDispatchQueue.h b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKDispatchQueue.h new file mode 100644 index 000000000..bce514719 --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKDispatchQueue.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#pragma once + +#import + +namespace facebook { + namespace sonar { + class DispatchQueue + { + public: + virtual void async(dispatch_block_t block) = 0; + }; + + class GCDQueue: public DispatchQueue + { + public: + GCDQueue(dispatch_queue_t underlyingQueue) + :_underlyingQueue(underlyingQueue) { } + + void async(dispatch_block_t block) override + { + dispatch_async(_underlyingQueue, block); + } + + private: + dispatch_queue_t _underlyingQueue; + }; + } +} + +#endif diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKNetworkReporter.h b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKNetworkReporter.h new file mode 100644 index 000000000..a08d18ba1 --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKNetworkReporter.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +struct RequestInfo { + int64_t identifier; + uint64_t timestamp; + NSURLRequest *request; + NSString *body; + + void setBody(NSData *data) { + body = data ? [data base64EncodedStringWithOptions: 0] + : [request.HTTPBody base64EncodedStringWithOptions: 0]; + } +}; + +struct ResponseInfo { + int64_t identifier; + uint64_t timestamp; + NSURLResponse *response; + NSString *body; + + bool shouldStripReponseBody() { + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; + NSString *contentType = httpResponse.allHeaderFields[@"content-type"]; + if (!contentType) { + return NO; + } + + return [contentType containsString:@"image/"] || + [contentType containsString:@"video/"] || + [contentType containsString:@"application/zip"]; + } + + void setBody(NSData *data) { + body = shouldStripReponseBody() ? nil : [data base64EncodedStringWithOptions: 0]; + } + +}; + +@protocol SKNetworkReporterDelegate + +- (void)didObserveRequest:(RequestInfo)request; +- (void)didObserveResponse:(ResponseInfo)response; + +@end + +@protocol SKNetworkAdapterDelegate + +@property (weak, nonatomic) id delegate; + +@end diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SonarKitNetworkPlugin.h b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SonarKitNetworkPlugin.h new file mode 100644 index 000000000..7792e41ce --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SonarKitNetworkPlugin.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED +#import + +#import + +#import "SKBufferingPlugin.h" +#import "SKNetworkReporter.h" +#import "SKDispatchQueue.h" + +@interface SonarKitNetworkPlugin : SKBufferingPlugin + +- (instancetype)initWithNetworkAdapter:(id)adapter NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithNetworkAdapter:(id)adapter queue:(const std::shared_ptr &)queue; //For test purposes + +@property (strong, nonatomic) id adapter; + +@end + +#endif diff --git a/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SonarKitNetworkPlugin.mm b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SonarKitNetworkPlugin.mm new file mode 100644 index 000000000..4c54dd8fb --- /dev/null +++ b/iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SonarKitNetworkPlugin.mm @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED +#import "SonarKitNetworkPlugin.h" +#import "SKNetworkReporter.h" + +@interface SonarKitNetworkPlugin () + +@end + +@implementation SonarKitNetworkPlugin + +- (void)setAdapter:(id)adapter { + _adapter = adapter; + _adapter.delegate = self; +} + +- (instancetype)init { + if (self = [super initWithQueue:std::make_shared(dispatch_queue_create("com.sonarkit.network.buffer", DISPATCH_QUEUE_SERIAL))]) { + } + return self; +} + +- (instancetype)initWithNetworkAdapter:(id)adapter { + if (self = [super initWithQueue:std::make_shared(dispatch_queue_create("com.sonarkit.network.buffer", DISPATCH_QUEUE_SERIAL))]) { + adapter.delegate = self; + _adapter = adapter; + } + return self; +} + +- (instancetype)initWithNetworkAdapter:(id)adapter queue:(const std::shared_ptr &)queue; { + if (self = [super initWithQueue:queue]) { + adapter.delegate = self; + _adapter = adapter; + } + return self; +} + +#pragma mark - SKNetworkReporterDelegate + + +- (void)didObserveRequest:(RequestInfo)request; +{ + NSMutableArray *> *headers = [NSMutableArray new]; + for (NSString *key in [request.request.allHTTPHeaderFields allKeys]) { + NSDictionary *header = @{ + @"key": key, + @"value": request.request.allHTTPHeaderFields[key] + }; + [headers addObject: header]; + } + + NSString *body = request.body; + + [self send:@"newRequest" + sonarObject:@{ + @"id": @(request.identifier), + @"timestamp": @(request.timestamp), + @"method": request.request.HTTPMethod ?: [NSNull null], + @"url": [request.request.URL absoluteString] ?: [NSNull null], + @"headers": headers, + @"data": body ? body : [NSNull null] + }]; +} + +- (void)didObserveResponse:(ResponseInfo)response +{ + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response.response; + + NSMutableArray *> *headers = [NSMutableArray new]; + for (NSString *key in [httpResponse.allHeaderFields allKeys]) { + NSDictionary *header = @{ + @"key": key, + @"value": httpResponse.allHeaderFields[key] + }; + [headers addObject: header]; + } + + NSString *body = response.body; + + [self send:@"newResponse" + sonarObject:@{ + @"id": @(response.identifier), + @"timestamp": @(response.timestamp), + @"status": @(httpResponse.statusCode), + @"reason": [NSHTTPURLResponse localizedStringForStatusCode: httpResponse.statusCode] ?: [NSNull null], + @"headers": headers, + @"data": body ? body : [NSNull null] + }]; + +} + +@end + +#endif diff --git a/iOS/Podfile b/iOS/Podfile new file mode 100644 index 000000000..056604147 --- /dev/null +++ b/iOS/Podfile @@ -0,0 +1,20 @@ +# Uncomment the next line to define a global platform for your project +platform :ios, '9.0' + +target 'SonarKit' do + # Uncomment the next line if you're using Swift or would like to use dynamic frameworks + # use_frameworks! + + project 'SonarKit.xcodeproj' + + # Pods for SonarKit + + # Third party deps podspec link + pod 'EasyWSClient', :podspec => 'third-party-podspecs/EasyWSClient.podspec' + pod 'DoubleConversion', :podspec => 'third-party-podspecs/DoubleConversion.podspec' + pod 'glog', :podspec => 'third-party-podspecs/glog.podspec' + pod 'Folly', :podspec => 'third-party-podspecs/Folly.podspec' + pod 'Sonar', :podspec => '../xplat/Sonar/SonarKitCPP.podspec' + pod 'CocoaAsyncSocket' + pod 'PeerTalk', :git => 'https://github.com/rsms/peertalk' +end diff --git a/iOS/Sample/AppDelegate.h b/iOS/Sample/AppDelegate.h new file mode 100644 index 000000000..ea28a10f5 --- /dev/null +++ b/iOS/Sample/AppDelegate.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@interface AppDelegate : UIResponder +@end diff --git a/iOS/Sample/AppDelegate.mm b/iOS/Sample/AppDelegate.mm new file mode 100644 index 000000000..e6a14051b --- /dev/null +++ b/iOS/Sample/AppDelegate.mm @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "AppDelegate.h" + +#import +#import +#import +#import +#import + +#import "MainViewController.h" +#import "RootViewController.h" + +#if !FB_SONARKIT_ENABLED +#error "Sample need to be run with SonarKit enabled in order to properly interact with Sonar. SonarKit is enabled by default if its a debug build." +#endif + +@implementation AppDelegate { + UIWindow *_window; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + + SonarClient *client = [SonarClient sharedClient]; + + SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; + [SonarKitLayoutComponentKitSupport setUpWithDescriptorMapper: layoutDescriptorMapper]; + [client addPlugin: [[SonarKitLayoutPlugin alloc] initWithRootNode: application + withDescriptorMapper: layoutDescriptorMapper]]; + + [[SonarClient sharedClient] addPlugin: [[SonarKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; + [[SonarClient sharedClient] start]; + + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryBoard" bundle:nil]; + MainViewController *mainViewController = [storyboard instantiateViewControllerWithIdentifier:@"MainViewController"]; + + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController: mainViewController]; + navigationController.navigationBar.topItem.title = @"Sample"; + navigationController.navigationBar.translucent = NO; + + [_window setRootViewController: [[UINavigationController alloc] initWithRootViewController: mainViewController]]; + [_window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/Contents.json b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..63acba97d --- /dev/null +++ b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,62 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "icon_20pt@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "icon_20pt@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon_29pt@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "icon_29pt@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon_40pt@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "icon_40pt@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon_60pt@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "icon_60pt@3x.png", + "scale" : "3x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "icon_83.5@2x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_20pt@2x.png b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_20pt@2x.png new file mode 100644 index 000000000..aa46fa5e1 Binary files /dev/null and b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_20pt@2x.png differ diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_20pt@3x.png b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_20pt@3x.png new file mode 100644 index 000000000..66792a48b Binary files /dev/null and b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_20pt@3x.png differ diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_29pt@2x.png b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_29pt@2x.png new file mode 100644 index 000000000..e1d4098ef Binary files /dev/null and b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_29pt@2x.png differ diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_29pt@3x.png b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_29pt@3x.png new file mode 100644 index 000000000..d84536f44 Binary files /dev/null and b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_29pt@3x.png differ diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_40pt@2x.png b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_40pt@2x.png new file mode 100644 index 000000000..063ff984a Binary files /dev/null and b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_40pt@2x.png differ diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_40pt@3x.png b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_40pt@3x.png new file mode 100644 index 000000000..3c26d5db6 Binary files /dev/null and b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_40pt@3x.png differ diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_60pt@2x.png b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_60pt@2x.png new file mode 100644 index 000000000..3c26d5db6 Binary files /dev/null and b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_60pt@2x.png differ diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_60pt@3x.png b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_60pt@3x.png new file mode 100644 index 000000000..e4c7ae242 Binary files /dev/null and b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_60pt@3x.png differ diff --git a/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_83.5@2x.png b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_83.5@2x.png new file mode 100644 index 000000000..c96d51700 Binary files /dev/null and b/iOS/Sample/Icons.xcassets/AppIcon.appiconset/icon_83.5@2x.png differ diff --git a/iOS/Sample/Icons.xcassets/Contents.json b/iOS/Sample/Icons.xcassets/Contents.json new file mode 100644 index 000000000..da4a164c9 --- /dev/null +++ b/iOS/Sample/Icons.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iOS/Sample/Icons.xcassets/sonarpattern.imageset/Contents.json b/iOS/Sample/Icons.xcassets/sonarpattern.imageset/Contents.json new file mode 100644 index 000000000..d313dccc5 --- /dev/null +++ b/iOS/Sample/Icons.xcassets/sonarpattern.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "sonarpattern.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/iOS/Sample/Icons.xcassets/sonarpattern.imageset/sonarpattern.png b/iOS/Sample/Icons.xcassets/sonarpattern.imageset/sonarpattern.png new file mode 100644 index 000000000..12283761d Binary files /dev/null and b/iOS/Sample/Icons.xcassets/sonarpattern.imageset/sonarpattern.png differ diff --git a/iOS/Sample/Info.plist b/iOS/Sample/Info.plist new file mode 100644 index 000000000..c20d05686 --- /dev/null +++ b/iOS/Sample/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.sonar + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/iOS/Sample/MainStoryBoard.storyboard b/iOS/Sample/MainStoryBoard.storyboard new file mode 100644 index 000000000..352bad524 --- /dev/null +++ b/iOS/Sample/MainStoryBoard.storyboard @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/iOS/Sample/MainViewController.h b/iOS/Sample/MainViewController.h new file mode 100644 index 000000000..e5cfabd21 --- /dev/null +++ b/iOS/Sample/MainViewController.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@interface MainViewController : UIViewController + +@end diff --git a/iOS/Sample/MainViewController.m b/iOS/Sample/MainViewController.m new file mode 100644 index 000000000..ac31bf779 --- /dev/null +++ b/iOS/Sample/MainViewController.m @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +#import "MainViewController.h" + +#import "NetworkViewController.h" +#import "RootViewController.h" + +@interface MainViewController () + +@end + +@implementation MainViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + +- (IBAction)tappedComponentKitLayout:(UIButton *)sender { + RootViewController *rootViewController = [RootViewController new]; + + [self.navigationController pushViewController:rootViewController animated:true]; +} + +- (IBAction)tappedNetworkInspector:(UIButton *)sender { + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryBoard" bundle:nil]; + NetworkViewController *networkViewController = [storyboard instantiateViewControllerWithIdentifier:@"NetworkViewController"]; + + [self.navigationController pushViewController:networkViewController animated:true]; +} + +@end diff --git a/iOS/Sample/NetworkViewController.h b/iOS/Sample/NetworkViewController.h new file mode 100644 index 000000000..9f89e2a7f --- /dev/null +++ b/iOS/Sample/NetworkViewController.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +#import + +@interface NetworkViewController : UIViewController + +@end diff --git a/iOS/Sample/NetworkViewController.m b/iOS/Sample/NetworkViewController.m new file mode 100644 index 000000000..3696be0b1 --- /dev/null +++ b/iOS/Sample/NetworkViewController.m @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +#import "NetworkViewController.h" + +@interface NetworkViewController () + +@end + +@implementation NetworkViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.navigationItem.title = @"Network"; +} + +- (IBAction)tappedGithubLitho:(UIButton *)sender { + [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/litho/master/docs/static/logo.png"] completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { + if (error && !data) { + return; + } + NSLog(@"Got Image"); + }] resume]; +} + +- (IBAction)tappedPOSTAPI:(UIButton *)sender { + NSString *post = @"https://demo9512366.mockable.io/SonarPost"; + NSURL *url = [NSURL URLWithString:post]; + NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL: url]; + [urlRequest addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + [urlRequest addValue:@"application/json" forHTTPHeaderField:@"Accept"]; + NSDictionary *mapData = [[NSDictionary alloc] initWithObjectsAndKeys: @"Sonar", @"app", + @"Its awesome", @"remarks", + nil]; + NSError *error = nil; + NSData *postData = [NSJSONSerialization dataWithJSONObject:mapData options:0 error:&error]; + [urlRequest setHTTPBody:postData]; + [urlRequest setHTTPMethod:@"POST"]; + __weak NetworkViewController *weakSelf = self; + [[[NSURLSession sharedSession] dataTaskWithRequest:urlRequest completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { + + if (error || !data) { + UIAlertController *alertController = [weakSelf alertControllerForMessage:@"Received error in POST API response"]; + [weakSelf presentViewController:alertController animated:true completion:nil]; + return; + } + NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + NSLog(@"MSG-POST: %@", dict[@"msg"]); + + UIAlertController *alertController = [weakSelf alertControllerForMessage:@"Received response from POST API"]; + [weakSelf presentViewController:alertController animated:true completion:nil]; + + }] resume]; +} + +- (IBAction)tappedGetAPI:(UIButton *)sender { + __weak NetworkViewController *weakSelf = self; + [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://demo9512366.mockable.io/"] completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { + if (error || !data) { + UIAlertController *alertController = [weakSelf alertControllerForMessage:@"Received error in GET API response"]; + [weakSelf presentViewController:alertController animated:true completion:nil]; + return; + } + NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + NSLog(@"MSG-GET: %@", dict[@"msg"]); + UIAlertController *alertController = [weakSelf alertControllerForMessage:@"Received response from GET API"]; + [weakSelf presentViewController:alertController animated:true completion:nil]; + }] resume]; +} + + +- (UIAlertController *)alertControllerForMessage:(nonnull NSString *)msg { + UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Sonar" message:msg preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *action = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:nil]; + [controller addAction:action]; + return controller; +} + +@end diff --git a/iOS/Sample/Podfile b/iOS/Sample/Podfile new file mode 100644 index 000000000..70f73b4a3 --- /dev/null +++ b/iOS/Sample/Podfile @@ -0,0 +1,31 @@ +project 'Sample.xcodeproj' + +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' +swift_version = "4.1" + +target 'Sample' do + + pod 'EasyWSClient', :podspec => '../third-party-podspecs/EasyWSClient.podspec' + pod 'DoubleConversion', :podspec => '../third-party-podspecs/DoubleConversion.podspec' + pod 'glog', :podspec => '../third-party-podspecs/glog.podspec' + pod 'Folly', :podspec => '../third-party-podspecs/Folly.podspec' + pod 'PeerTalk', :git => 'https://github.com/rsms/peertalk' + pod 'ComponentKit', :podspec => '../third-party-podspecs/ComponentKit.podspec' + pod 'Yoga','~>1.8.1', :modular_headers => true + pod 'Sonar', :podspec => '../../xplat/Sonar/SonarKitCPP.podspec' + pod 'SonarKit', :podspec => '../SonarKit.podspec' + pod 'SonarKit/SonarKitLayoutComponentKitSupport', :podspec => '../SonarKit.podspec' + pod 'SonarKit/SKIOSNetworkPlugin', :podspec => '../SonarKit.podspec' + + post_install do |installer| + installer.pods_project.targets.each do |target| + if ['YogaKit'].include? target.name + target.build_configurations.each do |config| + config.build_settings['SWIFT_VERSION'] = swift_version + end + end + end + end + +end diff --git a/iOS/Sample/Podfile.lock b/iOS/Sample/Podfile.lock new file mode 100644 index 000000000..399025b3e --- /dev/null +++ b/iOS/Sample/Podfile.lock @@ -0,0 +1,128 @@ +PODS: + - boost-for-react-native (1.63.0) + - CocoaAsyncSocket (7.6.3) + - ComponentKit (0.21): + - Yoga + - DoubleConversion (3.0.0) + - EasyWSClient (1.0.0) + - Folly (2018.05.07.00): + - boost-for-react-native + - DoubleConversion + - glog + - glog (0.3.5) + - PeerTalk (0.0.2) + - Sonar (1.0.0): + - EasyWSClient + - Folly + - SonarKit (1.0.0): + - CocoaAsyncSocket (~> 7.6) + - Folly + - PeerTalk + - Sonar + - SonarKit/SKIOSNetworkPlugin (= 1.0.0) + - SonarKit/SonarKitLayoutComponentKitSupport (= 1.0.0) + - SonarKit/SonarKitLayoutPlugin (= 1.0.0) + - SonarKit/SonarKitNetworkPlugin (= 1.0.0) + - SonarKit/SonarKitNetworkPlugin (= 1.0.0) + - SonarKit/SKIOSNetworkPlugin (1.0.0): + - CocoaAsyncSocket (~> 7.6) + - Folly + - PeerTalk + - Sonar + - SonarKit/SonarKitNetworkPlugin + - SonarKit/SonarKitLayoutComponentKitSupport (1.0.0): + - CocoaAsyncSocket (~> 7.6) + - ComponentKit + - Folly + - PeerTalk + - Sonar + - SonarKit/SonarKitLayoutPlugin + - Yoga (= 1.8.1) + - SonarKit/SonarKitLayoutPlugin (1.0.0): + - CocoaAsyncSocket (~> 7.6) + - Folly + - PeerTalk + - Sonar + - Yoga (= 1.8.1) + - YogaKit (= 1.8.1) + - SonarKit/SonarKitNetworkPlugin (1.0.0): + - CocoaAsyncSocket (~> 7.6) + - Folly + - PeerTalk + - Sonar + - Yoga (1.8.1) + - YogaKit (1.8.1): + - Yoga (~> 1.8.1) + +DEPENDENCIES: + - ComponentKit (from `../third-party-podspecs/ComponentKit.podspec`) + - DoubleConversion (from `../third-party-podspecs/DoubleConversion.podspec`) + - EasyWSClient (from `../third-party-podspecs/EasyWSClient.podspec`) + - Folly (from `../third-party-podspecs/Folly.podspec`) + - glog (from `../third-party-podspecs/glog.podspec`) + - PeerTalk (from `https://github.com/rsms/peertalk`) + - Sonar (from `../../xplat/Sonar/SonarKitCPP.podspec`) + - SonarKit (from `../SonarKit.podspec`) + - SonarKit/SKIOSNetworkPlugin (from `../SonarKit.podspec`) + - SonarKit/SonarKitLayoutComponentKitSupport (from `../SonarKit.podspec`) + - Yoga (~> 1.8.1) + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - boost-for-react-native + - CocoaAsyncSocket + - Yoga + - YogaKit + +EXTERNAL SOURCES: + ComponentKit: + :podspec: "../third-party-podspecs/ComponentKit.podspec" + DoubleConversion: + :podspec: "../third-party-podspecs/DoubleConversion.podspec" + EasyWSClient: + :podspec: "../third-party-podspecs/EasyWSClient.podspec" + Folly: + :podspec: "../third-party-podspecs/Folly.podspec" + glog: + :podspec: "../third-party-podspecs/glog.podspec" + PeerTalk: + :git: https://github.com/rsms/peertalk + Sonar: + :podspec: "../../xplat/Sonar/SonarKitCPP.podspec" + SonarKit: + :podspec: "../SonarKit.podspec" + +CHECKOUT OPTIONS: + ComponentKit: + :commit: f801317e71f88fbb5a398cd726fc0375255f43ba + :git: https://github.com/facebook/ComponentKit.git + EasyWSClient: + :commit: 9b87dc488048900a8cd684f51ddc98143682dbc3 + :git: https://github.com/dhbaird/easywsclient.git + PeerTalk: + :commit: 588303b43efa5082d654b6f75d1b84a6ba4b5b9e + :git: https://github.com/rsms/peertalk + Sonar: + :commit: 26c298ad3401157ac2b7336218c1dde63260dc0c + :git: https://github.com/facebook/Sonar.git + SonarKit: + :commit: 26c298ad3401157ac2b7336218c1dde63260dc0c + :git: https://github.com/facebook/Sonar.git + +SPEC CHECKSUMS: + boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c + CocoaAsyncSocket: eafaa68a7e0ec99ead0a7b35015e0bf25d2c8987 + ComponentKit: 7bd0ad508946aeb68dd52ed8739ced9846ff3671 + DoubleConversion: 310ccd7cdf00175c32883664f84fe026025604df + EasyWSClient: 7ec8effe7d86f6061a47d19a55355769c9edfd2f + Folly: 2d29ed217455246ae583ff1980f9ce882af31e80 + glog: f175af2df1f453be65bd355b287a07c842927a99 + PeerTalk: f5389c286e4d477e59b73dfbf25c5c70a2464761 + Sonar: 815b6c6357c78564d9132f6389605b285a06f052 + SonarKit: 29b45073b54d7f5db13e53b7afe6fb6f36c6bea7 + Yoga: e6f1fed82138c17da5332e15e5770abf0e9cc386 + YogaKit: bb90d11e297e06abef7e0cfb20e035a6bd00cdc4 + +PODFILE CHECKSUM: cab936292346d86ef8900c8f67d3c707dc421709 + +COCOAPODS: 1.5.2 \ No newline at end of file diff --git a/iOS/Sample/RootViewController.h b/iOS/Sample/RootViewController.h new file mode 100644 index 000000000..205e50499 --- /dev/null +++ b/iOS/Sample/RootViewController.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@interface RootViewController : UIViewController + +@end diff --git a/iOS/Sample/RootViewController.mm b/iOS/Sample/RootViewController.mm new file mode 100644 index 000000000..e92ebea42 --- /dev/null +++ b/iOS/Sample/RootViewController.mm @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "RootViewController.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +@interface RootViewController () + +@property (strong, nonatomic) CKComponentHostingView *rootCKHostingView; + +@end + +@implementation RootViewController + +- (instancetype)init +{ + if (self = [super init]) { + _rootCKHostingView = [[CKComponentHostingView alloc] + initWithComponentProvider:[self class] + sizeRangeProvider: + [CKComponentFlexibleSizeRangeProvider providerWithFlexibility:CKComponentSizeRangeFlexibleHeight]]; + + [self.view addSubview:_rootCKHostingView]; + [self loadViewIfNeeded]; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.navigationItem.title = @"ComponentKit Layout"; + self.edgesForExtendedLayout = UIRectEdgeNone; +} + +- (void)viewDidLayoutSubviews +{ + [super viewDidLayoutSubviews]; + _rootCKHostingView.frame = self.view.bounds; +} + ++ (CKComponent *)componentForModel:(id)model context:(id)context { + return [CKBackgroundLayoutComponent + newWithComponent: + [CKFlexboxComponent + newWithView:{ + } + size:{} + style:{} + children: { + { + [CKButtonComponent + newWithAction:nil + options:{ + .titles = @"Purple", + .titleColors = UIColor.purpleColor, + } + ] + }, + { + [CKButtonComponent + newWithAction:nil + options:{ + .titles = @"Brown", + .titleColors = UIColor.brownColor, + } + ] + }, + { + [CKButtonComponent + newWithAction:nil + options:{ + .titles = @"Cyan", + .titleColors = UIColor.cyanColor, + } + ] + }, + }] + background: + [CKImageComponent + newWithImage:[UIImage imageNamed:@"sonarpattern"] + attributes:{} + size:{}]]; +} + +@end diff --git a/iOS/Sample/Sample.xcodeproj/project.pbxproj b/iOS/Sample/Sample.xcodeproj/project.pbxproj new file mode 100644 index 000000000..bbe9e8cf1 --- /dev/null +++ b/iOS/Sample/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,480 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 53D59DB320ABA18400207065 /* NetworkViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53D59DAA20ABA18300207065 /* NetworkViewController.m */; }; + 53D59DB420ABA18400207065 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 53D59DAB20ABA18300207065 /* AppDelegate.mm */; }; + 53D59DB520ABA18400207065 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 53D59DAD20ABA18300207065 /* MainViewController.m */; }; + 53D59DB620ABA18400207065 /* RootViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 53D59DAF20ABA18300207065 /* RootViewController.mm */; }; + 53D59DB720ABA18400207065 /* MainStoryBoard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53D59DB020ABA18400207065 /* MainStoryBoard.storyboard */; }; + 53D59DB820ABA18400207065 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53D59DB120ABA18400207065 /* Icons.xcassets */; }; + 53E0DE5420ABA0E4005682E1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 53E0DE5320ABA0E4005682E1 /* main.m */; }; + B369CEA3B6F57057FAACAE2F /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27922CF99D8E609480377096 /* libPods-Sample.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 275FDB8D19C1C8C26A47DA09 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + 27922CF99D8E609480377096 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 53D59DAA20ABA18300207065 /* NetworkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkViewController.m; sourceTree = SOURCE_ROOT; }; + 53D59DAB20ABA18300207065 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AppDelegate.mm; sourceTree = SOURCE_ROOT; }; + 53D59DAC20ABA18300207065 /* NetworkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkViewController.h; sourceTree = SOURCE_ROOT; }; + 53D59DAD20ABA18300207065 /* MainViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainViewController.m; sourceTree = SOURCE_ROOT; }; + 53D59DAE20ABA18300207065 /* RootViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RootViewController.h; sourceTree = SOURCE_ROOT; }; + 53D59DAF20ABA18300207065 /* RootViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RootViewController.mm; sourceTree = SOURCE_ROOT; }; + 53D59DB020ABA18400207065 /* MainStoryBoard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = MainStoryBoard.storyboard; sourceTree = SOURCE_ROOT; }; + 53D59DB120ABA18400207065 /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Icons.xcassets; sourceTree = SOURCE_ROOT; }; + 53D59DB220ABA18400207065 /* MainViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainViewController.h; sourceTree = SOURCE_ROOT; }; + 53D59DBA20ABA20300207065 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 53E0DE4120ABA0E3005682E1 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 53E0DE5220ABA0E4005682E1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; + 53E0DE5320ABA0E4005682E1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 7086E77977EF48ABCAE4DED7 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 53E0DE3E20ABA0E3005682E1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B369CEA3B6F57057FAACAE2F /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 53D59DB920ABA19900207065 /* Sample */ = { + isa = PBXGroup; + children = ( + 53D59DBA20ABA20300207065 /* AppDelegate.h */, + 53D59DAB20ABA18300207065 /* AppDelegate.mm */, + 53D59DB120ABA18400207065 /* Icons.xcassets */, + 53D59DB020ABA18400207065 /* MainStoryBoard.storyboard */, + 53D59DB220ABA18400207065 /* MainViewController.h */, + 53D59DAD20ABA18300207065 /* MainViewController.m */, + 53D59DAC20ABA18300207065 /* NetworkViewController.h */, + 53D59DAA20ABA18300207065 /* NetworkViewController.m */, + 53D59DAE20ABA18300207065 /* RootViewController.h */, + 53D59DAF20ABA18300207065 /* RootViewController.mm */, + 53E0DE5220ABA0E4005682E1 /* Info.plist */, + 53E0DE5320ABA0E4005682E1 /* main.m */, + ); + name = Sample; + sourceTree = ""; + }; + 53E0DE3820ABA0E3005682E1 = { + isa = PBXGroup; + children = ( + 53D59DB920ABA19900207065 /* Sample */, + 53E0DE4220ABA0E3005682E1 /* Products */, + 71F5EB9E42BEADDCC04A205D /* Pods */, + E2FCEB633911AF4886EC0881 /* Frameworks */, + ); + sourceTree = ""; + }; + 53E0DE4220ABA0E3005682E1 /* Products */ = { + isa = PBXGroup; + children = ( + 53E0DE4120ABA0E3005682E1 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 71F5EB9E42BEADDCC04A205D /* Pods */ = { + isa = PBXGroup; + children = ( + 7086E77977EF48ABCAE4DED7 /* Pods-Sample.debug.xcconfig */, + 275FDB8D19C1C8C26A47DA09 /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + E2FCEB633911AF4886EC0881 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 27922CF99D8E609480377096 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 53E0DE4020ABA0E3005682E1 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 53E0DE5720ABA0E4005682E1 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 88DDF414B12B2B557290B67B /* [CP] Check Pods Manifest.lock */, + 53E0DE3D20ABA0E3005682E1 /* Sources */, + 53E0DE3E20ABA0E3005682E1 /* Frameworks */, + 53E0DE3F20ABA0E3005682E1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 53E0DE4120ABA0E3005682E1 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 53E0DE3920ABA0E3005682E1 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0940; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 53E0DE4020ABA0E3005682E1 = { + CreatedOnToolsVersion = 9.4; + }; + }; + }; + buildConfigurationList = 53E0DE3C20ABA0E3005682E1 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 53E0DE3820ABA0E3005682E1; + productRefGroup = 53E0DE4220ABA0E3005682E1 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 53E0DE4020ABA0E3005682E1 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 53E0DE3F20ABA0E3005682E1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 53D59DB820ABA18400207065 /* Icons.xcassets in Resources */, + 53D59DB720ABA18400207065 /* MainStoryBoard.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 88DDF414B12B2B557290B67B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 53E0DE3D20ABA0E3005682E1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 53E0DE5420ABA0E4005682E1 /* main.m in Sources */, + 53D59DB320ABA18400207065 /* NetworkViewController.m in Sources */, + 53D59DB420ABA18400207065 /* AppDelegate.mm in Sources */, + 53D59DB520ABA18400207065 /* MainViewController.m in Sources */, + 53D59DB620ABA18400207065 /* RootViewController.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 53E0DE5520ABA0E4005682E1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 53E0DE5620ABA0E4005682E1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 53E0DE5820ABA0E4005682E1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7086E77977EF48ABCAE4DED7 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "FB_SONARKIT_ENABLED=1", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/CocoaAsyncSocket\"", + "\"${PODS_ROOT}/Headers/Public/ComponentKit\"", + "\"${PODS_ROOT}/Headers/Public/DoubleConversion\"", + "\"${PODS_ROOT}/Headers/Public/EasyWSClient\"", + "\"${PODS_ROOT}/Headers/Public/Folly\"", + "\"${PODS_ROOT}/Headers/Public/PeerTalk\"", + "\"${PODS_ROOT}/Headers/Public/Sonar\"/**", + "\"${PODS_ROOT}/Headers/Public/SonarKit\"", + "\"${PODS_ROOT}/Headers/Public/Yoga\"", + "\"${PODS_ROOT}/Headers/Public/boost-for-react-native\"", + "\"${PODS_ROOT}/Headers/Public/glog\"/**", + "\"${PODS_ROOT}/SonarKit\"/**", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFB_SONARKIT_ENABLED=1", + "-Wno-implicit-retain-self", + "-Wno-global-constructors", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"CocoaAsyncSocket\"", + "-l\"ComponentKit\"", + "-l\"DoubleConversion\"", + "-l\"EasyWSClient\"", + "-l\"Folly\"", + "-l\"PeerTalk\"", + "-l\"Sonar\"", + "-l\"SonarKit\"", + "-l\"Yoga\"", + "-l\"c++\"", + "-l\"glog\"", + "-l\"stdc++\"", + "-framework", + "\"CFNetwork\"", + "-framework", + "\"CoreText\"", + "-framework", + "\"Security\"", + "-framework", + "\"UIKit\"", + "-DFB_SONARKIT_ENABLED=1", + ); + PRODUCT_BUNDLE_IDENTIFIER = FB.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 53E0DE5920ABA0E4005682E1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 275FDB8D19C1C8C26A47DA09 /* Pods-Sample.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + GCC_NO_COMMON_BLOCKS = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/CocoaAsyncSocket\"", + "\"${PODS_ROOT}/Headers/Public/ComponentKit\"", + "\"${PODS_ROOT}/Headers/Public/DoubleConversion\"", + "\"${PODS_ROOT}/Headers/Public/EasyWSClient\"", + "\"${PODS_ROOT}/Headers/Public/Folly\"", + "\"${PODS_ROOT}/Headers/Public/PeerTalk\"", + "\"${PODS_ROOT}/Headers/Public/Sonar\"/**", + "\"${PODS_ROOT}/Headers/Public/SonarKit\"", + "\"${PODS_ROOT}/Headers/Public/Yoga\"", + "\"${PODS_ROOT}/Headers/Public/boost-for-react-native\"", + "\"${PODS_ROOT}/Headers/Public/glog\"/**", + "\"${PODS_ROOT}/SonarKit\"/**", + ); + INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFB_SONARKIT_ENABLED=1", + "-Wno-implicit-retain-self", + "-Wno-global-constructors", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-l\"CocoaAsyncSocket\"", + "-l\"ComponentKit\"", + "-l\"DoubleConversion\"", + "-l\"EasyWSClient\"", + "-l\"Folly\"", + "-l\"PeerTalk\"", + "-l\"Sonar\"", + "-l\"SonarKit\"", + "-l\"Yoga\"", + "-l\"c++\"", + "-l\"glog\"", + "-l\"stdc++\"", + "-framework", + "\"CFNetwork\"", + "-framework", + "\"CoreText\"", + "-framework", + "\"Security\"", + "-framework", + "\"UIKit\"", + "-DFB_SONARKIT_ENABLED=1", + ); + PRODUCT_BUNDLE_IDENTIFIER = FB.Sample; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 53E0DE3C20ABA0E3005682E1 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 53E0DE5520ABA0E4005682E1 /* Debug */, + 53E0DE5620ABA0E4005682E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 53E0DE5720ABA0E4005682E1 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 53E0DE5820ABA0E4005682E1 /* Debug */, + 53E0DE5920ABA0E4005682E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 53E0DE3920ABA0E3005682E1 /* Project object */; +} diff --git a/iOS/Sample/main.m b/iOS/Sample/main.m new file mode 100644 index 000000000..017e75452 --- /dev/null +++ b/iOS/Sample/main.m @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/iOS/SonarKit.podspec b/iOS/SonarKit.podspec new file mode 100644 index 000000000..80136339f --- /dev/null +++ b/iOS/SonarKit.podspec @@ -0,0 +1,99 @@ +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_HAVE_LIBGFLAGS=0 -DFOLLY_HAVE_LIBJEMALLOC=0 -DFOLLY_HAVE_PREADV=0 -DFOLLY_HAVE_PWRITEV=0 -DFOLLY_HAVE_TFO=0 -DFOLLY_USE_SYMBOLIZER=0' +yoga_version = '1.8.1' +yogakit_version = '1.8.1' + +Pod::Spec.new do |spec| + spec.name = 'SonarKit' + spec.version = '1.0.0' + spec.license = { :type => 'MIT' } + spec.homepage = 'https://github.com/facebook/Sonar' + spec.summary = 'Sonar iOS podspec' + spec.authors = 'Facebook' + # spec.prepare_command = 'mv src double-conversion' + spec.source = { :git => 'https://github.com/facebook/Sonar.git', + :branch=> "master" } + spec.module_name = 'SonarKit' + spec.dependency 'Folly' + spec.dependency 'Sonar' + spec.dependency 'CocoaAsyncSocket', '~> 7.6' + spec.dependency 'PeerTalk' + spec.source_files = 'iOS/FBDefines/*.{h,cpp,m,mm}', 'iOS/SonarKit/**/*.{h,cpp,m,mm}' + spec.public_header_files = 'iOS/SonarKit/CppBridge/*.{h}', + 'iOS/SonarKit/SonarClient.h', + 'iOS/SonarKit/SonarDeviceData.h', + 'iOS/SonarKit/SonarPlugin.h', + 'iOS/SonarKit/SonarResponder.h', + 'iOS/SonarKit/SonarConnection.h', + 'iOS/SonarKit/SKMacros.h' + + spec.private_header_files = 'iOS/Sample/' + spec.compiler_flags = '-DFB_SONARKIT_ENABLED=1 -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_HAVE_LIBGFLAGS=0 -DFOLLY_HAVE_LIBJEMALLOC=0 -DFOLLY_HAVE_PREADV=0 -DFOLLY_HAVE_PWRITEV=0 -DFOLLY_HAVE_TFO=0 -DFOLLY_USE_SYMBOLIZER=0' + spec.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++14", + "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)\"/** \"$(PODS_ROOT)/boost-for-react-native\" \"$(PODS_ROOT)/DoubleConversion\" \"$(PODS_ROOT)/ComponentKit\"/**" } + spec.platforms = { :ios => "8.0", :tvos => "9.2" } + + spec.subspec "SonarKitLayoutPlugin" do |ss| + ss.dependency "Yoga", yoga_version + ss.dependency 'YogaKit', yogakit_version + ss.compiler_flags = folly_compiler_flags + ss.public_header_files = 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SonarKitLayoutPlugin.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTouch.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKDescriptorMapper.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNodeDescriptor.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKInvalidation.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKNamed.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKTapListener.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKObject.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/SKHighlightOverlay.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/UIColor+SKSonarValueCoder.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKObjectHash.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKSwizzle.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/utils/SKYogaKitHelper.h' + ss.source_files = 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutPlugin/**/*.{h,cpp,m,mm}' + end + + spec.subspec "SonarKitLayoutComponentKitSupport" do |ss| + ss.dependency "Yoga", yoga_version + ss.dependency "ComponentKit" + ss.dependency "SonarKit/SonarKitLayoutPlugin" + ss.public_header_files = 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SonarKitLayoutComponentKitSupport.h', + 'iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h' + + ss.source_files = "iOS/Plugins/SonarKitLayoutPlugin/SonarKitLayoutComponentKitSupport/**/*.{h,cpp,m,mm}" + ss.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++14", + "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)\"" } + end + + spec.subspec "SonarKitNetworkPlugin" do |ss| + ss.public_header_files = 'iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SonarKitNetworkPlugin.h', + 'iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKBufferingPlugin.h', + 'iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKDispatchQueue.h', + 'iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKNetworkReporter.h' + ss.source_files = "iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/*.{h,cpp,m,mm}" + ss.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++14", + "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)\"" } + end + + spec.subspec "SonarKitNetworkPlugin" do |ss| + ss.public_header_files = 'iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SonarKitNetworkPlugin.h', + 'iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKBufferingPlugin.h', + 'iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKDispatchQueue.h', + 'iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/SKNetworkReporter.h' + ss.source_files = "iOS/Plugins/SonarKitNetworkPlugin/SonarKitNetworkPlugin/*.{h,cpp,m,mm}" + ss.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++14", + "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)\"" } + end + + spec.subspec "SKIOSNetworkPlugin" do |ss| + ss.dependency 'SonarKit/SonarKitNetworkPlugin' + ss.public_header_files = 'iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/SKIOSNetworkAdapter.h' + ss.source_files = "iOS/Plugins/SonarKitNetworkPlugin/SKIOSNetworkPlugin/**/*.{h,cpp,m,mm}" + ss.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++14", + "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)\"" } + end +end diff --git a/iOS/SonarKit.xcodeproj/project.pbxproj b/iOS/SonarKit.xcodeproj/project.pbxproj new file mode 100644 index 000000000..bbcb046f2 --- /dev/null +++ b/iOS/SonarKit.xcodeproj/project.pbxproj @@ -0,0 +1,668 @@ + + + + + archiveVersion + 1 + classes + + + objectVersion + 46 + objects + + 1DD70E29AEDF69A200000000 + + isa + PBXFileReference + name + SonarKit-Debug.xcconfig + path + ../../buck-out/gen/Libraries/SonarKit/SonarKit-Debug.xcconfig + sourceTree + SOURCE_ROOT + explicitFileType + text.xcconfig + + 1DD70E2959CE208C00000000 + + isa + PBXFileReference + name + SonarKit-Profile.xcconfig + path + ../../buck-out/gen/Libraries/SonarKit/SonarKit-Profile.xcconfig + sourceTree + SOURCE_ROOT + explicitFileType + text.xcconfig + + 1DD70E29CD64CBEE00000000 + + isa + PBXFileReference + name + SonarKit-Release.xcconfig + path + ../../buck-out/gen/Libraries/SonarKit/SonarKit-Release.xcconfig + sourceTree + SOURCE_ROOT + explicitFileType + text.xcconfig + + B401C9792F7F325000000000 + + isa + PBXGroup + name + Buck (Do Not Modify) + sourceTree + ]]> + children + + 1DD70E29AEDF69A200000000 + 1DD70E2959CE208C00000000 + 1DD70E29CD64CBEE00000000 + + + B401C979B781F65D00000000 + + isa + PBXGroup + name + Configurations + sourceTree + ]]> + children + + B401C9792F7F325000000000 + + + 1DD70E291F97291900000000 + + isa + PBXFileReference + name + libSonarKit--916269148.a + path + libSonarKit--916269148.a + sourceTree + BUILT_PRODUCTS_DIR + explicitFileType + archive.ar + + B401C979C806358400000000 + + isa + PBXGroup + name + Products + sourceTree + ]]> + children + + 1DD70E291F97291900000000 + + + 1DD70E29001F47FB00000000 + + isa + PBXFileReference + name + BUCK + path + BUCK + sourceTree + SOURCE_ROOT + explicitFileType + text.script.python + + 1DD70E295D9C4C1E00000000 + + isa + PBXFileReference + name + SonarCppBridgingConnection.h + path + SonarKit/CppBridge/SonarCppBridgingConnection.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E2955ED38AA00000000 + + isa + PBXFileReference + name + SonarCppBridgingConnection.mm + path + SonarKit/CppBridge/SonarCppBridgingConnection.mm + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.cpp.objcpp + + 1DD70E29B6F151F600000000 + + isa + PBXFileReference + name + SonarCppBridgingResponder.h + path + SonarKit/CppBridge/SonarCppBridgingResponder.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E292738EDD200000000 + + isa + PBXFileReference + name + SonarCppBridgingResponder.mm + path + SonarKit/CppBridge/SonarCppBridgingResponder.mm + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.cpp.objcpp + + 1DD70E298E830C8000000000 + + isa + PBXFileReference + name + SonarCppWrapperPlugin.h + path + SonarKit/CppBridge/SonarCppWrapperPlugin.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + B401C979DC3E2AEC00000000 + + isa + PBXGroup + name + CppBridge + path + SonarKit/CppBridge + sourceTree + SOURCE_ROOT + children + + 1DD70E295D9C4C1E00000000 + 1DD70E2955ED38AA00000000 + 1DD70E29B6F151F600000000 + 1DD70E292738EDD200000000 + 1DD70E298E830C8000000000 + + + 1DD70E29978C461B00000000 + + isa + PBXFileReference + name + SKPortForwardingCommon.h + path + SonarKit/Utilities/PortForwarding/SKPortForwardingCommon.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E290DA3557300000000 + + isa + PBXFileReference + name + SKPortForwardingServer.h + path + SonarKit/Utilities/PortForwarding/SKPortForwardingServer.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E290DA3557800000000 + + isa + PBXFileReference + name + SKPortForwardingServer.m + path + SonarKit/Utilities/PortForwarding/SKPortForwardingServer.m + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.objc + + B401C9797FE3A67E00000000 + + isa + PBXGroup + name + PortForwarding + path + SonarKit/Utilities/PortForwarding + sourceTree + SOURCE_ROOT + children + + 1DD70E29978C461B00000000 + 1DD70E290DA3557300000000 + 1DD70E290DA3557800000000 + + + B401C979BD78D6EA00000000 + + isa + PBXGroup + name + Utilities + path + SonarKit/Utilities + sourceTree + SOURCE_ROOT + children + + B401C9797FE3A67E00000000 + + + 1DD70E299D3D8CD900000000 + + isa + PBXFileReference + name + SKMacros.h + path + SonarKit/SKMacros.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E29DD8668F300000000 + + isa + PBXFileReference + name + SKUtils.h + path + SonarKit/SKUtils.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E29D346B67500000000 + + isa + PBXFileReference + name + SKUtils.mm + path + SonarKit/SKUtils.mm + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.cpp.objcpp + + 1DD70E297513814800000000 + + isa + PBXFileReference + name + SonarClient.h + path + SonarKit/SonarClient.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E292D5CA8C000000000 + + isa + PBXFileReference + name + SonarClient.mm + path + SonarKit/SonarClient.mm + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.cpp.objcpp + + 1DD70E29D967BA1B00000000 + + isa + PBXFileReference + name + SonarConnection.h + path + SonarKit/SonarConnection.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E29D016C2B000000000 + + isa + PBXFileReference + name + SonarPlugin.h + path + SonarKit/SonarPlugin.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E29C331B05900000000 + + isa + PBXFileReference + name + SonarResponder.h + path + SonarKit/SonarResponder.h + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.h + + 1DD70E2969DE008400000000 + + isa + PBXFileReference + name + SonarUtil.m + path + SonarKit/SonarUtil.m + sourceTree + SOURCE_ROOT + lastKnownFileType + sourcecode.c.objc + + B401C979EAB5339800000000 + + isa + PBXGroup + name + Sources + sourceTree + ]]> + children + + B401C979DC3E2AEC00000000 + B401C979BD78D6EA00000000 + 1DD70E299D3D8CD900000000 + 1DD70E29DD8668F300000000 + 1DD70E29D346B67500000000 + 1DD70E297513814800000000 + 1DD70E292D5CA8C000000000 + 1DD70E29D967BA1B00000000 + 1DD70E29D016C2B000000000 + 1DD70E29C331B05900000000 + 1DD70E2969DE008400000000 + + + B401C9795F1632B300000000 + + isa + PBXGroup + name + SonarKit + sourceTree + ]]> + children + + 1DD70E29001F47FB00000000 + B401C979EAB5339800000000 + + + B401C979EFB6AC4600000000 + + isa + PBXGroup + name + mainGroup + sourceTree + ]]> + children + + B401C979B781F65D00000000 + B401C979C806358400000000 + B401C9795F1632B300000000 + + + E7A30F0455ED38AA00000000 + + isa + PBXBuildFile + fileRef + 1DD70E2955ED38AA00000000 + settings + + COMPILER_FLAGS + -stdlib=libc++ -D_LIBCPP_HAS_NO_STRONG_ENUMS=1 + + + E7A30F042738EDD200000000 + + isa + PBXBuildFile + fileRef + 1DD70E292738EDD200000000 + settings + + COMPILER_FLAGS + -stdlib=libc++ -D_LIBCPP_HAS_NO_STRONG_ENUMS=1 + + + E7A30F040DA3557800000000 + + isa + PBXBuildFile + fileRef + 1DD70E290DA3557800000000 + settings + + COMPILER_FLAGS + -stdlib=libc++ -D_LIBCPP_HAS_NO_STRONG_ENUMS=1 + + + E7A30F04D346B67500000000 + + isa + PBXBuildFile + fileRef + 1DD70E29D346B67500000000 + settings + + COMPILER_FLAGS + -stdlib=libc++ -D_LIBCPP_HAS_NO_STRONG_ENUMS=1 + + + E7A30F042D5CA8C000000000 + + isa + PBXBuildFile + fileRef + 1DD70E292D5CA8C000000000 + settings + + COMPILER_FLAGS + -stdlib=libc++ -D_LIBCPP_HAS_NO_STRONG_ENUMS=1 + + + E7A30F0469DE008400000000 + + isa + PBXBuildFile + fileRef + 1DD70E2969DE008400000000 + settings + + COMPILER_FLAGS + -stdlib=libc++ -D_LIBCPP_HAS_NO_STRONG_ENUMS=1 + + + 1870857F0000000000000000 + + isa + PBXSourcesBuildPhase + files + + E7A30F0455ED38AA00000000 + E7A30F042738EDD200000000 + E7A30F040DA3557800000000 + E7A30F04D346B67500000000 + E7A30F042D5CA8C000000000 + E7A30F0469DE008400000000 + + + 4952437303EDA63300000000 + + isa + XCBuildConfiguration + name + Debug + buildSettings + + + baseConfigurationReference + 1DD70E29AEDF69A200000000 + + 4952437350C7218900000000 + + isa + XCBuildConfiguration + name + Profile + buildSettings + + + baseConfigurationReference + 1DD70E2959CE208C00000000 + + 49524373A439BFE700000000 + + isa + XCBuildConfiguration + name + Release + buildSettings + + + baseConfigurationReference + 1DD70E29CD64CBEE00000000 + + 218C37090000000000000000 + + isa + XCConfigurationList + buildConfigurations + + 4952437303EDA63300000000 + 4952437350C7218900000000 + 49524373A439BFE700000000 + + defaultConfigurationIsVisible + + + E66DC04E5F1632B300000000 + + isa + PBXNativeTarget + name + SonarKit + productName + SonarKit--916269148 + productReference + 1DD70E291F97291900000000 + productType + com.apple.product-type.library.static + dependencies + + + buildPhases + + 1870857F0000000000000000 + + buildConfigurationList + 218C37090000000000000000 + + 4952437303EDA63300000001 + + isa + XCBuildConfiguration + name + Debug + buildSettings + + + + 4952437350C7218900000001 + + isa + XCBuildConfiguration + name + Profile + buildSettings + + + + 49524373A439BFE700000001 + + isa + XCBuildConfiguration + name + Release + buildSettings + + + + 218C37090000000000000001 + + isa + XCConfigurationList + buildConfigurations + + 4952437303EDA63300000001 + 4952437350C7218900000001 + 49524373A439BFE700000001 + + defaultConfigurationIsVisible + + + 96C847935F1632B300000000 + + isa + PBXProject + mainGroup + B401C979EFB6AC4600000000 + targets + + E66DC04E5F1632B300000000 + + buildConfigurationList + 218C37090000000000000001 + compatibilityVersion + Xcode 3.2 + attributes + + LastUpgradeCheck + 9999 + + + + rootObject + 96C847935F1632B300000000 + + \ No newline at end of file diff --git a/iOS/SonarKit/CppBridge/SonarCppBridgingConnection.h b/iOS/SonarKit/CppBridge/SonarCppBridgingConnection.h new file mode 100644 index 000000000..bafbee945 --- /dev/null +++ b/iOS/SonarKit/CppBridge/SonarCppBridgingConnection.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import +#import + +/** +SonarCppBridgingConnection is a simple ObjC wrapper around SonarConnection +that forwards messages to the underlying C++ connection. This class allows +pure Objective-C plugins to send messages to the underlying connection. +*/ +@interface SonarCppBridgingConnection : NSObject +- (instancetype)initWithCppConnection:(std::shared_ptr)conn; +@end diff --git a/iOS/SonarKit/CppBridge/SonarCppBridgingConnection.mm b/iOS/SonarKit/CppBridge/SonarCppBridgingConnection.mm new file mode 100644 index 000000000..031cdbca7 --- /dev/null +++ b/iOS/SonarKit/CppBridge/SonarCppBridgingConnection.mm @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "SonarCppBridgingConnection.h" + +#import + +#import "SonarCppBridgingResponder.h" + +@implementation SonarCppBridgingConnection +{ + std::shared_ptr conn_; +} + +- (instancetype)initWithCppConnection:(std::shared_ptr)conn +{ + if (self = [super init]) { + conn_ = conn; + } + return self; +} + +#pragma mark - SonarConnection + +- (void)send:(NSString *)method withParams:(NSDictionary *)params +{ + conn_->send([method UTF8String], facebook::cxxutils::convertIdToFollyDynamic(params)); +} + +- (void)receive:(NSString *)method withBlock:(SonarReceiver)receiver +{ + const auto lambda = [receiver](const folly::dynamic &message, + std::unique_ptr responder) { + SonarCppBridgingResponder *const objCResponder = + [[SonarCppBridgingResponder alloc] initWithCppResponder:std::move(responder)]; + receiver(facebook::cxxutils::convertFollyDynamicToId(message), objCResponder); + }; + conn_->receive([method UTF8String], lambda); +} + +@end diff --git a/iOS/SonarKit/CppBridge/SonarCppBridgingResponder.h b/iOS/SonarKit/CppBridge/SonarCppBridgingResponder.h new file mode 100644 index 000000000..a9f9e6948 --- /dev/null +++ b/iOS/SonarKit/CppBridge/SonarCppBridgingResponder.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import +#import + +/** +SonarCppBridgingResponder is a simple ObjC wrapper around SonarResponder +that forwards messages to the underlying C++ responder. This class allows +pure Objective-C plugins to send messages to the underlying responder. +*/ +@interface SonarCppBridgingResponder : NSObject +- (instancetype)initWithCppResponder:(std::unique_ptr)responder; +@end diff --git a/iOS/SonarKit/CppBridge/SonarCppBridgingResponder.mm b/iOS/SonarKit/CppBridge/SonarCppBridgingResponder.mm new file mode 100644 index 000000000..0b3714fb4 --- /dev/null +++ b/iOS/SonarKit/CppBridge/SonarCppBridgingResponder.mm @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "SonarCppBridgingResponder.h" + +#import + +@implementation SonarCppBridgingResponder { + std::unique_ptr responder_; +} + +- (instancetype)initWithCppResponder:(std::unique_ptr)responder +{ + if (!responder) { + return nil; + } + + if (self = [super init]) { + responder_ = std::move(responder); + } + + return self; +} + +#pragma mark - SonarResponder + +- (void)success:(NSDictionary *)response { responder_->success(facebook::cxxutils::convertIdToFollyDynamic(response)); } + +- (void)error:(NSDictionary *)response { responder_->error(facebook::cxxutils::convertIdToFollyDynamic(response)); } + +@end diff --git a/iOS/SonarKit/CppBridge/SonarCppWrapperPlugin.h b/iOS/SonarKit/CppBridge/SonarCppWrapperPlugin.h new file mode 100644 index 000000000..bf5db01fc --- /dev/null +++ b/iOS/SonarKit/CppBridge/SonarCppWrapperPlugin.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#ifndef __OBJC__ +#error This header can only be included in .mm (ObjC++) files +#endif + +#import +#import +#import + +namespace facebook { +namespace sonar { + +using ObjCPlugin = NSObject *; + +/** +SonarCppWrapperPlugin is a simple C++ wrapper around Objective-C Sonar plugins +that can be passed to SonarClient. This class allows developers to write pure +Objective-C plugins if they want. +*/ +class SonarCppWrapperPlugin final : public facebook::sonar::SonarPlugin { +public: + // Under ARC copying objCPlugin *does* increment its retain count + SonarCppWrapperPlugin(ObjCPlugin objCPlugin) : _objCPlugin(objCPlugin) {} + + std::string identifier() const override { return [[_objCPlugin identifier] UTF8String]; } + + void didConnect(std::shared_ptr conn) override + { + SonarCppBridgingConnection *const bridgingConn = [[SonarCppBridgingConnection alloc] initWithCppConnection:conn]; + [_objCPlugin didConnect:bridgingConn]; + } + + void didDisconnect() override { [_objCPlugin didDisconnect]; } + + ObjCPlugin getObjCPlugin() { return _objCPlugin; } + +private: + ObjCPlugin _objCPlugin; +}; + +} // namespace sonar +} // namespace facebook diff --git a/iOS/SonarKit/FBDefines/FBMacros.h b/iOS/SonarKit/FBDefines/FBMacros.h new file mode 100644 index 000000000..68d86b082 --- /dev/null +++ b/iOS/SonarKit/FBDefines/FBMacros.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#ifndef FB_SK_MACROS_H + #define FB_SK_MACROS_H + + #define FB_LINK_REQUIRE_(NAME, UNIQUE) + #define FB_LINKABLE(NAME) + #define FB_LINK_REQUIRE(NAME) + #define FB_LINK_REQUIRE_EXT(NAME) + +#endif diff --git a/iOS/SonarKit/SKMacros.h b/iOS/SonarKit/SKMacros.h new file mode 100644 index 000000000..9b21bf1ea --- /dev/null +++ b/iOS/SonarKit/SKMacros.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + + #ifndef SKMACROS_H + #define SKMACROS_H + + #import + + #ifdef __cplusplus + # define SK_EXTERN_C_BEGIN extern "C" { + # define SK_EXTERN_C_END } + # define SK_EXTERN_C extern "C" + #else + # define SK_EXTERN_C_BEGIN + # define SK_EXTERN_C_END + # define SK_EXTERN_C extern + #endif + + #define SKLog(...) NSLog(__VA_ARGS__) + #define SKTrace(...) /*NSLog(__VA_ARGS__)*/ + +#endif diff --git a/iOS/SonarKit/SonarClient.h b/iOS/SonarKit/SonarClient.h new file mode 100644 index 000000000..a3fc4b9f5 --- /dev/null +++ b/iOS/SonarKit/SonarClient.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import "SonarPlugin.h" + +/** +Represents a connection between the Sonar desktop och client side. Manages the lifecycle of attached +plugin instances. +*/ +@interface SonarClient : NSObject + +/** +The shared singleton SonarClient instance. It is an error to call this on non-debug builds to avoid leaking data. +*/ ++ (instancetype)sharedClient; + +/** +Register a plugin with the client. +*/ +- (void)addPlugin:(NSObject *)plugin; + +/** +Unregister a plugin with the client. +*/ +- (void)removePlugin:(NSObject *)plugin; + +/** +Retrieve the plugin with a given identifier which was previously registered with this client. +*/ +- (NSObject *)pluginWithIdentifier:(NSString *)identifier; + +/** +Establish a connection to the Sonar desktop. +*/ +- (void)start; + +/** +Stop the connection to the Sonar desktop. +*/ +- (void)stop; + +// initializers are disabled. You must use `+[SonarClient sharedClient]` instance. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end diff --git a/iOS/SonarKit/SonarClient.mm b/iOS/SonarKit/SonarClient.mm new file mode 100644 index 000000000..8f8834be3 --- /dev/null +++ b/iOS/SonarKit/SonarClient.mm @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#if FB_SONARKIT_ENABLED + +#import "SonarClient.h" +#import "SonarCppWrapperPlugin.h" +#import +#include +#include +#import + +#if !TARGET_OS_SIMULATOR +#import "SKPortForwardingServer.h" +#endif + +using WrapperPlugin = facebook::sonar::SonarCppWrapperPlugin; + +@implementation SonarClient { + facebook::sonar::SonarClient *_cppClient; + folly::ScopedEventBaseThread eventBaseThread; +#if !TARGET_OS_SIMULATOR + SKPortForwardingServer *_server; +#endif +} + ++ (instancetype)sharedClient +{ + static SonarClient *sharedClient = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedClient = [[self alloc] init]; + }); + return sharedClient; +} + +- (instancetype)init +{ + if (self = [super init]) { + UIDevice *device = [UIDevice currentDevice]; + NSString *deviceName = [device name]; + NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey]; + NSString *deviceId = [[device identifierForVendor]UUIDString]; + NSString *appId = appName; + NSString *privateAppDirectory = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0]; + +#if TARGET_OS_SIMULATOR + deviceName = [NSString stringWithFormat:@"%@ %@", [[UIDevice currentDevice] model], @"Simulator"]; +#endif + + facebook::sonar::SonarClient::init({ + { + "localhost", + "iOS", + [deviceName UTF8String], + [deviceId UTF8String], + [appName UTF8String], + [appId UTF8String], + [privateAppDirectory UTF8String], + }, + eventBaseThread.getEventBase(), + }); + _cppClient = facebook::sonar::SonarClient::instance(); + } + return self; +} + +- (void)refreshPlugins +{ + _cppClient->refreshPlugins(); +} + +- (void)addPlugin:(NSObject *)plugin +{ + _cppClient->addPlugin(std::make_shared(plugin)); +} + +- (void)removePlugin:(NSObject *)plugin +{ + _cppClient->removePlugin(std::make_shared(plugin)); +} + +- (NSObject *)pluginWithIdentifier:(NSString *)identifier +{ + auto cppPlugin = _cppClient->getPlugin([identifier UTF8String]); + if (auto wrapper = dynamic_cast(cppPlugin.get())) { + return wrapper->getObjCPlugin(); + } + return nil; +} + +- (void)start; +{ +#if !TARGET_OS_SIMULATOR + _server = [SKPortForwardingServer new]; + [_server forwardConnectionsFromPort:8088]; + [_server listenForMultiplexingChannelOnPort:8078]; +#endif + _cppClient->start(); +} + +- (void)stop +{ + _cppClient->stop(); +#if !TARGET_OS_SIMULATOR + [_server close]; + _server = nil; +#endif +} + +@end + +#endif diff --git a/iOS/SonarKit/SonarConnection.h b/iOS/SonarKit/SonarConnection.h new file mode 100644 index 000000000..ddf009d1e --- /dev/null +++ b/iOS/SonarKit/SonarConnection.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@protocol SonarResponder; +@protocol SonarWebSocket; + +typedef void (^SonarReceiver)(NSDictionary*, id); + +/** +Represents a connection between the Desktop and mobile plugins with corresponding identifiers. +*/ +@protocol SonarConnection + +/** +Invoke a method on the Sonar desktop plugin with with a matching identifier. +*/ +- (void)send:(NSString *)method withParams:(NSDictionary *)params; + +/** +Register a receiver to be notified of incoming calls of the given method from the Sonar desktop +plugin with a matching identifier. +*/ +- (void)receive:(NSString *)method withBlock:(SonarReceiver)receiver; + +@end diff --git a/iOS/SonarKit/SonarPlugin.h b/iOS/SonarKit/SonarPlugin.h new file mode 100644 index 000000000..b82092f41 --- /dev/null +++ b/iOS/SonarKit/SonarPlugin.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ + +#import +#import "SKMacros.h" + +SK_EXTERN_C_BEGIN +void SonarPerformBlockOnMainThread(void(^block)()); +SK_EXTERN_C_END + +@protocol SonarConnection; + +@protocol SonarPlugin + +/** +The plugin's identifier. This should map to a javascript plugin with the same identifier to ensure +messages are sent correctly. +*/ +- (NSString *)identifier; + +/** +Called when a connection has been established between this plugin and the corresponding plugin on +the Sonar desktop app. The provided connection can be used to register method receivers as well +as send messages back to the desktop app. +*/ +- (void)didConnect:(id)connection; + +/** +Called when a plugin has been disconnected and the SonarConnection provided in didConnect is no +longer valid to use. +*/ +- (void)didDisconnect; + +@end diff --git a/iOS/SonarKit/SonarResponder.h b/iOS/SonarKit/SonarResponder.h new file mode 100644 index 000000000..eeea2a0cd --- /dev/null +++ b/iOS/SonarKit/SonarResponder.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +/** +Acts as a hook for providing return values to remote called from Sonar desktop plugins. +*/ +@protocol SonarResponder + +/** +Respond with a successful return value. +*/ +- (void)success:(NSDictionary *)response; + +/** +Respond with an error. +*/ +- (void)error:(NSDictionary *)response; + +@end diff --git a/iOS/SonarKit/SonarUtil.m b/iOS/SonarKit/SonarUtil.m new file mode 100644 index 000000000..73234e41a --- /dev/null +++ b/iOS/SonarKit/SonarUtil.m @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import +#import "SonarPlugin.h" + +void SonarPerformBlockOnMainThread(void(^block)()) +{ + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_async(dispatch_get_main_queue(), block); + } +} diff --git a/iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingCommon.h b/iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingCommon.h new file mode 100644 index 000000000..13630dc81 --- /dev/null +++ b/iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingCommon.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +enum { + SKPortForwardingFrameTypeOpenPipe = 201, + SKPortForwardingFrameTypeWriteToPipe = 202, + SKPortForwardingFrameTypeClosePipe = 203, +}; + +static dispatch_data_t NSDataToGCDData(NSData *data) { + __block NSData *retainedData = data; + return dispatch_data_create(data.bytes, data.length, nil, ^{ + retainedData = nil; + }); +} diff --git a/iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingServer.h b/iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingServer.h new file mode 100644 index 000000000..72d4514a7 --- /dev/null +++ b/iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingServer.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +@interface SKPortForwardingServer : NSObject + +- (instancetype)init; + +- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port; +- (void)forwardConnectionsFromPort:(NSUInteger)port; +- (void)close; + +@end diff --git a/iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingServer.m b/iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingServer.m new file mode 100644 index 000000000..7bb61075a --- /dev/null +++ b/iOS/SonarKit/Utilities/PortForwarding/SKPortForwardingServer.m @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "SKPortForwardingServer.h" + +#import + +#import +#import + +#import "SKMacros.h" +#import "SKPortForwardingCommon.h" + +@interface SKPortForwardingServer () + +@property (nonatomic, weak) PTChannel *serverChannel; +@property (nonatomic, weak) PTChannel *peerChannel; + +@property (nonatomic, strong) GCDAsyncSocket *serverSocket; +@property (nonatomic, strong) NSMutableDictionary *clientSockets; +@property (nonatomic, assign) UInt32 lastClientSocketTag; +@property (nonatomic, strong) dispatch_queue_t socketQueue; +@property (nonatomic, strong) PTProtocol *protocol; + +@end + +@implementation SKPortForwardingServer + +- (instancetype)init +{ + if (self = [super init]) { + _socketQueue = dispatch_queue_create("SKPortForwardingServer", DISPATCH_QUEUE_SERIAL); + _lastClientSocketTag = 0; + _clientSockets = [NSMutableDictionary dictionary]; + _protocol = [[PTProtocol alloc] initWithDispatchQueue:_socketQueue]; + } + return self; +} + +- (void)dealloc +{ + [self close]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)forwardConnectionsFromPort:(NSUInteger)port +{ + [self _forwardConnectionsFromPort:port reportError:YES]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + [self _forwardConnectionsFromPort:port reportError:NO]; + }]; +} + +- (void)_forwardConnectionsFromPort:(NSUInteger)port reportError:(BOOL)shouldReportError +{ + GCDAsyncSocket *serverSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:_socketQueue]; + NSError *listenError; + if ([serverSocket acceptOnPort:port error:&listenError]) { + self.serverSocket = serverSocket; + } else { + if (shouldReportError) { + SKLog(@"Failed to listen: %@", listenError); + } + } +} + +- (void)listenForMultiplexingChannelOnPort:(NSUInteger)port +{ + [self _listenForMultiplexingChannelOnPort:port reportError:YES]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { + [self _listenForMultiplexingChannelOnPort:port reportError:NO]; + }]; +} + +- (void)_listenForMultiplexingChannelOnPort:(NSUInteger)port reportError:(BOOL)shouldReportError +{ + PTChannel *channel = [[PTChannel alloc] initWithProtocol:_protocol delegate:self]; + [channel listenOnPort:port IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { + if (error) { + if (shouldReportError) { + SKLog(@"Failed to listen on 127.0.0.1:%lu: %@", (unsigned long)port, error); + } + } else { + SKTrace(@"Listening on 127.0.0.1:%lu", (unsigned long)port); + self.serverChannel = channel; + } + }]; +} + +- (void)close +{ + if (self.serverChannel) { + [self.serverChannel close]; + self.serverChannel = nil; + } + [self.serverSocket disconnect]; +} + +#pragma mark - PTChannelDelegate + +- (void)ioFrameChannel:(PTChannel *)channel didAcceptConnection:(PTChannel *)otherChannel fromAddress:(PTAddress *)address { + // Cancel any other connection. We are FIFO, so the last connection + // established will cancel any previous connection and "take its place". + if (self.peerChannel) { + [self.peerChannel cancel]; + } + + // Weak pointer to current connection. Connection objects live by themselves + // (owned by its parent dispatch queue) until they are closed. + self.peerChannel = otherChannel; + self.peerChannel.userInfo = address; + SKTrace(@"Connected to %@", address); +} + +- (void)ioFrameChannel:(PTChannel *)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData *)payload { + //NSLog(@"didReceiveFrameOfType: %u, %u, %@", type, tag, payload); + if (type == SKPortForwardingFrameTypeWriteToPipe) { + GCDAsyncSocket *sock = self.clientSockets[@(tag)]; + [sock writeData:[NSData dataWithBytes:payload.data length:payload.length] withTimeout:-1 tag:0]; + SKTrace(@"channel -> socket (%d), %zu bytes", tag, payload.length); + } + + if (type == SKPortForwardingFrameTypeClosePipe) { + GCDAsyncSocket *sock = self.clientSockets[@(tag)]; + [sock disconnectAfterWriting]; + } +} + +- (void)ioFrameChannel:(PTChannel *)channel didEndWithError:(NSError *)error { + for (GCDAsyncSocket *sock in [_clientSockets objectEnumerator]) { + [sock setDelegate:nil]; + [sock disconnect]; + } + [self.clientSockets removeAllObjects]; + SKTrace(@"Disconnected from %@, error = %@", channel.userInfo, error); +} + + +#pragma mark - GCDAsyncSocketDelegate + +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket +{ + dispatch_block_t block = ^() { + if (!self.peerChannel) { + [newSocket setDelegate:nil]; + [newSocket disconnect]; + } + + UInt32 tag = ++self->_lastClientSocketTag; + newSocket.userData = @(tag); + newSocket.delegate = self; + self.clientSockets[@(tag)] = newSocket; + [self.peerChannel sendFrameOfType:SKPortForwardingFrameTypeOpenPipe tag:self->_lastClientSocketTag withPayload:nil callback:^(NSError *error) { + SKTrace(@"open socket (%d), error = %@", (unsigned int)tag, error); + [newSocket readDataWithTimeout:-1 tag:0]; + }]; + }; + + if (_peerChannel) { + block(); + } else { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), _socketQueue, block); + } +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)_ +{ + UInt32 tag = [[sock userData] unsignedIntValue]; + SKTrace(@"Incoming data on socket (%d) - %lu bytes", (unsigned int)tag, (unsigned long)data.length); + [_peerChannel sendFrameOfType:SKPortForwardingFrameTypeWriteToPipe tag:tag withPayload:NSDataToGCDData(data) callback:^(NSError *error) { + SKTrace(@"socket (%d) -> channel %lu bytes, error = %@", (unsigned int)tag, (unsigned long)data.length, error); + [sock readDataWithTimeout:-1 tag:_]; + }]; +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err +{ + UInt32 tag = [sock.userData unsignedIntValue]; + [_clientSockets removeObjectForKey:@(tag)]; + [_peerChannel sendFrameOfType:SKPortForwardingFrameTypeClosePipe tag:tag withPayload:nil callback:^(NSError *error) { + SKTrace(@"socket (%d) disconnected, err = %@, peer error = %@", (unsigned int)tag, err, error); + }]; +} + + +@end diff --git a/iOS/SonarKitTestUtils/BlockBasedSonarPlugin.h b/iOS/SonarKitTestUtils/BlockBasedSonarPlugin.h new file mode 100644 index 000000000..1810560a1 --- /dev/null +++ b/iOS/SonarKitTestUtils/BlockBasedSonarPlugin.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import + +@protocol SonarConnection; + +typedef void (^ConnectBlock)(id); +typedef void (^DisconnectBlock)(); + +@interface BlockBasedSonarPlugin : NSObject + +- (instancetype)initIdentifier:(NSString *)identifier connect:(ConnectBlock)connect disconnect:(DisconnectBlock)disconnect; + +@end diff --git a/iOS/SonarKitTestUtils/BlockBasedSonarPlugin.m b/iOS/SonarKitTestUtils/BlockBasedSonarPlugin.m new file mode 100644 index 000000000..3de673ef0 --- /dev/null +++ b/iOS/SonarKitTestUtils/BlockBasedSonarPlugin.m @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "BlockBasedSonarPlugin.h" + +@implementation BlockBasedSonarPlugin +{ + NSString *_identifier; + ConnectBlock _connect; + DisconnectBlock _disconnect; +} + +- (instancetype)initIdentifier:(NSString *)identifier connect:(ConnectBlock)connect disconnect:(DisconnectBlock)disconnect +{ + if (self = [super init]) { + _identifier = identifier; + _connect = connect; + _disconnect = disconnect; + } + return self; +} + +- (NSString *)identifier +{ + return _identifier; +} + +- (void)didConnect:(id)connection +{ + if (_connect) { + _connect(connection); + } +} + +- (void)didDisconnect +{ + if (_connect) { + _disconnect(); + } +} + +@end diff --git a/iOS/SonarKitTestUtils/SonarClient+Testing.h b/iOS/SonarKitTestUtils/SonarClient+Testing.h new file mode 100644 index 000000000..b2314e880 --- /dev/null +++ b/iOS/SonarKitTestUtils/SonarClient+Testing.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#ifndef __cplusplus +#error This header can only be included in .mm (ObjC++) files +#endif + +#import + +#import +#import + +@interface SonarClient (Testing) + +- (instancetype)initWithCppClient:(facebook::sonar::SonarClient *)cppClient; + +@end diff --git a/iOS/SonarKitTestUtils/SonarConnectionMock.h b/iOS/SonarKitTestUtils/SonarConnectionMock.h new file mode 100644 index 000000000..750a6a7c0 --- /dev/null +++ b/iOS/SonarKitTestUtils/SonarConnectionMock.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import + +@interface SonarConnectionMock : NSObject + +@property (nonatomic, assign, getter=isConnected) BOOL connected; +@property (nonatomic, readonly) NSDictionary *receivers; +@property (nonatomic, readonly) NSDictionary *> *sent; + +@end diff --git a/iOS/SonarKitTestUtils/SonarConnectionMock.m b/iOS/SonarKitTestUtils/SonarConnectionMock.m new file mode 100644 index 000000000..64f8c2eba --- /dev/null +++ b/iOS/SonarKitTestUtils/SonarConnectionMock.m @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "SonarConnectionMock.h" + +@implementation SonarConnectionMock + +- (instancetype)init +{ + if (self = [super init]) { + _connected = YES; + _receivers = @{}; + _sent = @{}; + } + return self; +} + + +- (void)send:(NSString *)method withParams:(NSDictionary *)params +{ + if (_connected) { + NSMutableDictionary *newSent = [NSMutableDictionary new]; + [newSent addEntriesFromDictionary:_sent]; + if (newSent[method]) { + newSent[method] = [_sent[method] arrayByAddingObject:params]; + } else { + newSent[method] = @[params]; + } + _sent = newSent; + } +} + +- (void)receive:(NSString *)method withBlock:(SonarReceiver)receiver +{ + if (_connected) { + NSMutableDictionary *newReceivers = [NSMutableDictionary new]; + [newReceivers addEntriesFromDictionary:_receivers]; + newReceivers[method] = receiver; + _receivers = newReceivers; + } +} + +@end diff --git a/iOS/SonarKitTestUtils/SonarResponderMock.h b/iOS/SonarKitTestUtils/SonarResponderMock.h new file mode 100644 index 000000000..d3e50fc91 --- /dev/null +++ b/iOS/SonarKitTestUtils/SonarResponderMock.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#import + +@interface SonarResponderMock : NSObject + +@property (nonatomic, readonly) NSArray *successes; +@property (nonatomic, readonly) NSArray *errors; + +@end diff --git a/iOS/SonarKitTestUtils/SonarResponderMock.m b/iOS/SonarKitTestUtils/SonarResponderMock.m new file mode 100644 index 000000000..bc2c5769d --- /dev/null +++ b/iOS/SonarKitTestUtils/SonarResponderMock.m @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import "SonarResponderMock.h" + +@implementation SonarResponderMock + +- (instancetype)init +{ + if (self = [super init]) { + _successes = @[]; + _errors = @[]; + } + return self; +} + +- (void)success:(NSDictionary *)response +{ + _successes = [_successes arrayByAddingObject:response]; +} + +- (void)error:(NSDictionary *)response +{ + _errors = [_errors arrayByAddingObject:response]; +} + +@end diff --git a/iOS/SonarKitTests/SonarCppBridgingTests.mm b/iOS/SonarKitTests/SonarCppBridgingTests.mm new file mode 100644 index 000000000..1d28998d4 --- /dev/null +++ b/iOS/SonarKitTests/SonarCppBridgingTests.mm @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#import + +#if FB_SONARKIT_ENABLED + +#import +#import + +using facebook::sonar::SonarCppWrapperPlugin; + +@interface DummyPlugin : NSObject +@end + +@implementation DummyPlugin +- (NSString *)identifier { return @"Dummy"; } +- (void)didConnect:(id)connection {} +- (void)didDisconnect {} +@end + +@interface SonarCppBridgingTests : XCTestCase +@end + +@implementation SonarCppBridgingTests + +- (void)testCppWrapperRetainsObjCPlugin { + NSObject *dummyPlugin = [DummyPlugin new]; + auto retainCountBefore = CFGetRetainCount((void *)dummyPlugin); + SonarCppWrapperPlugin wrapperPlugin(dummyPlugin); + auto retainCountAfter = CFGetRetainCount((void *)dummyPlugin); + XCTAssertTrue(retainCountAfter > retainCountBefore); +} + +@end + +#endif diff --git a/iOS/scripts/ios-configure-glog.sh b/iOS/scripts/ios-configure-glog.sh new file mode 100644 index 000000000..e6a15febb --- /dev/null +++ b/iOS/scripts/ios-configure-glog.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -e + +PLATFORM_NAME="${PLATFORM_NAME:-iphoneos}" +CURRENT_ARCH="${CURRENT_ARCH:-armv7}" + +export CC="$(xcrun -find -sdk $PLATFORM_NAME cc) -arch $CURRENT_ARCH -isysroot $(xcrun -sdk $PLATFORM_NAME --show-sdk-path)" +export CXX="$CC" + +# Remove automake symlink if it exists +if [ -h "test-driver" ]; then + rm test-driver +fi + +./configure --host arm-apple-darwin + +# Fix build for tvOS +cat << EOF >> src/config.h + +/* Add in so we have Apple Target Conditionals */ +#ifdef __APPLE__ +#include +#include +#endif + +/* Special configuration for AppleTVOS */ +#if TARGET_OS_TV +#undef HAVE_SYSCALL_H +#undef HAVE_SYS_SYSCALL_H +#undef OS_MACOSX +#endif + +/* Special configuration for ucontext */ +#undef HAVE_UCONTEXT_H +#undef PC_FROM_UCONTEXT +#if defined(__x86_64__) +#define PC_FROM_UCONTEXT uc_mcontext->__ss.__rip +#elif defined(__i386__) +#define PC_FROM_UCONTEXT uc_mcontext->__ss.__eip +#endif +EOF diff --git a/iOS/third-party-podspecs/ComponentKit.podspec b/iOS/third-party-podspecs/ComponentKit.podspec new file mode 100644 index 000000000..d6e54476c --- /dev/null +++ b/iOS/third-party-podspecs/ComponentKit.podspec @@ -0,0 +1,21 @@ +Pod::Spec.new do |s| + s.name = 'ComponentKit' + s.version = '0.21' + s.license = 'BSD' + s.summary = 'A React-inspired view framework for iOS' + s.homepage = 'https://componentkit.org' + s.social_media_url = 'https://twitter.com/componentkit' + s.authors = 'adamjernst@fb.com' + s.source = { :git => 'https://github.com/facebook/ComponentKit.git'} + s.ios.deployment_target = '8.1' + s.requires_arc = true + + s.source_files = 'ComponentKit/**/*', 'ComponentTextKit/**/*' + s.frameworks = 'UIKit', 'CoreText' + s.library = 'c++' + s.xcconfig = { + 'CLANG_CXX_LANGUAGE_STANDARD' => 'gnu++14', + 'CLANG_CXX_LIBRARY' => 'libc++', + } + s.dependency 'Yoga' +end diff --git a/iOS/third-party-podspecs/DoubleConversion.podspec b/iOS/third-party-podspecs/DoubleConversion.podspec new file mode 100755 index 000000000..91f4f819b --- /dev/null +++ b/iOS/third-party-podspecs/DoubleConversion.podspec @@ -0,0 +1,18 @@ +Pod::Spec.new do |spec| + spec.name = 'DoubleConversion' + spec.version = '3.0.0' + spec.license = { :type => 'MIT' } + spec.homepage = 'https://github.com/google/double-conversion' + spec.summary = 'Efficient binary-decimal and decimal-binary conversion routines for IEEE doubles' + spec.authors = 'Google' + # spec.prepare_command = 'mv src double-conversion' + spec.source = { :git => 'https://github.com/google/double-conversion.git', + :tag => "v#{spec.version}" } + spec.module_name = 'DoubleConversion' + spec.source_files = 'double-conversion/*.{h,cc}' + spec.libraries = "stdc++" + spec.compiler_flags = '-std=c++1y' + # Pinning to the same version as React.podspec. + spec.platforms = { :ios => "8.0", :tvos => "9.2" } + +end diff --git a/iOS/third-party-podspecs/EasyWSClient.podspec b/iOS/third-party-podspecs/EasyWSClient.podspec new file mode 100644 index 000000000..0b3edeba9 --- /dev/null +++ b/iOS/third-party-podspecs/EasyWSClient.podspec @@ -0,0 +1,17 @@ +Pod::Spec.new do |spec| + spec.name = 'EasyWSClient' + spec.version = '1.0.0' + spec.license = { :type => 'MIT' } + spec.homepage = 'https://github.com/google/double-conversion' + spec.summary = 'Easywsclient is an easy and powerful WebSocket client to get your C++ code connected to a web stack right away.' + spec.authors = 'David Baird' + # spec.prepare_command = 'mv src double-conversion' + spec.source = { :git => 'https://github.com/dhbaird/easywsclient.git', :branch => 'master'} + spec.module_name = 'EasyWSClient' + spec.source_files = '*.{hpp,cpp}' + spec.libraries = "stdc++" + spec.compiler_flags = '-std=c++1y' + # Pinning to the same version as React.podspec. + spec.platforms = { :ios => "8.0", :tvos => "9.2" } + +end diff --git a/iOS/third-party-podspecs/Folly.podspec b/iOS/third-party-podspecs/Folly.podspec new file mode 100755 index 000000000..4732038d7 --- /dev/null +++ b/iOS/third-party-podspecs/Folly.podspec @@ -0,0 +1,95 @@ +Pod::Spec.new do |spec| + spec.name = 'Folly' + spec.version = '2018.05.07.00' + spec.license = { :type => 'Apache License, Version 2.0' } + spec.homepage = 'https://github.com/facebook/folly' + spec.summary = 'An open-source C++ library developed and used at Facebook.' + spec.authors = 'Facebook' + spec.source = { :git => 'https://github.com/facebook/folly.git', + :tag => "v#{spec.version}" } + spec.module_name = 'folly' + spec.dependency 'boost-for-react-native' + spec.dependency 'DoubleConversion' + spec.dependency 'glog' + spec.compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_HAVE_LIBGFLAGS=0 -DFOLLY_HAVE_LIBJEMALLOC=0 -DFOLLY_HAVE_PREADV=0 -DFOLLY_HAVE_PWRITEV=0 -DFOLLY_HAVE_TFO=0 -DFOLLY_USE_SYMBOLIZER=0 -frtti + -fexceptions + -std=c++14 + -Wno-error + -Wno-unused-local-typedefs + -Wno-unused-variable + -Wno-sign-compare + -Wno-comment + -Wno-return-type + -Wno-global-constructors' + spec.source_files = 'folly/Executor.cpp', + 'folly/memory/detail/MallocImpl.cpp', + 'folly/String.cpp', + 'folly/*.cpp', + 'folly/ScopeGuard.h', + 'folly/lang/ColdClass.cpp', + 'folly/lang/Assume.h', + 'folly/lang/Assume.cpp' + + # workaround for https://github.com/facebook/react-native/issues/14326 + spec.preserve_paths = 'folly/*.h', + 'folly/portability/*.h', + 'folly/lang/*.h', + 'folly/functional/*.h', + 'folly/detail/*.h', + 'folly/hash/*.h', + 'folly/memory/*.h', + 'folly/**/*.h' + + spec.header_mappings_dir = 'folly' + spec.header_dir = 'folly' + spec.libraries = "stdc++" + spec.private_header_files = 'folly/portability/Stdlib.h', + 'folly/portability/Malloc.h', + 'folly/portability/Stdlib.h', + 'folly/portability/Stdio.h', + 'folly/portability/PThread.h' + spec.public_header_files = 'folly/portability/Config.h', + 'folly/Executor.h', + 'folly/Function.h', + 'folly/Utility.h', + 'folly/Portability.h', + 'folly/Traits.h', + 'folly/functional/Invoke.h', + 'folly/CPortability.h', + 'folly/dynamic.h', + 'folly/json_pointer.h', + 'folly/Expected.h', + 'folly/Preprocessor.h', + 'folly/Optional.h', + 'folly/Unit.h', + 'folly/Utility.h', + 'folly/lang/ColdClass.h', + 'folly/CppAttributes.h', + 'folly/json.h', + 'folly/Range.h', + 'folly/hash/SpookyHashV2.h', + 'folly/lang/Exception.h', + 'folly/portability/Constexpr.h', + 'folly/CpuId.h', + 'folly/Likely.h', + 'folly/detail/RangeCommon.h', + 'folly/detail/RangeSse42.h', + 'folly/portability/String.h', + 'folly/dynamic-inl.h', + 'folly/Conv.h', + 'folly/Demangle.h', + 'folly/FBString.h', + 'folly/hash/Hash.h', + 'folly/memory/Malloc.h', + 'folly/**/*.h', + 'folly/memory/detail/MallocImpl.h', + 'folly/String.h', + 'folly/*.h' + + spec.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++11", + "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)\" \"$(PODS_ROOT)/boost-for-react-native\" \"$(PODS_ROOT)/DoubleConversion\"" } + + # Pinning to the same version as React.podspec. + spec.platforms = { :ios => "8.0", :tvos => "10.0" } +end diff --git a/iOS/third-party-podspecs/glog.podspec b/iOS/third-party-podspecs/glog.podspec new file mode 100755 index 000000000..a68659fa0 --- /dev/null +++ b/iOS/third-party-podspecs/glog.podspec @@ -0,0 +1,41 @@ +Pod::Spec.new do |spec| + spec.name = 'glog' + spec.version = '0.3.5' + spec.license = { :type => 'Google', :file => 'COPYING' } + spec.homepage = 'https://github.com/google/glog' + spec.summary = 'Google logging module' + spec.authors = 'Google' + spec.prepare_command = File.read("../scripts/ios-configure-glog.sh") + spec.source = { :git => 'https://github.com/google/glog.git', + :tag => "v#{spec.version}" } + spec.module_name = 'glog' + spec.header_dir = 'glog' + spec.source_files = 'src/logging.cc', + 'src/utilities.h', + 'src/utilities.cc', + 'src/glog/*.h', + 'src/glog/*.cc', + 'src/base/mutex.h', + 'src/base/mutex.cc', + 'src/glog/*.h', + 'src/demangle.cc', + 'src/logging.cc', + 'src/raw_logging.cc', + 'src/signalhandler.cc', + 'src/symbolize.cc', + 'src/utilities.cc', + 'src/vlog_is_on.cc' + # workaround for https://github.com/facebook/react-native/issues/14326 + spec.preserve_paths = 'src/*.h', + 'src/base/*.h' + spec.exclude_files = "src/windows/**/*" + spec.libraries = "c++" + spec.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++11", + "HEADER_SEARCH_PATHS" => "$(PODS_TARGET_SRCROOT)/src" + } + spec.compiler_flags = '-std=c++1y' + spec.libraries = "stdc++" + spec.platforms = { :ios => "8.0", :tvos => "9.2" } + +end diff --git a/libs/easywsclient/AndroidManifest.xml b/libs/easywsclient/AndroidManifest.xml new file mode 100644 index 000000000..dd86bab56 --- /dev/null +++ b/libs/easywsclient/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/libs/easywsclient/CMakeLists.txt b/libs/easywsclient/CMakeLists.txt new file mode 100644 index 000000000..7775f9db6 --- /dev/null +++ b/libs/easywsclient/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required (VERSION 3.6.0) +set(PACKAGE_NAME "easywsclient") +include_directories(./) +set(SOURCES easywsclient.cpp) +add_compile_options( + -std=c++11 + -stdlib=libstdc++ + -Wall + ) + +add_library(${PACKAGE_NAME} SHARED ${SOURCES}) +install(TARGETS ${PACKAGE_NAME} DESTINATION ./build/) +target_link_libraries(${PACKAGE_NAME}) diff --git a/libs/easywsclient/build.gradle b/libs/easywsclient/build.gradle new file mode 100644 index 000000000..18878323c --- /dev/null +++ b/libs/easywsclient/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + + ndk { + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + } + + externalNativeBuild { + cmake { + arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_shared' + } + } + + } + + externalNativeBuild { + cmake { + path './CMakeLists.txt' + } + } + + sourceSets { + main { + manifest.srcFile './AndroidManifest.xml' + } + } +} diff --git a/libs/easywsclient/easywsclient.cpp b/libs/easywsclient/easywsclient.cpp new file mode 100644 index 000000000..0cd186ef5 --- /dev/null +++ b/libs/easywsclient/easywsclient.cpp @@ -0,0 +1,529 @@ + +#ifdef _WIN32 + #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS // _CRT_SECURE_NO_WARNINGS for sscanf errors in MSVC2013 Express + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include + #include + #pragma comment( lib, "ws2_32" ) + #include + #include + #include + #include + #include + #ifndef _SSIZE_T_DEFINED + typedef int ssize_t; + #define _SSIZE_T_DEFINED + #endif + #ifndef _SOCKET_T_DEFINED + typedef SOCKET socket_t; + #define _SOCKET_T_DEFINED + #endif + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #if _MSC_VER >=1600 + // vs2010 or later + #include + #else + typedef __int8 int8_t; + typedef unsigned __int8 uint8_t; + typedef __int32 int32_t; + typedef unsigned __int32 uint32_t; + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; + #endif + #define socketerrno WSAGetLastError() + #define SOCKET_EAGAIN_EINPROGRESS WSAEINPROGRESS + #define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #ifndef _SOCKET_T_DEFINED + typedef int socket_t; + #define _SOCKET_T_DEFINED + #endif + #ifndef INVALID_SOCKET + #define INVALID_SOCKET (-1) + #endif + #ifndef SOCKET_ERROR + #define SOCKET_ERROR (-1) + #endif + #define closesocket(s) ::close(s) + #include + #define socketerrno errno + #define SOCKET_EAGAIN_EINPROGRESS EAGAIN + #define SOCKET_EWOULDBLOCK EWOULDBLOCK +#endif + +#include +#include + +#include "easywsclient.hpp" + +using easywsclient::Callback_Imp; +using easywsclient::BytesCallback_Imp; + +namespace { // private module-only namespace + +socket_t hostname_connect(const std::string& hostname, int port) { + struct addrinfo hints; + struct addrinfo *result; + struct addrinfo *p; + int ret; + socket_t sockfd = INVALID_SOCKET; + char sport[16]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + snprintf(sport, 16, "%d", port); + if ((ret = getaddrinfo(hostname.c_str(), sport, &hints, &result)) != 0) + { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); + return 1; + } + for(p = result; p != NULL; p = p->ai_next) + { + sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sockfd == INVALID_SOCKET) { continue; } + if (connect(sockfd, p->ai_addr, p->ai_addrlen) != SOCKET_ERROR) { + break; + } + closesocket(sockfd); + sockfd = INVALID_SOCKET; + } + freeaddrinfo(result); + return sockfd; +} + + +class _DummyWebSocket : public easywsclient::WebSocket +{ + public: + void poll(int timeout) { } + void send(const std::string& message) { } + void sendBinary(const std::string& message) { } + void sendBinary(const std::vector& message) { } + void sendPing() { } + void close() { } + readyStateValues getReadyState() const { return CLOSED; } + void _dispatch(Callback_Imp & callable) { } + void _dispatchBinary(BytesCallback_Imp& callable) { } +}; + + +class _RealWebSocket : public easywsclient::WebSocket +{ + public: + // http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-------+-+-------------+-------------------------------+ + // |F|R|R|R| opcode|M| Payload len | Extended payload length | + // |I|S|S|S| (4) |A| (7) | (16/64) | + // |N|V|V|V| |S| | (if payload len==126/127) | + // | |1|2|3| |K| | | + // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + // | Extended payload length continued, if payload len == 127 | + // + - - - - - - - - - - - - - - - +-------------------------------+ + // | |Masking-key, if MASK set to 1 | + // +-------------------------------+-------------------------------+ + // | Masking-key (continued) | Payload Data | + // +-------------------------------- - - - - - - - - - - - - - - - + + // : Payload Data continued ... : + // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // | Payload Data continued ... | + // +---------------------------------------------------------------+ + struct wsheader_type { + unsigned header_size; + bool fin; + bool mask; + enum opcode_type { + CONTINUATION = 0x0, + TEXT_FRAME = 0x1, + BINARY_FRAME = 0x2, + CLOSE = 8, + PING = 9, + PONG = 0xa, + } opcode; + int N0; + uint64_t N; + uint8_t masking_key[4]; + }; + + std::vector rxbuf; + std::vector txbuf; + std::vector receivedData; + + socket_t sockfd; + readyStateValues readyState; + bool useMask; + + _RealWebSocket(socket_t sockfd, bool useMask) : sockfd(sockfd), readyState(OPEN), useMask(useMask) { + } + + readyStateValues getReadyState() const { + return readyState; + } + + void poll(int timeout) { // timeout in milliseconds + if (readyState == CLOSED) { + if (timeout > 0) { + timeval tv = { timeout/1000, (timeout%1000) * 1000 }; + select(0, NULL, NULL, NULL, &tv); + } + return; + } + if (timeout != 0) { + fd_set rfds; + fd_set wfds; + timeval tv = { timeout/1000, (timeout%1000) * 1000 }; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(sockfd, &rfds); + if (txbuf.size()) { FD_SET(sockfd, &wfds); } + select(sockfd + 1, &rfds, &wfds, 0, timeout > 0 ? &tv : 0); + } + while (true) { + // FD_ISSET(0, &rfds) will be true + int N = rxbuf.size(); + ssize_t ret; + rxbuf.resize(N + 1500); + ret = recv(sockfd, (char*)&rxbuf[0] + N, 1500, 0); + if (false) { } + else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) { + rxbuf.resize(N); + break; + } + else if (ret <= 0) { + rxbuf.resize(N); + closesocket(sockfd); + readyState = CLOSED; + fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr); + break; + } + else { + rxbuf.resize(N + ret); + } + } + while (txbuf.size()) { + int ret = ::send(sockfd, (char*)&txbuf[0], txbuf.size(), 0); + if (false) { } // ?? + else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) { + break; + } + else if (ret <= 0) { + closesocket(sockfd); + readyState = CLOSED; + fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr); + break; + } + else { + txbuf.erase(txbuf.begin(), txbuf.begin() + ret); + } + } + if (!txbuf.size() && readyState == CLOSING) { + closesocket(sockfd); + readyState = CLOSED; + } + } + + // Callable must have signature: void(const std::string & message). + // Should work with C functions, C++ functors, and C++11 std::function and + // lambda: + //template + //void dispatch(Callable callable) + virtual void _dispatch(Callback_Imp & callable) { + struct CallbackAdapter : public BytesCallback_Imp + // Adapt void(const std::string&) to void(const std::string&) + { + Callback_Imp& callable; + CallbackAdapter(Callback_Imp& callable) : callable(callable) { } + void operator()(const std::vector& message) { + std::string stringMessage(message.begin(), message.end()); + callable(stringMessage); + } + }; + CallbackAdapter bytesCallback(callable); + _dispatchBinary(bytesCallback); + } + + virtual void _dispatchBinary(BytesCallback_Imp & callable) { + // TODO: consider acquiring a lock on rxbuf... + while (true) { + wsheader_type ws; + if (rxbuf.size() < 2) { return; /* Need at least 2 */ } + const uint8_t * data = (uint8_t *) &rxbuf[0]; // peek, but don't consume + ws.fin = (data[0] & 0x80) == 0x80; + ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f); + ws.mask = (data[1] & 0x80) == 0x80; + ws.N0 = (data[1] & 0x7f); + ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0); + if (rxbuf.size() < ws.header_size) { return; /* Need: ws.header_size - rxbuf.size() */ } + int i = 0; + if (ws.N0 < 126) { + ws.N = ws.N0; + i = 2; + } + else if (ws.N0 == 126) { + ws.N = 0; + ws.N |= ((uint64_t) data[2]) << 8; + ws.N |= ((uint64_t) data[3]) << 0; + i = 4; + } + else if (ws.N0 == 127) { + ws.N = 0; + ws.N |= ((uint64_t) data[2]) << 56; + ws.N |= ((uint64_t) data[3]) << 48; + ws.N |= ((uint64_t) data[4]) << 40; + ws.N |= ((uint64_t) data[5]) << 32; + ws.N |= ((uint64_t) data[6]) << 24; + ws.N |= ((uint64_t) data[7]) << 16; + ws.N |= ((uint64_t) data[8]) << 8; + ws.N |= ((uint64_t) data[9]) << 0; + i = 10; + } + if (ws.mask) { + ws.masking_key[0] = ((uint8_t) data[i+0]) << 0; + ws.masking_key[1] = ((uint8_t) data[i+1]) << 0; + ws.masking_key[2] = ((uint8_t) data[i+2]) << 0; + ws.masking_key[3] = ((uint8_t) data[i+3]) << 0; + } + else { + ws.masking_key[0] = 0; + ws.masking_key[1] = 0; + ws.masking_key[2] = 0; + ws.masking_key[3] = 0; + } + if (rxbuf.size() < ws.header_size+ws.N) { return; /* Need: ws.header_size+ws.N - rxbuf.size() */ } + + // We got a whole message, now do something with it: + if (false) { } + else if ( + ws.opcode == wsheader_type::TEXT_FRAME + || ws.opcode == wsheader_type::BINARY_FRAME + || ws.opcode == wsheader_type::CONTINUATION + ) { + if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } } + receivedData.insert(receivedData.end(), rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed + if (ws.fin) { + callable((const std::vector) receivedData); + receivedData.erase(receivedData.begin(), receivedData.end()); + std::vector ().swap(receivedData);// free memory + } + } + else if (ws.opcode == wsheader_type::PING) { + if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } } + std::string data(rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N); + sendData(wsheader_type::PONG, data.size(), data.begin(), data.end()); + } + else if (ws.opcode == wsheader_type::PONG) { } + else if (ws.opcode == wsheader_type::CLOSE) { close(); } + else { fprintf(stderr, "ERROR: Got unexpected WebSocket message.\n"); close(); } + + rxbuf.erase(rxbuf.begin(), rxbuf.begin() + ws.header_size+(size_t)ws.N); + } + } + + void sendPing() { + std::string empty; + sendData(wsheader_type::PING, empty.size(), empty.begin(), empty.end()); + } + + void send(const std::string& message) { + sendData(wsheader_type::TEXT_FRAME, message.size(), message.begin(), message.end()); + } + + void sendBinary(const std::string& message) { + sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end()); + } + + void sendBinary(const std::vector& message) { + sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end()); + } + + template + void sendData(wsheader_type::opcode_type type, uint64_t message_size, Iterator message_begin, Iterator message_end) { + // TODO: + // Masking key should (must) be derived from a high quality random + // number generator, to mitigate attacks on non-WebSocket friendly + // middleware: + const uint8_t masking_key[4] = { 0x12, 0x34, 0x56, 0x78 }; + // TODO: consider acquiring a lock on txbuf... + if (readyState == CLOSING || readyState == CLOSED) { return; } + std::vector header; + header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + (useMask ? 4 : 0), 0); + header[0] = 0x80 | type; + if (false) { } + else if (message_size < 126) { + header[1] = (message_size & 0xff) | (useMask ? 0x80 : 0); + if (useMask) { + header[2] = masking_key[0]; + header[3] = masking_key[1]; + header[4] = masking_key[2]; + header[5] = masking_key[3]; + } + } + else if (message_size < 65536) { + header[1] = 126 | (useMask ? 0x80 : 0); + header[2] = (message_size >> 8) & 0xff; + header[3] = (message_size >> 0) & 0xff; + if (useMask) { + header[4] = masking_key[0]; + header[5] = masking_key[1]; + header[6] = masking_key[2]; + header[7] = masking_key[3]; + } + } + else { // TODO: run coverage testing here + header[1] = 127 | (useMask ? 0x80 : 0); + header[2] = (message_size >> 56) & 0xff; + header[3] = (message_size >> 48) & 0xff; + header[4] = (message_size >> 40) & 0xff; + header[5] = (message_size >> 32) & 0xff; + header[6] = (message_size >> 24) & 0xff; + header[7] = (message_size >> 16) & 0xff; + header[8] = (message_size >> 8) & 0xff; + header[9] = (message_size >> 0) & 0xff; + if (useMask) { + header[10] = masking_key[0]; + header[11] = masking_key[1]; + header[12] = masking_key[2]; + header[13] = masking_key[3]; + } + } + // N.B. - txbuf will keep growing until it can be transmitted over the socket: + txbuf.insert(txbuf.end(), header.begin(), header.end()); + txbuf.insert(txbuf.end(), message_begin, message_end); + if (useMask) { + size_t message_offset = txbuf.size() - message_size; + for (size_t i = 0; i != message_size; ++i) { + txbuf[message_offset + i] ^= masking_key[i&0x3]; + } + } + } + + void close() { + if(readyState == CLOSING || readyState == CLOSED) { return; } + readyState = CLOSING; + uint8_t closeFrame[6] = {0x88, 0x80, 0x00, 0x00, 0x00, 0x00}; // last 4 bytes are a masking key + std::vector header(closeFrame, closeFrame+6); + txbuf.insert(txbuf.end(), header.begin(), header.end()); + } + +}; + + +easywsclient::WebSocket::pointer from_url(const std::string& url, bool useMask, const std::string& origin) { + char host[128]; + int port; + char path[300]; + if (url.size() >= 300) { + fprintf(stderr, "ERROR: url size limit exceeded: %s\n", url.c_str()); + return NULL; + } + if (origin.size() >= 200) { + fprintf(stderr, "ERROR: origin size limit exceeded: %s\n", origin.c_str()); + return NULL; + } + if (false) { } + else if (sscanf(url.c_str(), "ws://%[^:/]:%d/%s", host, &port, path) == 3) { + } + else if (sscanf(url.c_str(), "ws://%[^:/]/%s", host, path) == 2) { + port = 80; + } + else if (sscanf(url.c_str(), "ws://%[^:/]:%d", host, &port) == 2) { + path[0] = '\0'; + } + else if (sscanf(url.c_str(), "ws://%[^:/]", host) == 1) { + port = 80; + path[0] = '\0'; + } + else { + fprintf(stderr, "ERROR: Could not parse WebSocket url: %s\n", url.c_str()); + return NULL; + } + //fprintf(stderr, "easywsclient: connecting: host=%s port=%d path=/%s\n", host, port, path); + socket_t sockfd = hostname_connect(host, port); + if (sockfd == INVALID_SOCKET) { + fprintf(stderr, "Unable to connect to %s:%d\n", host, port); + return NULL; + } + { + // XXX: this should be done non-blocking, + char line[256]; + int status; + int i; + snprintf(line, 256, "GET /%s HTTP/1.1\r\n", path); ::send(sockfd, line, strlen(line), 0); + if (port == 80) { + snprintf(line, 256, "Host: %s\r\n", host); ::send(sockfd, line, strlen(line), 0); + } + else { + snprintf(line, 256, "Host: %s:%d\r\n", host, port); ::send(sockfd, line, strlen(line), 0); + } + snprintf(line, 256, "Upgrade: websocket\r\n"); ::send(sockfd, line, strlen(line), 0); + snprintf(line, 256, "Connection: Upgrade\r\n"); ::send(sockfd, line, strlen(line), 0); + if (!origin.empty()) { + snprintf(line, 256, "Origin: %s\r\n", origin.c_str()); ::send(sockfd, line, strlen(line), 0); + } + snprintf(line, 256, "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"); ::send(sockfd, line, strlen(line), 0); + snprintf(line, 256, "Sec-WebSocket-Version: 13\r\n"); ::send(sockfd, line, strlen(line), 0); + snprintf(line, 256, "\r\n"); ::send(sockfd, line, strlen(line), 0); + for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } } + line[i] = 0; + if (i == 255) { fprintf(stderr, "ERROR: Got invalid status line connecting to: %s\n", url.c_str()); return NULL; } + if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { fprintf(stderr, "ERROR: Got bad status connecting to %s: %s", url.c_str(), line); return NULL; } + // TODO: verify response headers, + while (true) { + for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } } + if (line[0] == '\r' && line[1] == '\n') { break; } + } + } + int flag = 1; + setsockopt(sockfd, 6, TCP_NODELAY, (char*) &flag, sizeof(flag)); // Disable Nagle's algorithm +#ifdef _WIN32 + u_long on = 1; + ioctlsocket(sockfd, FIONBIO, &on); +#else + fcntl(sockfd, F_SETFL, O_NONBLOCK); +#endif + //fprintf(stderr, "Connected to: %s\n", url.c_str()); + return easywsclient::WebSocket::pointer(new _RealWebSocket(sockfd, useMask)); +} + +} // end of module-only namespace + + + +namespace easywsclient { + +WebSocket::pointer WebSocket::create_dummy() { + static pointer dummy = pointer(new _DummyWebSocket); + return dummy; +} + + +WebSocket::pointer WebSocket::from_url(const std::string& url, const std::string& origin) { + return ::from_url(url, true, origin); +} + +WebSocket::pointer WebSocket::from_url_no_mask(const std::string& url, const std::string& origin) { + return ::from_url(url, false, origin); +} + + +} // namespace easywsclient diff --git a/libs/easywsclient/easywsclient.hpp b/libs/easywsclient/easywsclient.hpp new file mode 100644 index 000000000..08c4a7b58 --- /dev/null +++ b/libs/easywsclient/easywsclient.hpp @@ -0,0 +1,72 @@ +#ifndef EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD +#define EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD + +// This code comes from: +// https://github.com/dhbaird/easywsclient +// +// To get the latest version: +// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp +// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp + +#include +#include + +namespace easywsclient { + +struct Callback_Imp { virtual void operator()(const std::string& message) = 0; }; +struct BytesCallback_Imp { virtual void operator()(const std::vector& message) = 0; }; + +class WebSocket { + public: + typedef WebSocket * pointer; + typedef enum readyStateValues { CLOSING, CLOSED, CONNECTING, OPEN } readyStateValues; + + // Factories: + static pointer create_dummy(); + static pointer from_url(const std::string& url, const std::string& origin = std::string()); + static pointer from_url_no_mask(const std::string& url, const std::string& origin = std::string()); + + // Interfaces: + virtual ~WebSocket() { } + virtual void poll(int timeout = 0) = 0; // timeout in milliseconds + virtual void send(const std::string& message) = 0; + virtual void sendBinary(const std::string& message) = 0; + virtual void sendBinary(const std::vector& message) = 0; + virtual void sendPing() = 0; + virtual void close() = 0; + virtual readyStateValues getReadyState() const = 0; + + template + void dispatch(Callable callable) + // For callbacks that accept a string argument. + { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers + struct _Callback : public Callback_Imp { + Callable& callable; + _Callback(Callable& callable) : callable(callable) { } + void operator()(const std::string& message) { callable(message); } + }; + _Callback callback(callable); + _dispatch(callback); + } + + template + void dispatchBinary(Callable callable) + // For callbacks that accept a std::vector argument. + { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers + struct _Callback : public BytesCallback_Imp { + Callable& callable; + _Callback(Callable& callable) : callable(callable) { } + void operator()(const std::vector& message) { callable(message); } + }; + _Callback callback(callable); + _dispatchBinary(callback); + } + + protected: + virtual void _dispatch(Callback_Imp& callable) = 0; + virtual void _dispatchBinary(BytesCallback_Imp& callable) = 0; +}; + +} // namespace easywsclient + +#endif /* EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD */ diff --git a/libs/fbjni/build.gradle b/libs/fbjni/build.gradle new file mode 100644 index 000000000..f4aa91810 --- /dev/null +++ b/libs/fbjni/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + + ndk { + abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' + } + + externalNativeBuild { + cmake { + arguments '-DANDROID_TOOLCHAIN=clang' + } + } + } + externalNativeBuild { + cmake { + path './src/main/cpp/CMakeLists.txt' + } + } +} + +dependencies { + // compileOnly dependencies + compileOnly deps.jsr305 + compileOnly deps.inferAnnotations + compileOnly 'com.facebook.litho:litho-annotations:0.15.0' +} diff --git a/libs/fbjni/src/main/AndroidManifest.xml b/libs/fbjni/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8e81b59db --- /dev/null +++ b/libs/fbjni/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/libs/fbjni/src/main/cpp/CMakeLists.txt b/libs/fbjni/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000..161471866 --- /dev/null +++ b/libs/fbjni/src/main/cpp/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# Copyright (c) 2014-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# + +cmake_minimum_required(VERSION 3.6.0) + +set(CMAKE_VERBOSE_MAKEFILE on) + +add_compile_options( + -fno-omit-frame-pointer + -fexceptions + -Wall + -std=c++11 + -DDISABLE_CPUCAP + -DDISABLE_XPLAT) + +file(GLOB fb_SRC + *.cpp + jni/*.cpp + lyra/*.cpp) + +set(libjnihack_DIR ../../../../jni-hack/) + +add_library(fb SHARED + ${fb_SRC}) + +target_include_directories(fb PRIVATE + include ${libjnihack_DIR}) + +target_link_libraries(fb log) diff --git a/libs/fbjni/src/main/cpp/assert.cpp b/libs/fbjni/src/main/cpp/assert.cpp new file mode 100644 index 000000000..aaa999e13 --- /dev/null +++ b/libs/fbjni/src/main/cpp/assert.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include +#include + +#include +#include + +namespace facebook { + +#define ASSERT_BUF_SIZE 4096 +static char sAssertBuf[ASSERT_BUF_SIZE]; +static AssertHandler gAssertHandler; + +void assertInternal(const char* formatstr ...) { + va_list va_args; + va_start(va_args, formatstr); + vsnprintf(sAssertBuf, sizeof(sAssertBuf), formatstr, va_args); + va_end(va_args); + if (gAssertHandler != NULL) { + gAssertHandler(sAssertBuf); + } + FBLOG(LOG_FATAL, "fbassert", "%s", sAssertBuf); + // crash at this specific address so that we can find our crashes easier + *(int*)0xdeadb00c = 0; + // let the compiler know we won't reach the end of the function + __builtin_unreachable(); +} + +void setAssertHandler(AssertHandler assertHandler) { + gAssertHandler = assertHandler; +} + +} // namespace facebook diff --git a/libs/fbjni/src/main/cpp/include/jni/Countable.h b/libs/fbjni/src/main/cpp/include/jni/Countable.h new file mode 100644 index 000000000..0b07e3228 --- /dev/null +++ b/libs/fbjni/src/main/cpp/include/jni/Countable.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#pragma once + +#include + +#include +#include +#include + +namespace facebook { +namespace jni { + +FBEXPORT const RefPtr& countableFromJava(JNIEnv* env, jobject obj); + +template RefPtr extractRefPtr(JNIEnv* env, jobject obj) { + return static_cast>(countableFromJava(env, obj)); +} + +template RefPtr extractPossiblyNullRefPtr(JNIEnv* env, jobject obj) { + return obj ? extractRefPtr(env, obj) : nullptr; +} + +FBEXPORT void setCountableForJava(JNIEnv* env, jobject obj, RefPtr&& countable); + +void CountableOnLoad(JNIEnv* env); + +} } + diff --git a/libs/fbjni/src/main/cpp/include/jni/GlobalReference.h b/libs/fbjni/src/main/cpp/include/jni/GlobalReference.h new file mode 100644 index 000000000..1cac8a889 --- /dev/null +++ b/libs/fbjni/src/main/cpp/include/jni/GlobalReference.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#pragma once + +#include +#include + +#include + +#include + +namespace facebook { namespace jni { + +template +class GlobalReference { + static_assert(std::is_convertible::value, + "GlobalReference instantiated with type that is not " + "convertible to jobject"); + + public: + explicit GlobalReference(T globalReference) : + reference_(globalReference? Environment::current()->NewGlobalRef(globalReference) : nullptr) { + } + + ~GlobalReference() { + reset(); + } + + GlobalReference() : + reference_(nullptr) { + } + + // enable move constructor and assignment + GlobalReference(GlobalReference&& rhs) : + reference_(std::move(rhs.reference_)) { + rhs.reference_ = nullptr; + } + + GlobalReference& operator=(GlobalReference&& rhs) { + if (this != &rhs) { + reset(); + reference_ = std::move(rhs.reference_); + rhs.reference_ = nullptr; + } + return *this; + } + + GlobalReference(const GlobalReference& rhs) : + reference_{} { + reset(rhs.get()); + } + + GlobalReference& operator=(const GlobalReference& rhs) { + if (this == &rhs) { + return *this; + } + reset(rhs.get()); + return *this; + } + + explicit operator bool() const { + return (reference_ != nullptr); + } + + T get() const { + return reinterpret_cast(reference_); + } + + void reset(T globalReference = nullptr) { + if (reference_) { + Environment::current()->DeleteGlobalRef(reference_); + } + if (globalReference) { + reference_ = Environment::current()->NewGlobalRef(globalReference); + } else { + reference_ = nullptr; + } + } + + private: + jobject reference_; +}; + +}} diff --git a/libs/fbjni/src/main/cpp/include/jni/LocalReference.h b/libs/fbjni/src/main/cpp/include/jni/LocalReference.h new file mode 100644 index 000000000..20955eac7 --- /dev/null +++ b/libs/fbjni/src/main/cpp/include/jni/LocalReference.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#pragma once + +#include +#include + +#include + +#include + +namespace facebook { +namespace jni { + +template +struct LocalReferenceDeleter { + static_assert(std::is_convertible::value, + "LocalReferenceDeleter instantiated with type that is not convertible to jobject"); + void operator()(T localReference) { + if (localReference != nullptr) { + Environment::current()->DeleteLocalRef(localReference); + } + } + }; + +template +using LocalReference = + std::unique_ptr::type, LocalReferenceDeleter>; + +} } diff --git a/libs/fbjni/src/main/cpp/include/jni/LocalString.h b/libs/fbjni/src/main/cpp/include/jni/LocalString.h new file mode 100644 index 000000000..77f0696b5 --- /dev/null +++ b/libs/fbjni/src/main/cpp/include/jni/LocalString.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#pragma once + +#include + +#include + +#include + +namespace facebook { +namespace jni { + +namespace detail { + +void utf8ToModifiedUTF8(const uint8_t* bytes, size_t len, uint8_t* modified, size_t modifiedLength); +size_t modifiedLength(const std::string& str); +size_t modifiedLength(const uint8_t* str, size_t* length); +std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept; +std::string utf16toUTF8(const uint16_t* utf16Bytes, size_t len) noexcept; + +} + +// JNI represents strings encoded with modified version of UTF-8. The difference between UTF-8 and +// Modified UTF-8 is that the latter support only 1-byte, 2-byte, and 3-byte formats. Supplementary +// character (4 bytes in unicode) needs to be represented in the form of surrogate pairs. To create +// a Modified UTF-8 surrogate pair that Dalvik would understand we take 4-byte unicode character, +// encode it with UTF-16 which gives us two 2 byte chars (surrogate pair) and then we encode each +// pair as UTF-8. This result in 2 x 3 byte characters. To convert modified UTF-8 to standard +// UTF-8, this mus tbe reversed. +// +// The second difference is that Modified UTF-8 is encoding NUL byte in 2-byte format. +// +// In order to avoid complex error handling, only a minimum of validity checking is done to avoid +// crashing. If the input is invalid, the output may be invalid as well. +// +// Relevant links: +// - http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html +// - https://docs.oracle.com/javase/6/docs/api/java/io/DataInput.html#modified-utf-8 + +class FBEXPORT LocalString { +public: + // Assumes UTF8 encoding and make a required convertion to modified UTF-8 when the string + // contains unicode supplementary characters. + explicit LocalString(const std::string& str); + explicit LocalString(const char* str); + jstring string() const { + return m_string; + } + ~LocalString(); +private: + jstring m_string; +}; + +// JString to UTF16 extractor using RAII idiom +class JStringUtf16Extractor { +public: + JStringUtf16Extractor(JNIEnv* env, jstring javaString) + : env_(env) + , javaString_(javaString) + , utf16String_(nullptr) { + if (env_ && javaString_) { + utf16String_ = env_->GetStringCritical(javaString_, nullptr); + } + } + + ~JStringUtf16Extractor() { + if (utf16String_) { + env_->ReleaseStringCritical(javaString_, utf16String_); + } + } + + operator const jchar* () const { + return utf16String_; + } + +private: + JNIEnv* env_; + jstring javaString_; + const jchar* utf16String_; +}; + +// The string from JNI is converted to standard UTF-8 if the string contains supplementary +// characters. +FBEXPORT std::string fromJString(JNIEnv* env, jstring str); + +} } diff --git a/libs/fbjni/src/main/cpp/include/jni/Registration.h b/libs/fbjni/src/main/cpp/include/jni/Registration.h new file mode 100644 index 000000000..600c32c0c --- /dev/null +++ b/libs/fbjni/src/main/cpp/include/jni/Registration.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#pragma once +#include +#include +#include + +namespace facebook { +namespace jni { + +static inline void registerNatives(JNIEnv* env, jclass cls, std::initializer_list methods) { + auto result = env->RegisterNatives(cls, methods.begin(), methods.size()); + FBASSERT(result == 0); +} + +static inline void registerNatives(JNIEnv* env, const char* cls, std::initializer_list list) { + registerNatives(env, env->FindClass(cls), list); +} + +} } diff --git a/libs/fbjni/src/main/cpp/include/jni/WeakReference.h b/libs/fbjni/src/main/cpp/include/jni/WeakReference.h new file mode 100644 index 000000000..e8bad1ace --- /dev/null +++ b/libs/fbjni/src/main/cpp/include/jni/WeakReference.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#pragma once +#include +#include +#include +#include +#include + + +namespace facebook { +namespace jni { + +class FBEXPORT WeakReference : public Countable { +public: + typedef RefPtr Ptr; + WeakReference(jobject strongRef); + ~WeakReference(); + jweak weakRef() { + return m_weakReference; + } + +private: + jweak m_weakReference; +}; + +// This class is intended to take a weak reference and turn it into a strong +// local reference. Consequently, it should only be allocated on the stack. +class FBEXPORT ResolvedWeakReference : public noncopyable { +public: + ResolvedWeakReference(jobject weakRef); + ResolvedWeakReference(const RefPtr& weakRef); + ~ResolvedWeakReference(); + + operator jobject () { + return m_strongReference; + } + + explicit operator bool () { + return m_strongReference != nullptr; + } + +private: + jobject m_strongReference; +}; + +} } + diff --git a/libs/fbjni/src/main/cpp/include/jni/jni_helpers.h b/libs/fbjni/src/main/cpp/include/jni/jni_helpers.h new file mode 100644 index 000000000..b740d830b --- /dev/null +++ b/libs/fbjni/src/main/cpp/include/jni/jni_helpers.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#pragma once + +#include + +#include + +namespace facebook { + +/** + * Instructs the JNI environment to throw an exception. + * + * @param pEnv JNI environment + * @param szClassName class name to throw + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +FBEXPORT jint throwException(JNIEnv* pEnv, const char* szClassName, const char* szFmt, va_list va_args); + +/** + * Instructs the JNI environment to throw a NoClassDefFoundError. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +FBEXPORT jint throwNoClassDefError(JNIEnv* pEnv, const char* szFmt, ...); + +/** + * Instructs the JNI environment to throw a RuntimeException. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +FBEXPORT jint throwRuntimeException(JNIEnv* pEnv, const char* szFmt, ...); + +/** + * Instructs the JNI environment to throw a IllegalArgumentException. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +FBEXPORT jint throwIllegalArgumentException(JNIEnv* pEnv, const char* szFmt, ...); + +/** + * Instructs the JNI environment to throw a IllegalStateException. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +FBEXPORT jint throwIllegalStateException(JNIEnv* pEnv, const char* szFmt, ...); + +/** + * Instructs the JNI environment to throw an IOException. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +FBEXPORT jint throwIOException(JNIEnv* pEnv, const char* szFmt, ...); + +/** + * Instructs the JNI environment to throw an AssertionError. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +FBEXPORT jint throwAssertionError(JNIEnv* pEnv, const char* szFmt, ...); + +/** + * Instructs the JNI environment to throw an OutOfMemoryError. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +FBEXPORT jint throwOutOfMemoryError(JNIEnv* pEnv, const char* szFmt, ...); + +/** + * Finds the specified class. If it's not found, instructs the JNI environment to throw an + * exception. + * + * @param pEnv JNI environment + * @param szClassName the classname to find in JNI format (e.g. "java/lang/String") + * @return the class or NULL if not found (in which case a pending exception will be queued). This + * returns a global reference (JNIEnv::NewGlobalRef). + */ +FBEXPORT jclass findClassOrThrow(JNIEnv *pEnv, const char* szClassName); + +/** + * Finds the specified field of the specified class. If it's not found, instructs the JNI + * environment to throw an exception. + * + * @param pEnv JNI environment + * @param clazz the class to lookup the field in + * @param szFieldName the name of the field to find + * @param szSig the signature of the field + * @return the field or NULL if not found (in which case a pending exception will be queued) + */ +FBEXPORT jfieldID getFieldIdOrThrow(JNIEnv* pEnv, jclass clazz, const char* szFieldName, const char* szSig); + +/** + * Finds the specified method of the specified class. If it's not found, instructs the JNI + * environment to throw an exception. + * + * @param pEnv JNI environment + * @param clazz the class to lookup the method in + * @param szMethodName the name of the method to find + * @param szSig the signature of the method + * @return the method or NULL if not found (in which case a pending exception will be queued) + */ +FBEXPORT jmethodID getMethodIdOrThrow( + JNIEnv* pEnv, + jclass clazz, + const char* szMethodName, + const char* szSig); + +} // namespace facebook + diff --git a/libs/fbjni/src/main/cpp/jni/ByteBuffer.cpp b/libs/fbjni/src/main/cpp/jni/ByteBuffer.cpp new file mode 100644 index 000000000..b41a9d309 --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/ByteBuffer.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include + +#include + +#include + +namespace facebook { +namespace jni { + +namespace { +local_ref createEmpty() { + static auto cls = JByteBuffer::javaClassStatic(); + static auto meth = cls->getStaticMethod("allocateDirect"); + return meth(cls, 0); +} +} + +local_ref JByteBuffer::wrapBytes(uint8_t* data, size_t size) { + // env->NewDirectByteBuffer requires that size is positive. Android's + // dalvik returns an invalid result and Android's art aborts if size == 0. + // Workaround this by using a slow path through Java in that case. + if (!size) { + return createEmpty(); + } + auto res = adopt_local(static_cast(Environment::current()->NewDirectByteBuffer(data, size))); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + if (!res) { + throw std::runtime_error("Direct byte buffers are unsupported."); + } + return res; +} + +uint8_t* JByteBuffer::getDirectBytes() const { + if (!self()) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + void* bytes = Environment::current()->GetDirectBufferAddress(self()); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + if (!bytes) { + throw std::runtime_error( + isDirect() ? + "Attempt to get direct bytes of non-direct byte buffer." : + "Error getting direct bytes of byte buffer."); + } + return static_cast(bytes); +} + +size_t JByteBuffer::getDirectSize() const { + if (!self()) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + int size = Environment::current()->GetDirectBufferCapacity(self()); + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + if (size < 0) { + throw std::runtime_error( + isDirect() ? + "Attempt to get direct size of non-direct byte buffer." : + "Error getting direct size of byte buffer."); + } + return static_cast(size); +} + +bool JByteBuffer::isDirect() const { + static auto meth = javaClassStatic()->getMethod("isDirect"); + return meth(self()); +} + +}} diff --git a/libs/fbjni/src/main/cpp/jni/Countable.cpp b/libs/fbjni/src/main/cpp/jni/Countable.cpp new file mode 100644 index 000000000..4e84a9f0e --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/Countable.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include +#include +#include +#include + +namespace facebook { +namespace jni { + +static jfieldID gCountableNativePtr; + +static RefPtr* rawCountableFromJava(JNIEnv* env, jobject obj) { + FBASSERT(obj); + return reinterpret_cast*>(env->GetLongField(obj, gCountableNativePtr)); +} + +const RefPtr& countableFromJava(JNIEnv* env, jobject obj) { + FBASSERT(obj); + return *rawCountableFromJava(env, obj); +} + +void setCountableForJava(JNIEnv* env, jobject obj, RefPtr&& countable) { + int oldValue = env->GetLongField(obj, gCountableNativePtr); + FBASSERTMSGF(oldValue == 0, "Cannot reinitialize object; expected nullptr, got %x", oldValue); + + FBASSERT(countable); + uintptr_t fieldValue = (uintptr_t) new RefPtr(std::move(countable)); + env->SetLongField(obj, gCountableNativePtr, fieldValue); +} + +/** + * NB: THREAD SAFETY (this comment also exists at Countable.java) + * + * This method deletes the corresponding native object on whatever thread the method is called + * on. In the common case when this is called by Countable#finalize(), this will be called on the + * system finalizer thread. If you manually call dispose on the Java object, the native object + * will be deleted synchronously on that thread. + */ +void dispose(JNIEnv* env, jobject obj) { + // Grab the pointer + RefPtr* countable = rawCountableFromJava(env, obj); + if (!countable) { + // That was easy. + return; + } + + // Clear out the old value to avoid double-frees + env->SetLongField(obj, gCountableNativePtr, 0); + + delete countable; +} + +void CountableOnLoad(JNIEnv* env) { + jclass countable = env->FindClass("com/facebook/jni/Countable"); + gCountableNativePtr = env->GetFieldID(countable, "mInstance", "J"); + registerNatives(env, countable, { + { "dispose", "()V", (void*) dispose }, + }); +} + +} } diff --git a/libs/fbjni/src/main/cpp/jni/Environment.cpp b/libs/fbjni/src/main/cpp/jni/Environment.cpp new file mode 100644 index 000000000..5be18b20f --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/Environment.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace facebook { +namespace jni { + +namespace { +StaticInitialized> g_env; +JavaVM* g_vm = nullptr; + +struct JThreadScopeSupport : JavaClass { + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/ThreadScopeSupport;"; + + // These reinterpret_casts are a totally dangerous pattern. Don't use them. Use HybridData instead. + static void runStdFunction(std::function&& func) { + static auto method = javaClassStatic()->getStaticMethod("runStdFunction"); + method(javaClassStatic(), reinterpret_cast(&func)); + } + + static void runStdFunctionImpl(alias_ref, jlong ptr) { + (*reinterpret_cast*>(ptr))(); + } + + static void OnLoad() { + // We need the javaClassStatic so that the class lookup is cached and that + // runStdFunction can be called from a ThreadScope-attached thread. + javaClassStatic()->registerNatives({ + makeNativeMethod("runStdFunctionImpl", runStdFunctionImpl), + }); + } +}; +} + +/* static */ +JNIEnv* Environment::current() { + JNIEnv* env = g_env->get(); + if ((env == nullptr) && (g_vm != nullptr)) { + if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { + FBLOGE("Error retrieving JNI Environment, thread is probably not attached to JVM"); + // TODO(cjhopman): This should throw an exception. + env = nullptr; + } else { + g_env->reset(env); + } + } + return env; +} + +/* static */ +void Environment::detachCurrentThread() { + auto env = g_env->get(); + if (env) { + FBASSERT(g_vm); + g_vm->DetachCurrentThread(); + g_env->reset(); + } +} + +struct EnvironmentInitializer { + EnvironmentInitializer(JavaVM* vm) { + FBASSERT(!g_vm); + FBASSERT(vm); + g_vm = vm; + g_env.initialize([] (void*) {}); + } +}; + +/* static */ +void Environment::initialize(JavaVM* vm) { + static EnvironmentInitializer init(vm); +} + +/* static */ +JNIEnv* Environment::ensureCurrentThreadIsAttached() { + auto env = g_env->get(); + if (!env) { + FBASSERT(g_vm); + g_vm->AttachCurrentThread(&env, nullptr); + g_env->reset(env); + } + return env; +} + +ThreadScope::ThreadScope() + : attachedWithThisScope_(false) { + JNIEnv* env = nullptr; + if (g_vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_EDETACHED) { + return; + } + env = facebook::jni::Environment::ensureCurrentThreadIsAttached(); + FBASSERT(env); + attachedWithThisScope_ = true; +} + +ThreadScope::~ThreadScope() { + if (attachedWithThisScope_) { + Environment::detachCurrentThread(); + } +} + +/* static */ +void ThreadScope::OnLoad() { + // These classes are required for ScopeWithClassLoader. Ensure they are looked up when loading. + JThreadScopeSupport::OnLoad(); +} + +/* static */ +void ThreadScope::WithClassLoader(std::function&& runnable) { + // TODO(cjhopman): If the classloader is already available in this scope, we + // shouldn't have to jump through java. + ThreadScope ts; + JThreadScopeSupport::runStdFunction(std::move(runnable)); +} + +} } + diff --git a/libs/fbjni/src/main/cpp/jni/Exceptions.cpp b/libs/fbjni/src/main/cpp/jni/Exceptions.cpp new file mode 100644 index 000000000..a425dc476 --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/Exceptions.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +namespace facebook { +namespace jni { + +namespace { +class JRuntimeException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/lang/RuntimeException;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } + + static local_ref create() { + return newInstance(); + } +}; + +class JIOException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/io/IOException;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; + +class JOutOfMemoryError : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/lang/OutOfMemoryError;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; + +class JArrayIndexOutOfBoundsException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/lang/ArrayIndexOutOfBoundsException;"; + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; + +class JUnknownCppException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/UnknownCppException;"; + + static local_ref create() { + return newInstance(); + } + + static local_ref create(const char* str) { + return newInstance(make_jstring(str)); + } +}; + +class JCppSystemErrorException : public JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/facebook/jni/CppSystemErrorException;"; + + static local_ref create(const std::system_error& e) { + return newInstance(make_jstring(e.what()), e.code().value()); + } +}; + +// Exception throwing & translating functions ////////////////////////////////////////////////////// + +// Functions that throw Java exceptions + +void setJavaExceptionAndAbortOnFailure(alias_ref throwable) { + auto env = Environment::current(); + if (throwable) { + env->Throw(throwable.get()); + } + if (env->ExceptionCheck() != JNI_TRUE) { + std::abort(); + } +} + +} + +// Functions that throw C++ exceptions + +// TODO(T6618159) Take a stack dump here to save context if it results in a crash when propagated +void throwPendingJniExceptionAsCppException() { + JNIEnv* env = Environment::current(); + if (env->ExceptionCheck() == JNI_FALSE) { + return; + } + + auto throwable = adopt_local(env->ExceptionOccurred()); + if (!throwable) { + throw std::runtime_error("Unable to get pending JNI exception."); + } + env->ExceptionClear(); + + throw JniException(throwable); +} + +void throwCppExceptionIf(bool condition) { + if (!condition) { + return; + } + + auto env = Environment::current(); + if (env->ExceptionCheck() == JNI_TRUE) { + throwPendingJniExceptionAsCppException(); + return; + } + + throw JniException(); +} + +void throwNewJavaException(jthrowable throwable) { + throw JniException(wrap_alias(throwable)); +} + +void throwNewJavaException(const char* throwableName, const char* msg) { + // If anything of the fbjni calls fail, an exception of a suitable + // form will be thrown, which is what we want. + auto throwableClass = findClassLocal(throwableName); + auto throwable = throwableClass->newObject( + throwableClass->getConstructor(), + make_jstring(msg).release()); + throwNewJavaException(throwable.get()); +} + +// Translate C++ to Java Exception + +namespace { + +// The implementation std::rethrow_if_nested uses a dynamic_cast to determine +// if the exception is a nested_exception. If the exception is from a library +// built with -fno-rtti, then that will crash. This avoids that. +void rethrow_if_nested() { + try { + throw; + } catch (const std::nested_exception& e) { + e.rethrow_nested(); + } catch (...) { + } +} + +// For each exception in the chain of the currently handled exception, func +// will be called with that exception as the currently handled exception (in +// reverse order, i.e. innermost first). +void denest(std::function func) { + try { + throw; + } catch (const std::exception& e) { + try { + rethrow_if_nested(); + } catch (...) { + denest(func); + } + func(); + } catch (...) { + func(); + } +} +} + +void translatePendingCppExceptionToJavaException() noexcept { + local_ref previous; + auto func = [&previous] () { + local_ref current; + try { + throw; + } catch(const JniException& ex) { + current = ex.getThrowable(); + } catch(const std::ios_base::failure& ex) { + current = JIOException::create(ex.what()); + } catch(const std::bad_alloc& ex) { + current = JOutOfMemoryError::create(ex.what()); + } catch(const std::out_of_range& ex) { + current = JArrayIndexOutOfBoundsException::create(ex.what()); + } catch(const std::system_error& ex) { + current = JCppSystemErrorException::create(ex); + } catch(const std::runtime_error& ex) { + current = JRuntimeException::create(ex.what()); + } catch(const std::exception& ex) { + current = JCppException::create(ex.what()); + } catch(const char* msg) { + current = JUnknownCppException::create(msg); + } catch(...) { + current = JUnknownCppException::create(); + } + if (previous) { + current->initCause(previous); + } + previous = current; + }; + + try { + denest(func); + setJavaExceptionAndAbortOnFailure(previous); + } catch (std::exception& e) { + FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException: %s", e.what()); + // rethrow the exception and let the noexcept handling abort. + throw; + } catch (...) { + FBLOGE("unexpected exception in translatePendingCppExceptionToJavaException"); + throw; + } +} + +// JniException //////////////////////////////////////////////////////////////////////////////////// + +const std::string JniException::kExceptionMessageFailure_ = "Unable to get exception message."; + +JniException::JniException() : JniException(JRuntimeException::create()) { } + +JniException::JniException(alias_ref throwable) : isMessageExtracted_(false) { + throwable_ = make_global(throwable); +} + +JniException::JniException(JniException &&rhs) + : throwable_(std::move(rhs.throwable_)), + what_(std::move(rhs.what_)), + isMessageExtracted_(rhs.isMessageExtracted_) { +} + +JniException::JniException(const JniException &rhs) + : what_(rhs.what_), isMessageExtracted_(rhs.isMessageExtracted_) { + throwable_ = make_global(rhs.throwable_); +} + +JniException::~JniException() { + ThreadScope ts; + throwable_.reset(); +} + +local_ref JniException::getThrowable() const noexcept { + return make_local(throwable_); +} + +// TODO 6900503: consider making this thread-safe. +void JniException::populateWhat() const noexcept { + ThreadScope ts; + try { + what_ = throwable_->toString(); + isMessageExtracted_ = true; + } catch(...) { + what_ = kExceptionMessageFailure_; + } +} + +const char* JniException::what() const noexcept { + if (!isMessageExtracted_) { + populateWhat(); + } + return what_.c_str(); +} + +void JniException::setJavaException() const noexcept { + setJavaExceptionAndAbortOnFailure(throwable_); +} + +}} diff --git a/libs/fbjni/src/main/cpp/jni/Hybrid.cpp b/libs/fbjni/src/main/cpp/jni/Hybrid.cpp new file mode 100644 index 000000000..45d5bff0b --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/Hybrid.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include "fb/fbjni.h" + + +namespace facebook { +namespace jni { + +namespace detail { + +void HybridData::setNativePointer(std::unique_ptr new_value) { + static auto pointerField = getClass()->getField("mNativePointer"); + auto* old_value = reinterpret_cast(getFieldValue(pointerField)); + if (new_value) { + // Modify should only ever be called once with a non-null + // new_value. If this happens again it's a programmer error, so + // blow up. + FBASSERTMSGF(old_value == 0, "Attempt to set C++ native pointer twice"); + } else if (old_value == 0) { + return; + } + // delete on a null pointer is defined to be a noop. + delete old_value; + // This releases ownership from the unique_ptr, and passes the pointer, and + // ownership of it, to HybridData which is managed by the java GC. The + // finalizer on hybridData calls resetNative which will delete the object, if + // resetNative has not already been called. + setFieldValue(pointerField, reinterpret_cast(new_value.release())); +} + +BaseHybridClass* HybridData::getNativePointer() { + static auto pointerField = getClass()->getField("mNativePointer"); + auto* value = reinterpret_cast(getFieldValue(pointerField)); + if (!value) { + throwNewJavaException("java/lang/NullPointerException", "java.lang.NullPointerException"); + } + return value; +} + +local_ref HybridData::create() { + return newInstance(); +} + +} + +namespace { +void resetNative(alias_ref jthis) { + jthis->setNativePointer(nullptr); +} +} + +void HybridDataOnLoad() { + registerNatives("com/facebook/jni/HybridData", { + makeNativeMethod("resetNative", resetNative), + }); +} + +}} diff --git a/libs/fbjni/src/main/cpp/jni/JThrowable.cpp b/libs/fbjni/src/main/cpp/jni/JThrowable.cpp new file mode 100644 index 000000000..cb2c8ae55 --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/JThrowable.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include "fb/fbjni.h" +#include + +std::string JThrowable::getStackTrace() const { + static auto getStackTraceMethod = javaClassStatic() + ->getMethod()>("getStackTrace"); + + std::ostringstream os; + + auto stackTrace = getStackTraceMethod(self()); + for (size_t i = 0; i < stackTrace->size(); ++i) { + os << facebook::jni::adopt_local((*stackTrace)[i])->toString() << ' '; + } + + return os.str(); +} diff --git a/libs/fbjni/src/main/cpp/jni/LocalString.cpp b/libs/fbjni/src/main/cpp/jni/LocalString.cpp new file mode 100644 index 000000000..aca121438 --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/LocalString.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include +#include +#include + +#include + +namespace facebook { +namespace jni { + +namespace { + +const uint16_t kUtf8OneByteBoundary = 0x80; +const uint16_t kUtf8TwoBytesBoundary = 0x800; +const uint16_t kUtf16HighSubLowBoundary = 0xD800; +const uint16_t kUtf16HighSubHighBoundary = 0xDC00; +const uint16_t kUtf16LowSubHighBoundary = 0xE000; + +inline void encode3ByteUTF8(char32_t code, uint8_t* out) { + FBASSERTMSGF((code & 0xffff0000) == 0, "3 byte utf-8 encodings only valid for up to 16 bits"); + + out[0] = 0xE0 | (code >> 12); + out[1] = 0x80 | ((code >> 6) & 0x3F); + out[2] = 0x80 | (code & 0x3F); +} + +inline char32_t decode3ByteUTF8(const uint8_t* in) { + return (((in[0] & 0x0f) << 12) | + ((in[1] & 0x3f) << 6) | + ( in[2] & 0x3f)); +} + +inline void encode4ByteUTF8(char32_t code, std::string& out, size_t offset) { + FBASSERTMSGF((code & 0xfff80000) == 0, "4 byte utf-8 encodings only valid for up to 21 bits"); + + out[offset] = (char) (0xF0 | (code >> 18)); + out[offset + 1] = (char) (0x80 | ((code >> 12) & 0x3F)); + out[offset + 2] = (char) (0x80 | ((code >> 6) & 0x3F)); + out[offset + 3] = (char) (0x80 | (code & 0x3F)); +} + +template +inline bool isFourByteUTF8Encoding(const T* utf8) { + return ((*utf8 & 0xF8) == 0xF0); +} + +} + +namespace detail { + +size_t modifiedLength(const std::string& str) { + // Scan for supplementary characters + size_t j = 0; + for (size_t i = 0; i < str.size(); ) { + if (str[i] == 0) { + i += 1; + j += 2; + } else if (i + 4 > str.size() || + !isFourByteUTF8Encoding(&(str[i]))) { + // See the code in utf8ToModifiedUTF8 for what's happening here. + i += 1; + j += 1; + } else { + i += 4; + j += 6; + } + } + + return j; +} + +// returns modified utf8 length; *length is set to strlen(str) +size_t modifiedLength(const uint8_t* str, size_t* length) { + // NUL-terminated: Scan for length and supplementary characters + size_t i = 0; + size_t j = 0; + while (str[i] != 0) { + if (str[i + 1] == 0 || + str[i + 2] == 0 || + str[i + 3] == 0 || + !isFourByteUTF8Encoding(&(str[i]))) { + i += 1; + j += 1; + } else { + i += 4; + j += 6; + } + } + + *length = i; + return j; +} + +void utf8ToModifiedUTF8(const uint8_t* utf8, size_t len, uint8_t* modified, size_t modifiedBufLen) +{ + size_t j = 0; + for (size_t i = 0; i < len; ) { + FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short"); + if (utf8[i] == 0) { + FBASSERTMSGF(j + 1 < modifiedBufLen, "output buffer is too short"); + modified[j] = 0xc0; + modified[j + 1] = 0x80; + i += 1; + j += 2; + continue; + } + + if (i + 4 > len || + !isFourByteUTF8Encoding(utf8 + i)) { + // If the input is too short for this to be a four-byte + // encoding, or it isn't one for real, just copy it on through. + modified[j] = utf8[i]; + i++; + j++; + continue; + } + + // Convert 4 bytes of input to 2 * 3 bytes of output + char32_t code = (((utf8[i] & 0x07) << 18) | + ((utf8[i + 1] & 0x3f) << 12) | + ((utf8[i + 2] & 0x3f) << 6) | + ( utf8[i + 3] & 0x3f)); + char32_t first; + char32_t second; + + if (code > 0x10ffff) { + // These could be valid utf-8, but cannot be represented as modified UTF-8, due to the 20-bit + // limit on that representation. Encode two replacement characters, so the expected output + // length lines up. + const char32_t kUnicodeReplacementChar = 0xfffd; + first = kUnicodeReplacementChar; + second = kUnicodeReplacementChar; + } else { + // split into surrogate pair + first = ((code - 0x010000) >> 10) | 0xd800; + second = ((code - 0x010000) & 0x3ff) | 0xdc00; + } + + // encode each as a 3 byte surrogate value + FBASSERTMSGF(j + 5 < modifiedBufLen, "output buffer is too short"); + encode3ByteUTF8(first, modified + j); + encode3ByteUTF8(second, modified + j + 3); + i += 4; + j += 6; + } + + FBASSERTMSGF(j < modifiedBufLen, "output buffer is too short"); + modified[j++] = '\0'; +} + +std::string modifiedUTF8ToUTF8(const uint8_t* modified, size_t len) noexcept { + // Converting from modified utf8 to utf8 will always shrink, so this will always be sufficient + std::string utf8(len, 0); + size_t j = 0; + for (size_t i = 0; i < len; ) { + // surrogate pair: 1101 10xx xxxx xxxx 1101 11xx xxxx xxxx + // encoded pair: 1110 1101 1010 xxxx 10xx xxxx 1110 1101 1011 xxxx 10xx xxxx + + if (len >= i + 6 && + modified[i] == 0xed && + (modified[i + 1] & 0xf0) == 0xa0 && + modified[i + 3] == 0xed && + (modified[i + 4] & 0xf0) == 0xb0) { + // Valid surrogate pair + char32_t pair1 = decode3ByteUTF8(modified + i); + char32_t pair2 = decode3ByteUTF8(modified + i + 3); + char32_t ch = 0x10000 + (((pair1 & 0x3ff) << 10) | + ( pair2 & 0x3ff)); + encode4ByteUTF8(ch, utf8, j); + i += 6; + j += 4; + continue; + } else if (len >= i + 2 && + modified[i] == 0xc0 && + modified[i + 1] == 0x80) { + utf8[j] = 0; + i += 2; + j += 1; + continue; + } + + // copy one byte. This might be a one, two, or three-byte encoding. It might be an invalid + // encoding of some sort, but garbage in garbage out is ok. + + utf8[j] = (char) modified[i]; + i++; + j++; + } + + utf8.resize(j); + + return utf8; +} + +// Calculate how many bytes are needed to convert an UTF16 string into UTF8 +// UTF16 string +size_t utf16toUTF8Length(const uint16_t* utf16String, size_t utf16StringLen) { + if (!utf16String || utf16StringLen == 0) { + return 0; + } + + uint32_t utf8StringLen = 0; + auto utf16StringEnd = utf16String + utf16StringLen; + auto idx16 = utf16String; + while (idx16 < utf16StringEnd) { + auto ch = *idx16++; + if (ch < kUtf8OneByteBoundary) { + utf8StringLen++; + } else if (ch < kUtf8TwoBytesBoundary) { + utf8StringLen += 2; + } else if ( + (ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) && + (idx16 < utf16StringEnd) && + (*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) { + utf8StringLen += 4; + idx16++; + } else { + utf8StringLen += 3; + } + } + + return utf8StringLen; +} + +std::string utf16toUTF8(const uint16_t* utf16String, size_t utf16StringLen) noexcept { + if (!utf16String || utf16StringLen <= 0) { + return ""; + } + + std::string utf8String(utf16toUTF8Length(utf16String, utf16StringLen), '\0'); + auto idx8 = utf8String.begin(); + auto idx16 = utf16String; + auto utf16StringEnd = utf16String + utf16StringLen; + while (idx16 < utf16StringEnd) { + auto ch = *idx16++; + if (ch < kUtf8OneByteBoundary) { + *idx8++ = (ch & 0x7F); + } else if (ch < kUtf8TwoBytesBoundary) { + *idx8++ = 0b11000000 | (ch >> 6); + *idx8++ = 0b10000000 | (ch & 0x3F); + } else if ( + (ch >= kUtf16HighSubLowBoundary) && (ch < kUtf16HighSubHighBoundary) && + (idx16 < utf16StringEnd) && + (*idx16 >= kUtf16HighSubHighBoundary) && (*idx16 < kUtf16LowSubHighBoundary)) { + auto ch2 = *idx16++; + uint8_t trunc_byte = (((ch >> 6) & 0x0F) + 1); + *idx8++ = 0b11110000 | (trunc_byte >> 2); + *idx8++ = 0b10000000 | ((trunc_byte & 0x03) << 4) | ((ch >> 2) & 0x0F); + *idx8++ = 0b10000000 | ((ch & 0x03) << 4) | ((ch2 >> 6) & 0x0F); + *idx8++ = 0b10000000 | (ch2 & 0x3F); + } else { + *idx8++ = 0b11100000 | (ch >> 12); + *idx8++ = 0b10000000 | ((ch >> 6) & 0x3F); + *idx8++ = 0b10000000 | (ch & 0x3F); + } + } + + return utf8String; +} + +} + +LocalString::LocalString(const std::string& str) +{ + size_t modlen = detail::modifiedLength(str); + if (modlen == str.size()) { + // no supplementary characters, build jstring from input buffer + m_string = Environment::current()->NewStringUTF(str.data()); + return; + } + auto modified = std::vector(modlen + 1); // allocate extra byte for \0 + detail::utf8ToModifiedUTF8( + reinterpret_cast(str.data()), str.size(), + reinterpret_cast(modified.data()), modified.size()); + m_string = Environment::current()->NewStringUTF(modified.data()); +} + +LocalString::LocalString(const char* str) +{ + size_t len; + size_t modlen = detail::modifiedLength(reinterpret_cast(str), &len); + if (modlen == len) { + // no supplementary characters, build jstring from input buffer + m_string = Environment::current()->NewStringUTF(str); + return; + } + auto modified = std::vector(modlen + 1); // allocate extra byte for \0 + detail::utf8ToModifiedUTF8( + reinterpret_cast(str), len, + reinterpret_cast(modified.data()), modified.size()); + m_string = Environment::current()->NewStringUTF(modified.data()); +} + +LocalString::~LocalString() { + Environment::current()->DeleteLocalRef(m_string); +} + +std::string fromJString(JNIEnv* env, jstring str) { + auto utf16String = JStringUtf16Extractor(env, str); + auto length = env->GetStringLength(str); + return detail::utf16toUTF8(utf16String, length); +} + +} } diff --git a/libs/fbjni/src/main/cpp/jni/OnLoad.cpp b/libs/fbjni/src/main/cpp/jni/OnLoad.cpp new file mode 100644 index 000000000..9ba476055 --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/OnLoad.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include +#include +#include +#include + +using namespace facebook::jni; + +void initialize_fbjni() { + CountableOnLoad(Environment::current()); + HybridDataOnLoad(); + JNativeRunnable::OnLoad(); + ThreadScope::OnLoad(); +} diff --git a/libs/fbjni/src/main/cpp/jni/References.cpp b/libs/fbjni/src/main/cpp/jni/References.cpp new file mode 100644 index 000000000..e838de1f0 --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/References.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include + +namespace facebook { +namespace jni { + +JniLocalScope::JniLocalScope(JNIEnv* env, jint capacity) + : env_(env) { + hasFrame_ = false; + auto pushResult = env->PushLocalFrame(capacity); + FACEBOOK_JNI_THROW_EXCEPTION_IF(pushResult < 0); + hasFrame_ = true; +} + +JniLocalScope::~JniLocalScope() { + if (hasFrame_) { + env_->PopLocalFrame(nullptr); + } +} + +namespace internal { + +// Default implementation always returns true. +// Platform-specific sources can override this. +bool doesGetObjectRefTypeWork() __attribute__ ((weak)); +bool doesGetObjectRefTypeWork() { + return true; +} + +} + +} +} diff --git a/libs/fbjni/src/main/cpp/jni/WeakReference.cpp b/libs/fbjni/src/main/cpp/jni/WeakReference.cpp new file mode 100644 index 000000000..ad0e0f1c7 --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/WeakReference.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include +#include + +namespace facebook { +namespace jni { + +WeakReference::WeakReference(jobject strongRef) : + m_weakReference(Environment::current()->NewWeakGlobalRef(strongRef)) +{ +} + +WeakReference::~WeakReference() { + auto env = Environment::current(); + FBASSERTMSGF(env, "Attempt to delete jni::WeakReference from non-JNI thread"); + env->DeleteWeakGlobalRef(m_weakReference); +} + +ResolvedWeakReference::ResolvedWeakReference(jobject weakRef) : + m_strongReference(Environment::current()->NewLocalRef(weakRef)) +{ +} + +ResolvedWeakReference::ResolvedWeakReference(const RefPtr& weakRef) : + m_strongReference(Environment::current()->NewLocalRef(weakRef->weakRef())) +{ +} + +ResolvedWeakReference::~ResolvedWeakReference() { + if (m_strongReference) + Environment::current()->DeleteLocalRef(m_strongReference); +} + +} } + diff --git a/libs/fbjni/src/main/cpp/jni/fbjni.cpp b/libs/fbjni/src/main/cpp/jni/fbjni.cpp new file mode 100644 index 000000000..d07d62ffa --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/fbjni.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include + +#include +#include +#include +#include + +namespace facebook { +namespace jni { + +jint initialize(JavaVM* vm, std::function&& init_fn) noexcept { + static std::once_flag flag{}; + // TODO (t7832883): DTRT when we have exception pointers + static auto error_msg = std::string{"Failed to initialize fbjni"}; + static auto error_occured = false; + + std::call_once(flag, [vm] { + try { + Environment::initialize(vm); + } catch (std::exception& ex) { + error_occured = true; + try { + error_msg = std::string{"Failed to initialize fbjni: "} + ex.what(); + } catch (...) { + // Ignore, we already have a fall back message + } + } catch (...) { + error_occured = true; + } + }); + + try { + if (error_occured) { + throw std::runtime_error(error_msg); + } + + init_fn(); + } catch (const std::exception& e) { + FBLOGE("error %s", e.what()); + translatePendingCppExceptionToJavaException(); + } catch (...) { + translatePendingCppExceptionToJavaException(); + // So Java will handle the translated exception, fall through and + // return a good version number. + } + return JNI_VERSION_1_6; +} + +alias_ref findClassStatic(const char* name) { + const auto env = internal::getEnv(); + if (!env) { + throw std::runtime_error("Unable to retrieve JNIEnv*."); + } + auto cls = env->FindClass(name); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); + auto leaking_ref = (jclass)env->NewGlobalRef(cls); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!leaking_ref); + return wrap_alias(leaking_ref); +} + +local_ref findClassLocal(const char* name) { + const auto env = internal::getEnv(); + if (!env) { + throw std::runtime_error("Unable to retrieve JNIEnv*."); + } + auto cls = env->FindClass(name); + FACEBOOK_JNI_THROW_EXCEPTION_IF(!cls); + return adopt_local(cls); +} + + +// jstring ///////////////////////////////////////////////////////////////////////////////////////// + +std::string JString::toStdString() const { + const auto env = internal::getEnv(); + auto utf16String = JStringUtf16Extractor(env, self()); + auto length = env->GetStringLength(self()); + return detail::utf16toUTF8(utf16String, length); +} + +local_ref make_jstring(const char* utf8) { + if (!utf8) { + return {}; + } + const auto env = internal::getEnv(); + size_t len; + size_t modlen = detail::modifiedLength(reinterpret_cast(utf8), &len); + jstring result; + if (modlen == len) { + // The only difference between utf8 and modifiedUTF8 is in encoding 4-byte UTF8 chars + // and '\0' that is encoded on 2 bytes. + // + // Since modifiedUTF8-encoded string can be no shorter than it's UTF8 conterpart we + // know that if those two strings are of the same length we don't need to do any + // conversion -> no 4-byte chars nor '\0'. + result = env->NewStringUTF(utf8); + } else { + auto modified = std::vector(modlen + 1); // allocate extra byte for \0 + detail::utf8ToModifiedUTF8( + reinterpret_cast(utf8), len, + reinterpret_cast(modified.data()), modified.size()); + result = env->NewStringUTF(modified.data()); + } + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); + return adopt_local(result); +} + + +// JniPrimitiveArrayFunctions ////////////////////////////////////////////////////////////////////// + +#pragma push_macro("DEFINE_PRIMITIVE_METHODS") +#undef DEFINE_PRIMITIVE_METHODS +#define DEFINE_PRIMITIVE_METHODS(TYPE, NAME, SMALLNAME) \ + \ +template<> \ +FBEXPORT \ +TYPE* JPrimitiveArray::getElements(jboolean* isCopy) { \ + auto env = internal::getEnv(); \ + TYPE* res = env->Get ## NAME ## ArrayElements(self(), isCopy); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ + return res; \ +} \ + \ +template<> \ +FBEXPORT \ +void JPrimitiveArray::releaseElements( \ + TYPE* elements, jint mode) { \ + auto env = internal::getEnv(); \ + env->Release ## NAME ## ArrayElements(self(), elements, mode); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ +} \ + \ +template<> \ +FBEXPORT \ +void JPrimitiveArray::getRegion( \ + jsize start, jsize length, TYPE* buf) { \ + auto env = internal::getEnv(); \ + env->Get ## NAME ## ArrayRegion(self(), start, length, buf); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ +} \ + \ +template<> \ +FBEXPORT \ +void JPrimitiveArray::setRegion( \ + jsize start, jsize length, const TYPE* elements) { \ + auto env = internal::getEnv(); \ + env->Set ## NAME ## ArrayRegion(self(), start, length, elements); \ + FACEBOOK_JNI_THROW_PENDING_EXCEPTION(); \ +} \ + \ +FBEXPORT \ +local_ref make_ ## SMALLNAME ## _array(jsize size) { \ + auto array = internal::getEnv()->New ## NAME ## Array(size); \ + FACEBOOK_JNI_THROW_EXCEPTION_IF(!array); \ + return adopt_local(array); \ +} \ + \ +template<> \ +FBEXPORT \ +local_ref JArray ## NAME::newArray(size_t count) { \ + return make_ ## SMALLNAME ## _array(count); \ +} \ + \ + +DEFINE_PRIMITIVE_METHODS(jboolean, Boolean, boolean) +DEFINE_PRIMITIVE_METHODS(jbyte, Byte, byte) +DEFINE_PRIMITIVE_METHODS(jchar, Char, char) +DEFINE_PRIMITIVE_METHODS(jshort, Short, short) +DEFINE_PRIMITIVE_METHODS(jint, Int, int) +DEFINE_PRIMITIVE_METHODS(jlong, Long, long) +DEFINE_PRIMITIVE_METHODS(jfloat, Float, float) +DEFINE_PRIMITIVE_METHODS(jdouble, Double, double) +#pragma pop_macro("DEFINE_PRIMITIVE_METHODS") + +// Internal debug ///////////////////////////////////////////////////////////////////////////////// + +namespace internal { + +FBEXPORT ReferenceStats g_reference_stats; + +FBEXPORT void facebook::jni::internal::ReferenceStats::reset() noexcept { + locals_deleted = globals_deleted = weaks_deleted = 0; +} + +} + +}} diff --git a/libs/fbjni/src/main/cpp/jni/jni_helpers.cpp b/libs/fbjni/src/main/cpp/jni/jni_helpers.cpp new file mode 100644 index 000000000..c1bfb5cd8 --- /dev/null +++ b/libs/fbjni/src/main/cpp/jni/jni_helpers.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include +#include +#include + +#include + +#define MSG_SIZE 1024 + +namespace facebook { + +/** + * Instructs the JNI environment to throw an exception. + * + * @param pEnv JNI environment + * @param szClassName class name to throw + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +jint throwException(JNIEnv* pEnv, const char* szClassName, const char* szFmt, va_list va_args) { + char szMsg[MSG_SIZE]; + vsnprintf(szMsg, MSG_SIZE, szFmt, va_args); + jclass exClass = pEnv->FindClass(szClassName); + return pEnv->ThrowNew(exClass, szMsg); +} + +/** + * Instructs the JNI environment to throw a NoClassDefFoundError. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +jint throwNoClassDefError(JNIEnv* pEnv, const char* szFmt, ...) { + va_list va_args; + va_start(va_args, szFmt); + jint ret = throwException(pEnv, "java/lang/NoClassDefFoundError", szFmt, va_args); + va_end(va_args); + return ret; +} + +/** + * Instructs the JNI environment to throw a RuntimeException. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +jint throwRuntimeException(JNIEnv* pEnv, const char* szFmt, ...) { + va_list va_args; + va_start(va_args, szFmt); + jint ret = throwException(pEnv, "java/lang/RuntimeException", szFmt, va_args); + va_end(va_args); + return ret; +} + +/** + * Instructs the JNI environment to throw an IllegalArgumentException. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +jint throwIllegalArgumentException(JNIEnv* pEnv, const char* szFmt, ...) { + va_list va_args; + va_start(va_args, szFmt); + jint ret = throwException(pEnv, "java/lang/IllegalArgumentException", szFmt, va_args); + va_end(va_args); + return ret; +} + +/** + * Instructs the JNI environment to throw an IllegalStateException. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +jint throwIllegalStateException(JNIEnv* pEnv, const char* szFmt, ...) { + va_list va_args; + va_start(va_args, szFmt); + jint ret = throwException(pEnv, "java/lang/IllegalStateException", szFmt, va_args); + va_end(va_args); + return ret; +} + +/** + * Instructs the JNI environment to throw an OutOfMemoryError. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +jint throwOutOfMemoryError(JNIEnv* pEnv, const char* szFmt, ...) { + va_list va_args; + va_start(va_args, szFmt); + jint ret = throwException(pEnv, "java/lang/OutOfMemoryError", szFmt, va_args); + va_end(va_args); + return ret; +} + +/** + * Instructs the JNI environment to throw an AssertionError. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +jint throwAssertionError(JNIEnv* pEnv, const char* szFmt, ...) { + va_list va_args; + va_start(va_args, szFmt); + jint ret = throwException(pEnv, "java/lang/AssertionError", szFmt, va_args); + va_end(va_args); + return ret; +} + +/** + * Instructs the JNI environment to throw an IOException. + * + * @param pEnv JNI environment + * @param szFmt sprintf-style format string + * @param ... sprintf-style args + * @return 0 on success; a negative value on failure + */ +jint throwIOException(JNIEnv* pEnv, const char* szFmt, ...) { + va_list va_args; + va_start(va_args, szFmt); + jint ret = throwException(pEnv, "java/io/IOException", szFmt, va_args); + va_end(va_args); + return ret; +} + +/** + * Finds the specified class. If it's not found, instructs the JNI environment to throw an + * exception. + * + * @param pEnv JNI environment + * @param szClassName the classname to find in JNI format (e.g. "java/lang/String") + * @return the class or NULL if not found (in which case a pending exception will be queued). This + * returns a global reference (JNIEnv::NewGlobalRef). + */ +jclass findClassOrThrow(JNIEnv* pEnv, const char* szClassName) { + jclass clazz = pEnv->FindClass(szClassName); + if (!clazz) { + return NULL; + } + return (jclass) pEnv->NewGlobalRef(clazz); +} + +/** + * Finds the specified field of the specified class. If it's not found, instructs the JNI + * environment to throw an exception. + * + * @param pEnv JNI environment + * @param clazz the class to lookup the field in + * @param szFieldName the name of the field to find + * @param szSig the signature of the field + * @return the field or NULL if not found (in which case a pending exception will be queued) + */ +jfieldID getFieldIdOrThrow(JNIEnv* pEnv, jclass clazz, const char* szFieldName, const char* szSig) { + return pEnv->GetFieldID(clazz, szFieldName, szSig); +} + +/** + * Finds the specified method of the specified class. If it's not found, instructs the JNI + * environment to throw an exception. + * + * @param pEnv JNI environment + * @param clazz the class to lookup the method in + * @param szMethodName the name of the method to find + * @param szSig the signature of the method + * @return the method or NULL if not found (in which case a pending exception will be queued) + */ +jmethodID getMethodIdOrThrow( + JNIEnv* pEnv, + jclass clazz, + const char* szMethodName, + const char* szSig) { + return pEnv->GetMethodID(clazz, szMethodName, szSig); +} + +} // namespace facebook diff --git a/libs/fbjni/src/main/cpp/log.cpp b/libs/fbjni/src/main/cpp/log.cpp new file mode 100644 index 000000000..5504ebf60 --- /dev/null +++ b/libs/fbjni/src/main/cpp/log.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include +#include +#include +#include + +#define LOG_BUFFER_SIZE 4096 +static LogHandler gLogHandler; + +void setLogHandler(LogHandler logHandler) { + gLogHandler = logHandler; +} + +int fb_printLog(int prio, const char *tag, const char *fmt, ...) { + char logBuffer[LOG_BUFFER_SIZE]; + + va_list va_args; + va_start(va_args, fmt); + int result = vsnprintf(logBuffer, sizeof(logBuffer), fmt, va_args); + va_end(va_args); + if (gLogHandler != NULL) { + gLogHandler(prio, tag, logBuffer); + } + __android_log_write(prio, tag, logBuffer); + return result; +} + +void logPrintByDelims(int priority, const char* tag, const char* delims, + const char* msg, ...) +{ + va_list ap; + char buf[32768]; + char* context; + char* tok; + + va_start(ap, msg); + vsnprintf(buf, sizeof(buf), msg, ap); + va_end(ap); + + tok = strtok_r(buf, delims, &context); + + if (!tok) { + return; + } + + do { + __android_log_write(priority, tag, tok); + } while ((tok = strtok_r(NULL, delims, &context))); +} + +#ifndef ANDROID + +// Implementations of the basic android logging functions for non-android platforms. + +static char logTagChar(int prio) { + switch (prio) { + default: + case ANDROID_LOG_UNKNOWN: + case ANDROID_LOG_DEFAULT: + case ANDROID_LOG_SILENT: + return ' '; + case ANDROID_LOG_VERBOSE: + return 'V'; + case ANDROID_LOG_DEBUG: + return 'D'; + case ANDROID_LOG_INFO: + return 'I'; + case ANDROID_LOG_WARN: + return 'W'; + case ANDROID_LOG_ERROR: + return 'E'; + case ANDROID_LOG_FATAL: + return 'F'; + } +} + +int __android_log_write(int prio, const char *tag, const char *text) { + return fprintf(stderr, "[%c/%.16s] %s\n", logTagChar(prio), tag, text); +} + +int __android_log_print(int prio, const char *tag, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + + int res = fprintf(stderr, "[%c/%.16s] ", logTagChar(prio), tag); + res += vfprintf(stderr, "%s\n", ap); + + va_end(ap); + return res; +} + +#endif diff --git a/libs/fbjni/src/main/cpp/lyra/lyra.cpp b/libs/fbjni/src/main/cpp/lyra/lyra.cpp new file mode 100644 index 000000000..23da86cc8 --- /dev/null +++ b/libs/fbjni/src/main/cpp/lyra/lyra.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include + +#include +#include +#include + +#include +#include + +using namespace std; + +namespace facebook { +namespace lyra { + +namespace { + +class IosFlagsSaver { + ios_base& ios_; + ios_base::fmtflags flags_; + + public: + IosFlagsSaver(ios_base& ios) + : ios_(ios), + flags_(ios.flags()) + {} + + ~IosFlagsSaver() { + ios_.flags(flags_); + } +}; + +struct BacktraceState { + size_t skip; + vector& stackTrace; +}; + +_Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg) { + BacktraceState* state = reinterpret_cast(arg); + auto absoluteProgramCounter = + reinterpret_cast(_Unwind_GetIP(context)); + + if (state->skip > 0) { + --state->skip; + return _URC_NO_REASON; + } + + if (state->stackTrace.size() == state->stackTrace.capacity()) { + return _URC_END_OF_STACK; + } + + state->stackTrace.push_back(absoluteProgramCounter); + + return _URC_NO_REASON; +} + +void captureBacktrace(size_t skip, vector& stackTrace) { + // Beware of a bug on some platforms, which makes the trace loop until the + // buffer is full when it reaches a noexcept function. It seems to be fixed in + // newer versions of gcc. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56846 + // TODO(t10738439): Investigate workaround for the stack trace bug + BacktraceState state = {skip, stackTrace}; + _Unwind_Backtrace(unwindCallback, &state); +} +} + +void getStackTrace(vector& stackTrace, size_t skip) { + stackTrace.clear(); + captureBacktrace(skip + 1, stackTrace); +} + +// TODO(t10737622): Improve on-device symbolification +void getStackTraceSymbols(vector& symbols, + const vector& trace) { + symbols.clear(); + symbols.reserve(trace.size()); + + for (size_t i = 0; i < trace.size(); ++i) { + Dl_info info; + if (dladdr(trace[i], &info)) { + symbols.emplace_back(trace[i], info.dli_fbase, info.dli_saddr, + info.dli_fname ? info.dli_fname : "", + info.dli_sname ? info.dli_sname : ""); + } + } +} + +ostream& operator<<(ostream& out, const StackTraceElement& elm) { + IosFlagsSaver flags{out}; + + // TODO(t10748683): Add build id to the output + out << "{dso=" << elm.libraryName() << " offset=" << hex + << showbase << elm.libraryOffset(); + + if (!elm.functionName().empty()) { + out << " func=" << elm.functionName() << "()+" << elm.functionOffset(); + } + + out << " build-id=" << hex << setw(8) << 0 + << "}"; + + return out; +} + +// TODO(t10737667): The implement a tool that parse the stack trace and +// symbolicate it +ostream& operator<<(ostream& out, const vector& trace) { + IosFlagsSaver flags{out}; + + auto i = 0; + out << "Backtrace:\n"; + for (auto& elm : trace) { + out << " #" << dec << setfill('0') << setw(2) << i++ << " " << elm << '\n'; + } + + return out; +} +} +} diff --git a/libs/fbjni/src/main/cpp/onload.cpp b/libs/fbjni/src/main/cpp/onload.cpp new file mode 100644 index 000000000..f7b1e5d89 --- /dev/null +++ b/libs/fbjni/src/main/cpp/onload.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#include +#ifndef DISABLE_CPUCAP +#include +#endif +#include + +using namespace facebook::jni; + +void initialize_xplatinit(); +void initialize_fbjni(); + +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + return facebook::jni::initialize(vm, [] { + initialize_fbjni(); +#ifndef DISABLE_XPLAT + initialize_xplatinit(); +#endif +#ifndef DISABLE_CPUCAP + initialize_cpucapabilities(); +#endif + }); +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/Countable.java b/libs/fbjni/src/main/java/com/facebook/jni/Countable.java new file mode 100644 index 000000000..4e4280b52 --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/Countable.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; + +/** + * A Java Object that has native memory allocated corresponding to this instance. + * + *

NB: THREAD SAFETY (this comment also exists at Countable.cpp) + * + *

{@link #dispose} deletes the corresponding native object on whatever thread the method is + * called on. In the common case when this is called by Countable#finalize(), this will be called on + * the system finalizer thread. If you manually call dispose on the Java object, the native object + * will be deleted synchronously on that thread. + */ +@DoNotStrip +public class Countable { + + // Private C++ instance + @DoNotStrip private long mInstance = 0; + + public native void dispose(); + + protected void finalize() throws Throwable { + dispose(); + super.finalize(); + } +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/CppException.java b/libs/fbjni/src/main/java/com/facebook/jni/CppException.java new file mode 100644 index 000000000..1d3f7f7e6 --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/CppException.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; + +@DoNotStrip +public class CppException extends RuntimeException { + @DoNotStrip + public CppException(String message) { + super(message); + } +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/CppSystemErrorException.java b/libs/fbjni/src/main/java/com/facebook/jni/CppSystemErrorException.java new file mode 100644 index 000000000..c4c08cf43 --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/CppSystemErrorException.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; + +@DoNotStrip +public class CppSystemErrorException extends CppException { + int errorCode; + + @DoNotStrip + public CppSystemErrorException(String message, int errorCode) { + super(message); + this.errorCode = errorCode; + } + + public int getErrorCode() { + return errorCode; + } +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/HybridData.java b/libs/fbjni/src/main/java/com/facebook/jni/HybridData.java new file mode 100644 index 000000000..ed9e43cca --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/HybridData.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; + +/** + * This object holds a native C++ member for hybrid Java/C++ objects. + * + *

NB: THREAD SAFETY + * + *

{@link #dispose} deletes the corresponding native object on whatever thread the method is + * called on. In the common case when this is called by HybridData#finalize(), this will be called + * on the system finalizer thread. If you manually call resetNative() on the Java object, the C++ + * object will be deleted synchronously on that thread. + */ +@DoNotStrip +public class HybridData { + + // Private C++ instance + @DoNotStrip private long mNativePointer = 0; + + /** + * To explicitly delete the instance, call resetNative(). If the C++ instance is referenced after + * this is called, a NullPointerException will be thrown. resetNative() may be called multiple + * times safely. Because {@link #finalize} calls resetNative, the instance will not leak if this + * is not called, but timing of deletion and the thread the C++ dtor is called on will be at the + * whim of the Java GC. If you want to control the thread and timing of the destructor, you should + * call resetNative() explicitly. + */ + public native void resetNative(); + + protected void finalize() throws Throwable { + resetNative(); + super.finalize(); + } + + public boolean isValid() { + return mNativePointer != 0; + } +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/IteratorHelper.java b/libs/fbjni/src/main/java/com/facebook/jni/IteratorHelper.java new file mode 100644 index 000000000..3a12ec090 --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/IteratorHelper.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; +import java.util.Iterator; +import javax.annotation.Nullable; + +/** + * To iterate over an Iterator from C++ requires two calls per entry: hasNext() and next(). This + * helper reduces it to one call and one field get per entry. It does not use a generic argument, + * since in C++, the types will be erased, anyway. This is *not* a {@link java.util.Iterator}. + */ +@DoNotStrip +public class IteratorHelper { + private final Iterator mIterator; + + // This is private, but accessed via JNI. + @DoNotStrip private @Nullable Object mElement; + + @DoNotStrip + public IteratorHelper(Iterator iterator) { + mIterator = iterator; + } + + @DoNotStrip + public IteratorHelper(Iterable iterable) { + mIterator = iterable.iterator(); + } + + /** + * Moves the helper to the next entry in the map, if any. Returns true iff there is an entry to + * read. + */ + @DoNotStrip + boolean hasNext() { + if (mIterator.hasNext()) { + mElement = mIterator.next(); + return true; + } else { + mElement = null; + return false; + } + } +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/MapIteratorHelper.java b/libs/fbjni/src/main/java/com/facebook/jni/MapIteratorHelper.java new file mode 100644 index 000000000..f9f44da69 --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/MapIteratorHelper.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; +import java.util.Iterator; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * To iterate over a Map from C++ requires four calls per entry: hasNext(), next(), getKey(), + * getValue(). This helper reduces it to one call and two field gets per entry. It does not use a + * generic argument, since in C++, the types will be erased, anyway. This is *not* a {@link + * java.util.Iterator}. + */ +@DoNotStrip +public class MapIteratorHelper { + @DoNotStrip private final Iterator mIterator; + @DoNotStrip private @Nullable Object mKey; + @DoNotStrip private @Nullable Object mValue; + + @DoNotStrip + public MapIteratorHelper(Map map) { + mIterator = map.entrySet().iterator(); + } + + /** + * Moves the helper to the next entry in the map, if any. Returns true iff there is an entry to + * read. + */ + @DoNotStrip + boolean hasNext() { + if (mIterator.hasNext()) { + Map.Entry entry = mIterator.next(); + mKey = entry.getKey(); + mValue = entry.getValue(); + return true; + } else { + mKey = null; + mValue = null; + return false; + } + } +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/NativeRunnable.java b/libs/fbjni/src/main/java/com/facebook/jni/NativeRunnable.java new file mode 100644 index 000000000..ecc3fc14e --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/NativeRunnable.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; + +/** A Runnable that has a native run implementation. */ +@DoNotStrip +public class NativeRunnable implements Runnable { + + private final HybridData mHybridData; + + private NativeRunnable(HybridData hybridData) { + mHybridData = hybridData; + } + + public native void run(); +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/ThreadScopeSupport.java b/libs/fbjni/src/main/java/com/facebook/jni/ThreadScopeSupport.java new file mode 100644 index 000000000..a23b46aff --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/ThreadScopeSupport.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2004-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; + +@DoNotStrip +public class ThreadScopeSupport { + // This is just used for ThreadScope::withClassLoader to have a java function + // in the stack so that jni has access to the correct classloader. + @DoNotStrip + private static void runStdFunction(long ptr) { + runStdFunctionImpl(ptr); + } + + private static native void runStdFunctionImpl(long ptr); +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/UnknownCppException.java b/libs/fbjni/src/main/java/com/facebook/jni/UnknownCppException.java new file mode 100644 index 000000000..4ffc63acb --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/UnknownCppException.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +package com.facebook.jni; + +import com.facebook.proguard.annotations.DoNotStrip; + +@DoNotStrip +public class UnknownCppException extends CppException { + @DoNotStrip + public UnknownCppException() { + super("Unknown"); + } + + @DoNotStrip + public UnknownCppException(String message) { + super(message); + } +} diff --git a/libs/fbjni/src/main/java/com/facebook/jni/fbjni.pro b/libs/fbjni/src/main/java/com/facebook/jni/fbjni.pro new file mode 100644 index 000000000..5b5b6454d --- /dev/null +++ b/libs/fbjni/src/main/java/com/facebook/jni/fbjni.pro @@ -0,0 +1,11 @@ +# For common use cases for the hybrid pattern, keep symbols which may +# be referenced only from C++. + +-keepclassmembers class * { + com.facebook.jni.HybridData *; + (com.facebook.jni.HybridData); +} + +-keepclasseswithmembers class * { + com.facebook.jni.HybridData *; +} diff --git a/libs/folly b/libs/folly new file mode 160000 index 000000000..711b3fdc6 --- /dev/null +++ b/libs/folly @@ -0,0 +1 @@ +Subproject commit 711b3fdc69f5ed8db5d21008551e0e9cb2cbc566 diff --git a/libs/jni-hack/README.md b/libs/jni-hack/README.md new file mode 100644 index 000000000..c30486ead --- /dev/null +++ b/libs/jni-hack/README.md @@ -0,0 +1 @@ +This buck module exists so that targets that need to be built against both 1) Android (where we can and should use the Android NDK jni headers) and 2) the host platform(generally for local unit tests) can depend on a single target and get the right jni header for whatever platform they're building against automatically. diff --git a/libs/jni-hack/jni.h b/libs/jni-hack/jni.h new file mode 100644 index 000000000..66e424d1e --- /dev/null +++ b/libs/jni-hack/jni.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2014-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +#pragma once + +#ifdef __ANDROID__ +#include_next +#else +#include "real/jni.h" +#endif diff --git a/libs/jni-hack/real/jni.h b/libs/jni-hack/real/jni.h new file mode 100644 index 000000000..b3d111240 --- /dev/null +++ b/libs/jni-hack/real/jni.h @@ -0,0 +1,1132 @@ +/* + * Copyright (c) 2006-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the LICENSE + * file in the root directory of this source tree. + * + */ +/* + * JNI specification, as defined by Sun: + * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html + * + * Everything here is expected to be VM-neutral. + */ + +#ifndef JNI_H_ +#define JNI_H_ + +#include +#include + +/* Primitive types that match up with Java equivalents. */ +typedef uint8_t jboolean; /* unsigned 8 bits */ +typedef int8_t jbyte; /* signed 8 bits */ +typedef uint16_t jchar; /* unsigned 16 bits */ +typedef int16_t jshort; /* signed 16 bits */ +typedef int32_t jint; /* signed 32 bits */ +typedef int64_t jlong; /* signed 64 bits */ +typedef float jfloat; /* 32-bit IEEE 754 */ +typedef double jdouble; /* 64-bit IEEE 754 */ + +/* "cardinal indices and sizes" */ +typedef jint jsize; + +#ifdef __cplusplus +/* + * Reference types, in C++ + */ +class _jobject {}; +class _jclass : public _jobject {}; +class _jstring : public _jobject {}; +class _jarray : public _jobject {}; +class _jobjectArray : public _jarray {}; +class _jbooleanArray : public _jarray {}; +class _jbyteArray : public _jarray {}; +class _jcharArray : public _jarray {}; +class _jshortArray : public _jarray {}; +class _jintArray : public _jarray {}; +class _jlongArray : public _jarray {}; +class _jfloatArray : public _jarray {}; +class _jdoubleArray : public _jarray {}; +class _jthrowable : public _jobject {}; + +typedef _jobject* jobject; +typedef _jclass* jclass; +typedef _jstring* jstring; +typedef _jarray* jarray; +typedef _jobjectArray* jobjectArray; +typedef _jbooleanArray* jbooleanArray; +typedef _jbyteArray* jbyteArray; +typedef _jcharArray* jcharArray; +typedef _jshortArray* jshortArray; +typedef _jintArray* jintArray; +typedef _jlongArray* jlongArray; +typedef _jfloatArray* jfloatArray; +typedef _jdoubleArray* jdoubleArray; +typedef _jthrowable* jthrowable; +typedef _jobject* jweak; + + +#else /* not __cplusplus */ + +/* + * Reference types, in C. + */ +typedef void* jobject; +typedef jobject jclass; +typedef jobject jstring; +typedef jobject jarray; +typedef jarray jobjectArray; +typedef jarray jbooleanArray; +typedef jarray jbyteArray; +typedef jarray jcharArray; +typedef jarray jshortArray; +typedef jarray jintArray; +typedef jarray jlongArray; +typedef jarray jfloatArray; +typedef jarray jdoubleArray; +typedef jobject jthrowable; +typedef jobject jweak; + +#endif /* not __cplusplus */ + +struct _jfieldID; /* opaque structure */ +typedef struct _jfieldID* jfieldID; /* field IDs */ + +struct _jmethodID; /* opaque structure */ +typedef struct _jmethodID* jmethodID; /* method IDs */ + +struct JNIInvokeInterface; + +typedef union jvalue { + jboolean z; + jbyte b; + jchar c; + jshort s; + jint i; + jlong j; + jfloat f; + jdouble d; + jobject l; +} jvalue; + +typedef enum jobjectRefType { + JNIInvalidRefType = 0, + JNILocalRefType = 1, + JNIGlobalRefType = 2, + JNIWeakGlobalRefType = 3 +} jobjectRefType; + +typedef struct { + const char* name; + const char* signature; + void* fnPtr; +} JNINativeMethod; + +struct _JNIEnv; +struct _JavaVM; +typedef const struct JNINativeInterface* C_JNIEnv; + +#if defined(__cplusplus) +typedef _JNIEnv JNIEnv; +typedef _JavaVM JavaVM; +#else +typedef const struct JNINativeInterface* JNIEnv; +typedef const struct JNIInvokeInterface* JavaVM; +#endif + +/* + * Table of interface function pointers. + */ +struct JNINativeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + void* reserved3; + + jint (*GetVersion)(JNIEnv *); + + jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, + jsize); + jclass (*FindClass)(JNIEnv*, const char*); + + jmethodID (*FromReflectedMethod)(JNIEnv*, jobject); + jfieldID (*FromReflectedField)(JNIEnv*, jobject); + /* spec doesn't show jboolean parameter */ + jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean); + + jclass (*GetSuperclass)(JNIEnv*, jclass); + jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass); + + /* spec doesn't show jboolean parameter */ + jobject (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean); + + jint (*Throw)(JNIEnv*, jthrowable); + jint (*ThrowNew)(JNIEnv *, jclass, const char *); + jthrowable (*ExceptionOccurred)(JNIEnv*); + void (*ExceptionDescribe)(JNIEnv*); + void (*ExceptionClear)(JNIEnv*); + void (*FatalError)(JNIEnv*, const char*); + + jint (*PushLocalFrame)(JNIEnv*, jint); + jobject (*PopLocalFrame)(JNIEnv*, jobject); + + jobject (*NewGlobalRef)(JNIEnv*, jobject); + void (*DeleteGlobalRef)(JNIEnv*, jobject); + void (*DeleteLocalRef)(JNIEnv*, jobject); + jboolean (*IsSameObject)(JNIEnv*, jobject, jobject); + + jobject (*NewLocalRef)(JNIEnv*, jobject); + jint (*EnsureLocalCapacity)(JNIEnv*, jint); + + jobject (*AllocObject)(JNIEnv*, jclass); + jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...); + jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, jvalue*); + + jclass (*GetObjectClass)(JNIEnv*, jobject); + jboolean (*IsInstanceOf)(JNIEnv*, jobject, jclass); + jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...); + jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); + jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...); + jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...); + jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...); + jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); + jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...); + jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...); + jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...); + jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); + void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list); + void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*); + + jobject (*CallNonvirtualObjectMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jobject (*CallNonvirtualObjectMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jobject (*CallNonvirtualObjectMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + jboolean (*CallNonvirtualBooleanMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jboolean (*CallNonvirtualBooleanMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jboolean (*CallNonvirtualBooleanMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + jbyte (*CallNonvirtualByteMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jbyte (*CallNonvirtualByteMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jbyte (*CallNonvirtualByteMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + jchar (*CallNonvirtualCharMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jchar (*CallNonvirtualCharMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jchar (*CallNonvirtualCharMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + jshort (*CallNonvirtualShortMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jshort (*CallNonvirtualShortMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jshort (*CallNonvirtualShortMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + jint (*CallNonvirtualIntMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jint (*CallNonvirtualIntMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jint (*CallNonvirtualIntMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + jlong (*CallNonvirtualLongMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jlong (*CallNonvirtualLongMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jlong (*CallNonvirtualLongMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + jfloat (*CallNonvirtualFloatMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jfloat (*CallNonvirtualFloatMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jfloat (*CallNonvirtualFloatMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + jdouble (*CallNonvirtualDoubleMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jdouble (*CallNonvirtualDoubleMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jdouble (*CallNonvirtualDoubleMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + void (*CallNonvirtualVoidMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + void (*CallNonvirtualVoidMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + void (*CallNonvirtualVoidMethodA)(JNIEnv*, jobject, jclass, + jmethodID, jvalue*); + + jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID); + jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID); + jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID); + jchar (*GetCharField)(JNIEnv*, jobject, jfieldID); + jshort (*GetShortField)(JNIEnv*, jobject, jfieldID); + jint (*GetIntField)(JNIEnv*, jobject, jfieldID); + jlong (*GetLongField)(JNIEnv*, jobject, jfieldID); + jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID); + jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID); + + void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject); + void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean); + void (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte); + void (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar); + void (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort); + void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint); + void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong); + void (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat); + void (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble); + + jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...); + jobject (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*CallStaticObjectMethodA)(JNIEnv*, jclass, jmethodID, jvalue*); + jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...); + jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID, + va_list); + jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID, + jvalue*); + jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...); + jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jbyte (*CallStaticByteMethodA)(JNIEnv*, jclass, jmethodID, jvalue*); + jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...); + jchar (*CallStaticCharMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jchar (*CallStaticCharMethodA)(JNIEnv*, jclass, jmethodID, jvalue*); + jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...); + jshort (*CallStaticShortMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jshort (*CallStaticShortMethodA)(JNIEnv*, jclass, jmethodID, jvalue*); + jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...); + jint (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jint (*CallStaticIntMethodA)(JNIEnv*, jclass, jmethodID, jvalue*); + jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...); + jlong (*CallStaticLongMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jlong (*CallStaticLongMethodA)(JNIEnv*, jclass, jmethodID, jvalue*); + jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...); + jfloat (*CallStaticFloatMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jfloat (*CallStaticFloatMethodA)(JNIEnv*, jclass, jmethodID, jvalue*); + jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...); + jdouble (*CallStaticDoubleMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jdouble (*CallStaticDoubleMethodA)(JNIEnv*, jclass, jmethodID, jvalue*); + void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); + void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list); + void (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, jvalue*); + + jfieldID (*GetStaticFieldID)(JNIEnv*, jclass, const char*, + const char*); + + jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID); + jboolean (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID); + jbyte (*GetStaticByteField)(JNIEnv*, jclass, jfieldID); + jchar (*GetStaticCharField)(JNIEnv*, jclass, jfieldID); + jshort (*GetStaticShortField)(JNIEnv*, jclass, jfieldID); + jint (*GetStaticIntField)(JNIEnv*, jclass, jfieldID); + jlong (*GetStaticLongField)(JNIEnv*, jclass, jfieldID); + jfloat (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID); + jdouble (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID); + + void (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject); + void (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean); + void (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte); + void (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar); + void (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort); + void (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint); + void (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong); + void (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat); + void (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble); + + jstring (*NewString)(JNIEnv*, const jchar*, jsize); + jsize (*GetStringLength)(JNIEnv*, jstring); + const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*); + jstring (*NewStringUTF)(JNIEnv*, const char*); + jsize (*GetStringUTFLength)(JNIEnv*, jstring); + /* JNI spec says this returns const jbyte*, but that's inconsistent */ + const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*); + jsize (*GetArrayLength)(JNIEnv*, jarray); + jobjectArray (*NewObjectArray)(JNIEnv*, jsize, jclass, jobject); + jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize); + void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, jsize, jobject); + + jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize); + jbyteArray (*NewByteArray)(JNIEnv*, jsize); + jcharArray (*NewCharArray)(JNIEnv*, jsize); + jshortArray (*NewShortArray)(JNIEnv*, jsize); + jintArray (*NewIntArray)(JNIEnv*, jsize); + jlongArray (*NewLongArray)(JNIEnv*, jsize); + jfloatArray (*NewFloatArray)(JNIEnv*, jsize); + jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize); + + jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*); + jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*); + jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*); + jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*); + jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); + jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*); + jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*); + jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*); + + void (*ReleaseBooleanArrayElements)(JNIEnv*, jbooleanArray, + jboolean*, jint); + void (*ReleaseByteArrayElements)(JNIEnv*, jbyteArray, + jbyte*, jint); + void (*ReleaseCharArrayElements)(JNIEnv*, jcharArray, + jchar*, jint); + void (*ReleaseShortArrayElements)(JNIEnv*, jshortArray, + jshort*, jint); + void (*ReleaseIntArrayElements)(JNIEnv*, jintArray, + jint*, jint); + void (*ReleaseLongArrayElements)(JNIEnv*, jlongArray, + jlong*, jint); + void (*ReleaseFloatArrayElements)(JNIEnv*, jfloatArray, + jfloat*, jint); + void (*ReleaseDoubleArrayElements)(JNIEnv*, jdoubleArray, + jdouble*, jint); + + void (*GetBooleanArrayRegion)(JNIEnv*, jbooleanArray, + jsize, jsize, jboolean*); + void (*GetByteArrayRegion)(JNIEnv*, jbyteArray, + jsize, jsize, jbyte*); + void (*GetCharArrayRegion)(JNIEnv*, jcharArray, + jsize, jsize, jchar*); + void (*GetShortArrayRegion)(JNIEnv*, jshortArray, + jsize, jsize, jshort*); + void (*GetIntArrayRegion)(JNIEnv*, jintArray, + jsize, jsize, jint*); + void (*GetLongArrayRegion)(JNIEnv*, jlongArray, + jsize, jsize, jlong*); + void (*GetFloatArrayRegion)(JNIEnv*, jfloatArray, + jsize, jsize, jfloat*); + void (*GetDoubleArrayRegion)(JNIEnv*, jdoubleArray, + jsize, jsize, jdouble*); + + /* spec shows these without const; some jni.h do, some don't */ + void (*SetBooleanArrayRegion)(JNIEnv*, jbooleanArray, + jsize, jsize, const jboolean*); + void (*SetByteArrayRegion)(JNIEnv*, jbyteArray, + jsize, jsize, const jbyte*); + void (*SetCharArrayRegion)(JNIEnv*, jcharArray, + jsize, jsize, const jchar*); + void (*SetShortArrayRegion)(JNIEnv*, jshortArray, + jsize, jsize, const jshort*); + void (*SetIntArrayRegion)(JNIEnv*, jintArray, + jsize, jsize, const jint*); + void (*SetLongArrayRegion)(JNIEnv*, jlongArray, + jsize, jsize, const jlong*); + void (*SetFloatArrayRegion)(JNIEnv*, jfloatArray, + jsize, jsize, const jfloat*); + void (*SetDoubleArrayRegion)(JNIEnv*, jdoubleArray, + jsize, jsize, const jdouble*); + + jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*, + jint); + jint (*UnregisterNatives)(JNIEnv*, jclass); + jint (*MonitorEnter)(JNIEnv*, jobject); + jint (*MonitorExit)(JNIEnv*, jobject); + jint (*GetJavaVM)(JNIEnv*, JavaVM**); + + void (*GetStringRegion)(JNIEnv*, jstring, jsize, jsize, jchar*); + void (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*); + + void* (*GetPrimitiveArrayCritical)(JNIEnv*, jarray, jboolean*); + void (*ReleasePrimitiveArrayCritical)(JNIEnv*, jarray, void*, jint); + + const jchar* (*GetStringCritical)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringCritical)(JNIEnv*, jstring, const jchar*); + + jweak (*NewWeakGlobalRef)(JNIEnv*, jobject); + void (*DeleteWeakGlobalRef)(JNIEnv*, jweak); + + jboolean (*ExceptionCheck)(JNIEnv*); + + jobject (*NewDirectByteBuffer)(JNIEnv*, void*, jlong); + void* (*GetDirectBufferAddress)(JNIEnv*, jobject); + jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject); + + /* added in JNI 1.6 */ + jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject); +}; + +/* + * C++ object wrapper. + * + * This is usually overlaid on a C struct whose first element is a + * JNINativeInterface*. We rely somewhat on compiler behavior. + */ +struct _JNIEnv { + /* do not rename this; it does not seem to be entirely opaque */ + const struct JNINativeInterface* functions; + +#if defined(__cplusplus) + + jint GetVersion() + { return functions->GetVersion(this); } + + jclass DefineClass(const char *name, jobject loader, const jbyte* buf, + jsize bufLen) + { return functions->DefineClass(this, name, loader, buf, bufLen); } + + jclass FindClass(const char* name) + { return functions->FindClass(this, name); } + + jmethodID FromReflectedMethod(jobject method) + { return functions->FromReflectedMethod(this, method); } + + jfieldID FromReflectedField(jobject field) + { return functions->FromReflectedField(this, field); } + + jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) + { return functions->ToReflectedMethod(this, cls, methodID, isStatic); } + + jclass GetSuperclass(jclass clazz) + { return functions->GetSuperclass(this, clazz); } + + jboolean IsAssignableFrom(jclass clazz1, jclass clazz2) + { return functions->IsAssignableFrom(this, clazz1, clazz2); } + + jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic) + { return functions->ToReflectedField(this, cls, fieldID, isStatic); } + + jint Throw(jthrowable obj) + { return functions->Throw(this, obj); } + + jint ThrowNew(jclass clazz, const char* message) + { return functions->ThrowNew(this, clazz, message); } + + jthrowable ExceptionOccurred() + { return functions->ExceptionOccurred(this); } + + void ExceptionDescribe() + { functions->ExceptionDescribe(this); } + + void ExceptionClear() + { functions->ExceptionClear(this); } + + void FatalError(const char* msg) + { functions->FatalError(this, msg); } + + jint PushLocalFrame(jint capacity) + { return functions->PushLocalFrame(this, capacity); } + + jobject PopLocalFrame(jobject result) + { return functions->PopLocalFrame(this, result); } + + jobject NewGlobalRef(jobject obj) + { return functions->NewGlobalRef(this, obj); } + + void DeleteGlobalRef(jobject globalRef) + { functions->DeleteGlobalRef(this, globalRef); } + + void DeleteLocalRef(jobject localRef) + { functions->DeleteLocalRef(this, localRef); } + + jboolean IsSameObject(jobject ref1, jobject ref2) + { return functions->IsSameObject(this, ref1, ref2); } + + jobject NewLocalRef(jobject ref) + { return functions->NewLocalRef(this, ref); } + + jint EnsureLocalCapacity(jint capacity) + { return functions->EnsureLocalCapacity(this, capacity); } + + jobject AllocObject(jclass clazz) + { return functions->AllocObject(this, clazz); } + + jobject NewObject(jclass clazz, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + jobject result = functions->NewObjectV(this, clazz, methodID, args); + va_end(args); + return result; + } + + jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args) + { return functions->NewObjectV(this, clazz, methodID, args); } + + jobject NewObjectA(jclass clazz, jmethodID methodID, jvalue* args) + { return functions->NewObjectA(this, clazz, methodID, args); } + + jclass GetObjectClass(jobject obj) + { return functions->GetObjectClass(this, obj); } + + jboolean IsInstanceOf(jobject obj, jclass clazz) + { return functions->IsInstanceOf(this, obj, clazz); } + + jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) + { return functions->GetMethodID(this, clazz, name, sig); } + +#define CALL_TYPE_METHOD(_jtype, _jname) \ + _jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->Call##_jname##MethodV(this, obj, methodID, \ + args); \ + va_end(args); \ + return result; \ + } +#define CALL_TYPE_METHODV(_jtype, _jname) \ + _jtype Call##_jname##MethodV(jobject obj, jmethodID methodID, \ + va_list args) \ + { return functions->Call##_jname##MethodV(this, obj, methodID, args); } +#define CALL_TYPE_METHODA(_jtype, _jname) \ + _jtype Call##_jname##MethodA(jobject obj, jmethodID methodID, \ + jvalue* args) \ + { return functions->Call##_jname##MethodA(this, obj, methodID, args); } + +#define CALL_TYPE(_jtype, _jname) \ + CALL_TYPE_METHOD(_jtype, _jname) \ + CALL_TYPE_METHODV(_jtype, _jname) \ + CALL_TYPE_METHODA(_jtype, _jname) + + CALL_TYPE(jobject, Object) + CALL_TYPE(jboolean, Boolean) + CALL_TYPE(jbyte, Byte) + CALL_TYPE(jchar, Char) + CALL_TYPE(jshort, Short) + CALL_TYPE(jint, Int) + CALL_TYPE(jlong, Long) + CALL_TYPE(jfloat, Float) + CALL_TYPE(jdouble, Double) + + void CallVoidMethod(jobject obj, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallVoidMethodV(this, obj, methodID, args); + va_end(args); + } + void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args) + { functions->CallVoidMethodV(this, obj, methodID, args); } + void CallVoidMethodA(jobject obj, jmethodID methodID, jvalue* args) + { functions->CallVoidMethodA(this, obj, methodID, args); } + +#define CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##Method(jobject obj, jclass clazz, \ + jmethodID methodID, ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->CallNonvirtual##_jname##MethodV(this, obj, \ + clazz, methodID, args); \ + va_end(args); \ + return result; \ + } +#define CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##MethodV(jobject obj, jclass clazz, \ + jmethodID methodID, va_list args) \ + { return functions->CallNonvirtual##_jname##MethodV(this, obj, clazz, \ + methodID, args); } +#define CALL_NONVIRT_TYPE_METHODA(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##MethodA(jobject obj, jclass clazz, \ + jmethodID methodID, jvalue* args) \ + { return functions->CallNonvirtual##_jname##MethodA(this, obj, clazz, \ + methodID, args); } + +#define CALL_NONVIRT_TYPE(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHODA(_jtype, _jname) + + CALL_NONVIRT_TYPE(jobject, Object) + CALL_NONVIRT_TYPE(jboolean, Boolean) + CALL_NONVIRT_TYPE(jbyte, Byte) + CALL_NONVIRT_TYPE(jchar, Char) + CALL_NONVIRT_TYPE(jshort, Short) + CALL_NONVIRT_TYPE(jint, Int) + CALL_NONVIRT_TYPE(jlong, Long) + CALL_NONVIRT_TYPE(jfloat, Float) + CALL_NONVIRT_TYPE(jdouble, Double) + + void CallNonvirtualVoidMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args); + va_end(args); + } + void CallNonvirtualVoidMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) + { functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args); } + void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, + jmethodID methodID, jvalue* args) + { functions->CallNonvirtualVoidMethodA(this, obj, clazz, methodID, args); } + + jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) + { return functions->GetFieldID(this, clazz, name, sig); } + + jobject GetObjectField(jobject obj, jfieldID fieldID) + { return functions->GetObjectField(this, obj, fieldID); } + jboolean GetBooleanField(jobject obj, jfieldID fieldID) + { return functions->GetBooleanField(this, obj, fieldID); } + jbyte GetByteField(jobject obj, jfieldID fieldID) + { return functions->GetByteField(this, obj, fieldID); } + jchar GetCharField(jobject obj, jfieldID fieldID) + { return functions->GetCharField(this, obj, fieldID); } + jshort GetShortField(jobject obj, jfieldID fieldID) + { return functions->GetShortField(this, obj, fieldID); } + jint GetIntField(jobject obj, jfieldID fieldID) + { return functions->GetIntField(this, obj, fieldID); } + jlong GetLongField(jobject obj, jfieldID fieldID) + { return functions->GetLongField(this, obj, fieldID); } + jfloat GetFloatField(jobject obj, jfieldID fieldID) + { return functions->GetFloatField(this, obj, fieldID); } + jdouble GetDoubleField(jobject obj, jfieldID fieldID) + { return functions->GetDoubleField(this, obj, fieldID); } + + void SetObjectField(jobject obj, jfieldID fieldID, jobject value) + { functions->SetObjectField(this, obj, fieldID, value); } + void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value) + { functions->SetBooleanField(this, obj, fieldID, value); } + void SetByteField(jobject obj, jfieldID fieldID, jbyte value) + { functions->SetByteField(this, obj, fieldID, value); } + void SetCharField(jobject obj, jfieldID fieldID, jchar value) + { functions->SetCharField(this, obj, fieldID, value); } + void SetShortField(jobject obj, jfieldID fieldID, jshort value) + { functions->SetShortField(this, obj, fieldID, value); } + void SetIntField(jobject obj, jfieldID fieldID, jint value) + { functions->SetIntField(this, obj, fieldID, value); } + void SetLongField(jobject obj, jfieldID fieldID, jlong value) + { functions->SetLongField(this, obj, fieldID, value); } + void SetFloatField(jobject obj, jfieldID fieldID, jfloat value) + { functions->SetFloatField(this, obj, fieldID, value); } + void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value) + { functions->SetDoubleField(this, obj, fieldID, value); } + + jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig) + { return functions->GetStaticMethodID(this, clazz, name, sig); } + +#define CALL_STATIC_TYPE_METHOD(_jtype, _jname) \ + _jtype CallStatic##_jname##Method(jclass clazz, jmethodID methodID, \ + ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->CallStatic##_jname##MethodV(this, clazz, \ + methodID, args); \ + va_end(args); \ + return result; \ + } +#define CALL_STATIC_TYPE_METHODV(_jtype, _jname) \ + _jtype CallStatic##_jname##MethodV(jclass clazz, jmethodID methodID, \ + va_list args) \ + { return functions->CallStatic##_jname##MethodV(this, clazz, methodID, \ + args); } +#define CALL_STATIC_TYPE_METHODA(_jtype, _jname) \ + _jtype CallStatic##_jname##MethodA(jclass clazz, jmethodID methodID, \ + jvalue* args) \ + { return functions->CallStatic##_jname##MethodA(this, clazz, methodID, \ + args); } + +#define CALL_STATIC_TYPE(_jtype, _jname) \ + CALL_STATIC_TYPE_METHOD(_jtype, _jname) \ + CALL_STATIC_TYPE_METHODV(_jtype, _jname) \ + CALL_STATIC_TYPE_METHODA(_jtype, _jname) + + CALL_STATIC_TYPE(jobject, Object) + CALL_STATIC_TYPE(jboolean, Boolean) + CALL_STATIC_TYPE(jbyte, Byte) + CALL_STATIC_TYPE(jchar, Char) + CALL_STATIC_TYPE(jshort, Short) + CALL_STATIC_TYPE(jint, Int) + CALL_STATIC_TYPE(jlong, Long) + CALL_STATIC_TYPE(jfloat, Float) + CALL_STATIC_TYPE(jdouble, Double) + + void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallStaticVoidMethodV(this, clazz, methodID, args); + va_end(args); + } + void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args) + { functions->CallStaticVoidMethodV(this, clazz, methodID, args); } + void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, jvalue* args) + { functions->CallStaticVoidMethodA(this, clazz, methodID, args); } + + jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig) + { return functions->GetStaticFieldID(this, clazz, name, sig); } + + jobject GetStaticObjectField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticObjectField(this, clazz, fieldID); } + jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticBooleanField(this, clazz, fieldID); } + jbyte GetStaticByteField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticByteField(this, clazz, fieldID); } + jchar GetStaticCharField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticCharField(this, clazz, fieldID); } + jshort GetStaticShortField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticShortField(this, clazz, fieldID); } + jint GetStaticIntField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticIntField(this, clazz, fieldID); } + jlong GetStaticLongField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticLongField(this, clazz, fieldID); } + jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticFloatField(this, clazz, fieldID); } + jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticDoubleField(this, clazz, fieldID); } + + void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value) + { functions->SetStaticObjectField(this, clazz, fieldID, value); } + void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value) + { functions->SetStaticBooleanField(this, clazz, fieldID, value); } + void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value) + { functions->SetStaticByteField(this, clazz, fieldID, value); } + void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value) + { functions->SetStaticCharField(this, clazz, fieldID, value); } + void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value) + { functions->SetStaticShortField(this, clazz, fieldID, value); } + void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value) + { functions->SetStaticIntField(this, clazz, fieldID, value); } + void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value) + { functions->SetStaticLongField(this, clazz, fieldID, value); } + void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value) + { functions->SetStaticFloatField(this, clazz, fieldID, value); } + void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value) + { functions->SetStaticDoubleField(this, clazz, fieldID, value); } + + jstring NewString(const jchar* unicodeChars, jsize len) + { return functions->NewString(this, unicodeChars, len); } + + jsize GetStringLength(jstring string) + { return functions->GetStringLength(this, string); } + + const jchar* GetStringChars(jstring string, jboolean* isCopy) + { return functions->GetStringChars(this, string, isCopy); } + + void ReleaseStringChars(jstring string, const jchar* chars) + { functions->ReleaseStringChars(this, string, chars); } + + jstring NewStringUTF(const char* bytes) + { return functions->NewStringUTF(this, bytes); } + + jsize GetStringUTFLength(jstring string) + { return functions->GetStringUTFLength(this, string); } + + const char* GetStringUTFChars(jstring string, jboolean* isCopy) + { return functions->GetStringUTFChars(this, string, isCopy); } + + void ReleaseStringUTFChars(jstring string, const char* utf) + { functions->ReleaseStringUTFChars(this, string, utf); } + + jsize GetArrayLength(jarray array) + { return functions->GetArrayLength(this, array); } + + jobjectArray NewObjectArray(jsize length, jclass elementClass, + jobject initialElement) + { return functions->NewObjectArray(this, length, elementClass, + initialElement); } + + jobject GetObjectArrayElement(jobjectArray array, jsize index) + { return functions->GetObjectArrayElement(this, array, index); } + + void SetObjectArrayElement(jobjectArray array, jsize index, jobject value) + { functions->SetObjectArrayElement(this, array, index, value); } + + jbooleanArray NewBooleanArray(jsize length) + { return functions->NewBooleanArray(this, length); } + jbyteArray NewByteArray(jsize length) + { return functions->NewByteArray(this, length); } + jcharArray NewCharArray(jsize length) + { return functions->NewCharArray(this, length); } + jshortArray NewShortArray(jsize length) + { return functions->NewShortArray(this, length); } + jintArray NewIntArray(jsize length) + { return functions->NewIntArray(this, length); } + jlongArray NewLongArray(jsize length) + { return functions->NewLongArray(this, length); } + jfloatArray NewFloatArray(jsize length) + { return functions->NewFloatArray(this, length); } + jdoubleArray NewDoubleArray(jsize length) + { return functions->NewDoubleArray(this, length); } + + jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy) + { return functions->GetBooleanArrayElements(this, array, isCopy); } + jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy) + { return functions->GetByteArrayElements(this, array, isCopy); } + jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy) + { return functions->GetCharArrayElements(this, array, isCopy); } + jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy) + { return functions->GetShortArrayElements(this, array, isCopy); } + jint* GetIntArrayElements(jintArray array, jboolean* isCopy) + { return functions->GetIntArrayElements(this, array, isCopy); } + jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy) + { return functions->GetLongArrayElements(this, array, isCopy); } + jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy) + { return functions->GetFloatArrayElements(this, array, isCopy); } + jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy) + { return functions->GetDoubleArrayElements(this, array, isCopy); } + + void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems, + jint mode) + { functions->ReleaseBooleanArrayElements(this, array, elems, mode); } + void ReleaseByteArrayElements(jbyteArray array, jbyte* elems, + jint mode) + { functions->ReleaseByteArrayElements(this, array, elems, mode); } + void ReleaseCharArrayElements(jcharArray array, jchar* elems, + jint mode) + { functions->ReleaseCharArrayElements(this, array, elems, mode); } + void ReleaseShortArrayElements(jshortArray array, jshort* elems, + jint mode) + { functions->ReleaseShortArrayElements(this, array, elems, mode); } + void ReleaseIntArrayElements(jintArray array, jint* elems, + jint mode) + { functions->ReleaseIntArrayElements(this, array, elems, mode); } + void ReleaseLongArrayElements(jlongArray array, jlong* elems, + jint mode) + { functions->ReleaseLongArrayElements(this, array, elems, mode); } + void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems, + jint mode) + { functions->ReleaseFloatArrayElements(this, array, elems, mode); } + void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems, + jint mode) + { functions->ReleaseDoubleArrayElements(this, array, elems, mode); } + + void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + jboolean* buf) + { functions->GetBooleanArrayRegion(this, array, start, len, buf); } + void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, + jbyte* buf) + { functions->GetByteArrayRegion(this, array, start, len, buf); } + void GetCharArrayRegion(jcharArray array, jsize start, jsize len, + jchar* buf) + { functions->GetCharArrayRegion(this, array, start, len, buf); } + void GetShortArrayRegion(jshortArray array, jsize start, jsize len, + jshort* buf) + { functions->GetShortArrayRegion(this, array, start, len, buf); } + void GetIntArrayRegion(jintArray array, jsize start, jsize len, + jint* buf) + { functions->GetIntArrayRegion(this, array, start, len, buf); } + void GetLongArrayRegion(jlongArray array, jsize start, jsize len, + jlong* buf) + { functions->GetLongArrayRegion(this, array, start, len, buf); } + void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + jfloat* buf) + { functions->GetFloatArrayRegion(this, array, start, len, buf); } + void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + jdouble* buf) + { functions->GetDoubleArrayRegion(this, array, start, len, buf); } + + void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + const jboolean* buf) + { functions->SetBooleanArrayRegion(this, array, start, len, buf); } + void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, + const jbyte* buf) + { functions->SetByteArrayRegion(this, array, start, len, buf); } + void SetCharArrayRegion(jcharArray array, jsize start, jsize len, + const jchar* buf) + { functions->SetCharArrayRegion(this, array, start, len, buf); } + void SetShortArrayRegion(jshortArray array, jsize start, jsize len, + const jshort* buf) + { functions->SetShortArrayRegion(this, array, start, len, buf); } + void SetIntArrayRegion(jintArray array, jsize start, jsize len, + const jint* buf) + { functions->SetIntArrayRegion(this, array, start, len, buf); } + void SetLongArrayRegion(jlongArray array, jsize start, jsize len, + const jlong* buf) + { functions->SetLongArrayRegion(this, array, start, len, buf); } + void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + const jfloat* buf) + { functions->SetFloatArrayRegion(this, array, start, len, buf); } + void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + const jdouble* buf) + { functions->SetDoubleArrayRegion(this, array, start, len, buf); } + + jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, + jint nMethods) + { return functions->RegisterNatives(this, clazz, methods, nMethods); } + + jint UnregisterNatives(jclass clazz) + { return functions->UnregisterNatives(this, clazz); } + + jint MonitorEnter(jobject obj) + { return functions->MonitorEnter(this, obj); } + + jint MonitorExit(jobject obj) + { return functions->MonitorExit(this, obj); } + + jint GetJavaVM(JavaVM** vm) + { return functions->GetJavaVM(this, vm); } + + void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf) + { functions->GetStringRegion(this, str, start, len, buf); } + + void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf) + { return functions->GetStringUTFRegion(this, str, start, len, buf); } + + void* GetPrimitiveArrayCritical(jarray array, jboolean* isCopy) + { return functions->GetPrimitiveArrayCritical(this, array, isCopy); } + + void ReleasePrimitiveArrayCritical(jarray array, void* carray, jint mode) + { functions->ReleasePrimitiveArrayCritical(this, array, carray, mode); } + + const jchar* GetStringCritical(jstring string, jboolean* isCopy) + { return functions->GetStringCritical(this, string, isCopy); } + + void ReleaseStringCritical(jstring string, const jchar* carray) + { functions->ReleaseStringCritical(this, string, carray); } + + jweak NewWeakGlobalRef(jobject obj) + { return functions->NewWeakGlobalRef(this, obj); } + + void DeleteWeakGlobalRef(jweak obj) + { functions->DeleteWeakGlobalRef(this, obj); } + + jboolean ExceptionCheck() + { return functions->ExceptionCheck(this); } + + jobject NewDirectByteBuffer(void* address, jlong capacity) + { return functions->NewDirectByteBuffer(this, address, capacity); } + + void* GetDirectBufferAddress(jobject buf) + { return functions->GetDirectBufferAddress(this, buf); } + + jlong GetDirectBufferCapacity(jobject buf) + { return functions->GetDirectBufferCapacity(this, buf); } + + /* added in JNI 1.6 */ + jobjectRefType GetObjectRefType(jobject obj) + { return functions->GetObjectRefType(this, obj); } +#endif /*__cplusplus*/ +}; + + +/* + * JNI invocation interface. + */ +struct JNIInvokeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + + jint (*DestroyJavaVM)(JavaVM*); + jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*); + jint (*DetachCurrentThread)(JavaVM*); + jint (*GetEnv)(JavaVM*, void**, jint); + jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*); +}; + +/* + * C++ version. + */ +struct _JavaVM { + const struct JNIInvokeInterface* functions; + +#if defined(__cplusplus) + jint DestroyJavaVM() + { return functions->DestroyJavaVM(this); } + jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) + { return functions->AttachCurrentThread(this, p_env, thr_args); } + jint DetachCurrentThread() + { return functions->DetachCurrentThread(this); } + jint GetEnv(void** env, jint version) + { return functions->GetEnv(this, env, version); } + jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args) + { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); } +#endif /*__cplusplus*/ +}; + +struct JavaVMAttachArgs { + jint version; /* must be >= JNI_VERSION_1_2 */ + const char* name; /* NULL or name of thread as modified UTF-8 str */ + jobject group; /* global ref of a ThreadGroup object, or NULL */ +}; +typedef struct JavaVMAttachArgs JavaVMAttachArgs; + +/* + * JNI 1.2+ initialization. (As of 1.6, the pre-1.2 structures are no + * longer supported.) + */ +typedef struct JavaVMOption { + const char* optionString; + void* extraInfo; +} JavaVMOption; + +typedef struct JavaVMInitArgs { + jint version; /* use JNI_VERSION_1_2 or later */ + + jint nOptions; + JavaVMOption* options; + jboolean ignoreUnrecognized; +} JavaVMInitArgs; + +#ifdef __cplusplus +extern "C" { +#endif +/* + * VM initialization functions. + * + * Note these are the only symbols exported for JNI by the VM. + */ +jint JNI_GetDefaultJavaVMInitArgs(void*); +jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*); +jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*); + +#define JNIIMPORT +#define JNIEXPORT __attribute__ ((visibility ("default"))) +#define JNICALL + +/* + * Prototypes for functions exported by loadable shared libs. These are + * called by JNI, not provided by JNI. + */ +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved); +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved); + +#ifdef __cplusplus +} +#endif + + +/* + * Manifest constants. + */ +#define JNI_FALSE 0 +#define JNI_TRUE 1 + +#define JNI_VERSION_1_1 0x00010001 +#define JNI_VERSION_1_2 0x00010002 +#define JNI_VERSION_1_4 0x00010004 +#define JNI_VERSION_1_6 0x00010006 + +#define JNI_OK (0) /* no error */ +#define JNI_ERR (-1) /* generic error */ +#define JNI_EDETACHED (-2) /* thread detached from the VM */ +#define JNI_EVERSION (-3) /* JNI version error */ + +#define JNI_COMMIT 1 /* copy content, do not free buffer */ +#define JNI_ABORT 2 /* free buffer w/o copying back */ + +#endif /* JNI_H_ */ diff --git a/local.properties b/local.properties new file mode 100644 index 000000000..323ad260c --- /dev/null +++ b/local.properties @@ -0,0 +1,12 @@ +## This file is automatically generated by Android Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Tue May 29 22:12:21 BST 2018 +ndk.dir=/opt/android_sdk/ndk-bundle +sdk.dir=/opt/android_sdk diff --git a/package.json b/package.json new file mode 100644 index 000000000..e4c78c4d7 --- /dev/null +++ b/package.json @@ -0,0 +1,86 @@ +{ + "name": "sonar", + "private": true, + "version": "0.6.6", + "versionDate": "2018-4-12", + "description": "Mobile development tool", + "productName": "Sonar", + "author": "Facebook Inc", + "main": "src/index.js", + "icon": "icon.png", + "category": "facebook-intern", + "privileged": true, + "build": { + "appId": "sonar", + "productName": "Sonar", + "artifactName": "Sonar.${ext}", + "mac": { + "category": "public.app-category.developer-tools" + }, + "win": { + "publisherName": "Facebook, Inc." + } + }, + "devDependencies": { + "babel-eslint": "^8.2.1", + "electron": "^2.0.1", + "electron-builder": "^19.49.0", + "eslint": "^4.15.0", + "eslint-config-fbjs": "^2.0.1", + "eslint-plugin-babel": "^4.1.2", + "eslint-plugin-flowtype": "^2.41.0", + "eslint-plugin-header": "^1.2.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-prettier": "^2.4.0", + "eslint-plugin-react": "^7.5.1", + "eslint-plugin-relay": "^0.0.20", + "flow-bin": "^0.69.0", + "glob": "^7.1.2", + "prettier": "1.12.1" + }, + "dependencies": { + "JSONStream": "^1.3.1", + "adbkit-fb": "2.10.1", + "ansi-to-html": "^0.6.3", + "chalk": "^2.3.0", + "codemirror": "^5.25.0", + "dashify": "^1.0.0", + "deep-equal": "^1.0.1", + "detect-port": "^1.1.1", + "electron-devtools-installer": "^2.2.0", + "express": "^4.15.2", + "fs-extra": "^5.0.0", + "invariant": "^2.2.2", + "jest": "^22.2.1", + "mkdirp": "^0.5.1", + "openssl-wrapper": "^0.3.4", + "prop-types": "^15.6.0", + "react": "16", + "react-color": "^2.11.7", + "react-devtools-core": "3.1.0", + "react-dom": "16", + "react-redux": "^5.0.7", + "react-test-renderer": "^16", + "react-virtualized": "^9.13.0", + "redux": "^4.0.0", + "rsocket-core": "^0.0.6", + "rsocket-tcp-server": "^0.0.6", + "socket.io": "^2.0.4", + "string-natural-compare": "^2.0.2", + "tmp": "^0.0.33", + "websocket": "^1.0.24", + "ws": "^4.0.0", + "yargs": "^11.0.0" + }, + "scripts": { + "postinstall": "node node_modules/electron/install.js && node scripts/yarn-install.js", + "rm-dist": "rm -rf dist", + "rm-modules": "rm -rf node_modules static/node_modules", + "rm-temp": "rm -rf $TMPDIR/jest* $TMPDIR/react-native-packager*", + "reset": "yarn cache clean && yarn rm-dist && yarn rm-modules && yarn rm-temp", + "start": "NODE_ENV=development node scripts/start-dev-server.js", + "build": "yarn rm-dist && NODE_ENV=production node scripts/build-release.js $@", + "fix": "eslint . --fix", + "lint": "eslint . && flow check" + } +} diff --git a/scripts/build-release.js b/scripts/build-release.js new file mode 100755 index 000000000..07e150010 --- /dev/null +++ b/scripts/build-release.js @@ -0,0 +1,205 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +const path = require('path'); +const tmp = require('tmp'); +const fs = require('fs-extra'); +const builder = require('electron-builder'); +const Platform = builder.Platform; +const metro = require('../static/node_modules/metro'); +const compilePlugins = require('../static/compilePlugins'); + +function generateManifest(versionNumber) { + const filePath = path.join(__dirname, '..', 'dist'); + if (!fs.existsSync(filePath)) { + fs.mkdirSync(filePath); + } + fs.writeFileSync( + path.join(__dirname, '../dist/manifest.json'), + JSON.stringify({ + package: 'com.facebook.sonar', + version_name: versionNumber, + }), + ); +} + +function buildFolder() { + // eslint-disable-next-line no-console + console.log('Creating build directory'); + return new Promise((resolve, reject) => { + tmp.dir((err, buildFolder) => { + if (err) { + reject(err); + } else { + resolve(buildFolder); + } + }); + }).catch(die); +} + +function modifyPackageManifest(buildFolder) { + // eslint-disable-next-line no-console + console.log('Creating package.json manifest'); + const manifest = require('../package.json'); + const manifestStatic = require('../static/package.json'); + + // The manifest's dependencies are bundled with the final app by + // electron-builder. We want to bundle the dependencies from the static-folder + // because all dependencies from the root-folder are already bundled by metro. + manifest.dependencies = manifestStatic.dependencies; + manifest.main = 'index.js'; + + const BUILD_NUMBER_ARG = 'build-number='; + const buildNumber = ( + process.argv.find(arg => arg.startsWith(BUILD_NUMBER_ARG)) || '' + ).replace(BUILD_NUMBER_ARG, ''); + if (buildNumber) { + manifest.version = [ + ...manifest.version.split('.').slice(0, 2), + buildNumber, + ].join('.'); + } + + return new Promise((resolve, reject) => { + fs.writeFile( + path.join(buildFolder, 'package.json'), + JSON.stringify(manifest, null, ' '), + err => { + if (err) { + reject(err); + } else { + resolve(manifest.version); + } + }, + ); + }).catch(die); +} + +function buildDist(buildFolder) { + const targetsRaw = []; + targetsRaw.push(Platform.MAC.createTarget(['zip'])); + if (process.argv.slice(2).indexOf('macOnly') === -1) { + targetsRaw.push(Platform.LINUX.createTarget(['dir'])); + targetsRaw.push(Platform.WINDOWS.createTarget(['dir'])); + } + + if (!targetsRaw.length) { + throw new Error('No targets specified. eg. --osx pkg,dmg --linux tar.gz'); + } + + // merge all target maps into a single map + let targetsMerged = []; + for (const target of targetsRaw) { + targetsMerged = targetsMerged.concat(Array.from(target)); + } + const targets = new Map(targetsMerged); + + const electronDownload = {}; + if (process.env.electron_config_cache) { + electronDownload.cache = process.env.electron_config_cache; + } + + return builder + .build({ + appDir: buildFolder, + config: { + appId: `com.facebook.sonar`, + directories: { + buildResources: path.join(__dirname, '..', 'static'), + output: path.join(__dirname, '..', 'dist'), + }, + electronDownload, + npmRebuild: false, + asarUnpack: 'PortForwardingMacApp.app/**/*', + }, + projectDir: buildFolder, + targets, + }) + .catch(die); +} + +function die(err) { + console.error(err.stack); + process.exit(1); +} + +function compile(buildFolder) { + // eslint-disable-next-line no-console + console.log( + 'Building main bundle', + path.join(__dirname, '..', 'src', 'init.js'), + ); + return metro + .runBuild({ + config: { + getProjectRoots: () => [path.join(__dirname, '..')], + getTransformModulePath: () => + path.join(__dirname, '..', 'static', 'transforms', 'index.js'), + }, + resetCache: true, + dev: false, + entry: path.join(__dirname, '..', 'src', 'init.js'), + out: path.join(buildFolder, 'bundle.js'), + }) + .catch(die); +} + +function copyStaticFolder(buildFolder) { + return new Promise((resolve, reject) => { + fs.copy( + path.join(__dirname, '..', 'static'), + buildFolder, + { + dereference: true, + }, + err => { + if (err) { + reject(err); + } else { + resolve(); + } + }, + ); + }).catch(die); +} + +function compileDefaultPlugins(buildFolder) { + const defaultPluginFolder = 'defaultPlugins'; + const defaultPluginDir = path.join(buildFolder, defaultPluginFolder); + return compilePlugins( + null, + [ + path.join(__dirname, '..', 'src', 'plugins'), + path.join(__dirname, '..', 'src', 'fb', 'plugins'), + ], + defaultPluginDir, + ).then(defaultPlugins => + fs.writeFileSync( + path.join(defaultPluginDir, 'index.json'), + JSON.stringify( + defaultPlugins.map(plugin => ({ + ...plugin, + out: path.join(defaultPluginFolder, path.parse(plugin.out).base), + })), + ), + ), + ); +} + +(async () => { + const dir = await buildFolder(); + // eslint-disable-next-line no-console + console.log('Created build directory', dir); + await copyStaticFolder(dir); + await compileDefaultPlugins(dir); + await compile(dir); + const versionNumber = await modifyPackageManifest(dir); + generateManifest(versionNumber); + await buildDist(dir); + // eslint-disable-next-line no-console + console.log('✨ Done'); + process.exit(); +})(); diff --git a/scripts/eslint.sh b/scripts/eslint.sh new file mode 100755 index 000000000..ea28635f7 --- /dev/null +++ b/scripts/eslint.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -e + +# This script is used by `arc lint`. + +THIS_DIR=$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ROOT_DIR=$(cd "$THIS_DIR" && hg root) + +cd "$ROOT_DIR/xplat/sonar" + +# Sonar's Electron dependency downloads itself via a post-install script. +# When running in Sandcastle or devservers, the module install will fail +# because we can't reach the internet. Setting the fwdproxy is dangerous, so +# the next best thing is to install the modules with `--ignore-scripts`. +# However, we can't run `install-node-modules.sh` like this all of the time. +# `install-node-modules.sh` uses its args as keys for the "yarn watchman check" +# cache. So if we run `install-node-modules.sh` outside of this script without +# the flag, but then this script runs it with the flag, we're going to +# invalidate the cache. + +# If `node_modules` exists, we can't tell if it was created with +# `--ignore-scripts` or not, so we play it safe, and avoid touching it. +if [[ ! -d "node_modules" ]]; then + "$ROOT_DIR/xplat/third-party/yarn/install-node-modules.sh" --ignore-scripts +fi + +exec \ + "$ROOT_DIR/xplat/third-party/node/bin/node" \ + "$ROOT_DIR/xplat/sonar/node_modules/.bin/eslint" \ + "$@" diff --git a/scripts/flow.sh b/scripts/flow.sh new file mode 100755 index 000000000..0f6bbb1fa --- /dev/null +++ b/scripts/flow.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +# This script is used by `arc lint`. + +THIS_DIR=$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd) +ROOT_DIR=$(cd "$THIS_DIR" && hg root) + +cd "$ROOT_DIR/xplat/sonar" + +# Sonar's Electron dependency downloads itself via a post-install script. +# When running in Sandcastle or devservers, the module install will fail +# because we can't reach the internet. Setting the fwdproxy is dangerous, so +# the next best thing is to install the modules with `--ignore-scripts`. +# However, we can't run `install-node-modules.sh` like this all of the time. +# `install-node-modules.sh` uses its args as keys for the "yarn watchman check" +# cache. So if we run `install-node-modules.sh` outside of this script without +# the flag, but then this script runs it with the flag, we're going to +# invalidate the cache. + +# If `node_modules` exists, we can't tell if it was created with +# `--ignore-scripts` or not, so we play it safe, and avoid touching it. +if [[ ! -d "node_modules" ]]; then + "$ROOT_DIR/xplat/third-party/yarn/install-node-modules.sh" --ignore-scripts +fi + +# Prefer the internal version of Flow, which should be in the PATH - but +# fallback to the OSS version (this is needed in Sandcastle). +FLOW_BINARY="$(which flow 2>/dev/null || echo "$ROOT_DIR/xplat/sonar/node_modules/.bin/flow")" + +exec "$FLOW_BINARY" "$@" diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh new file mode 100755 index 000000000..5d90c2ef8 --- /dev/null +++ b/scripts/install-dependencies.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +set -e + +main () { + local -r THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + ROOT_DIR=$(cd "$THIS_DIR" && hg root) + source "$ROOT_DIR/xplat/sonar/scripts/setup-env.sh" + + # save current cursor location + printf "Ensuring correct dependencies..." + + PREV_DIR="`pwd`" + + # install dependencies + cd "$INFINITY_DIR" + "$INSTALL_NODE_MODULES" + + # ensure electron gets installed + node node_modules/electron/install.js + + # go back + cd "$PREV_DIR" + + # remove correct dependencies log + printf "\r" +} + +main diff --git a/scripts/metro-transform.js b/scripts/metro-transform.js new file mode 100644 index 000000000..fae367ee4 --- /dev/null +++ b/scripts/metro-transform.js @@ -0,0 +1,63 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +const generate = require('babel-generator').default; +const babylon = require('babylon'); +const babel = require('babel-core'); +const metro = require('metro'); + +exports.transform = function({ + filename, + options, + src, + plugins: defaultPlugins, +}) { + const presets = []; + + let ast = babylon.parse(src, { + filename, + plugins: ['jsx', 'flow', 'classProperties', 'objectRestSpread'], + sourceType: filename.includes('node_modules') ? 'script' : 'module', + }); + + // run babel + const plugins = [ + ...defaultPlugins, + require('./babel-plugins/electron-requires.js'), + require('./babel-plugins/dynamic-requires.js'), + ]; + if (!filename.includes('node_modules')) { + plugins.unshift(require('babel-plugin-transform-es2015-modules-commonjs')); + } + ast = babel.transformFromAst(ast, src, { + babelrc: !filename.includes('node_modules'), + code: false, + comments: false, + compact: false, + filename, + plugins, + presets, + sourceMaps: true, + }).ast; + + const result = generate( + ast, + { + filename, + sourceFileName: filename, + sourceMaps: true, + }, + src, + ); + + return { + ast, + code: result.code, + filename, + map: result.rawMappings.map(metro.sourceMaps.compactMapping), + }; +}; diff --git a/scripts/public-build.json b/scripts/public-build.json new file mode 100644 index 000000000..40db5cde3 --- /dev/null +++ b/scripts/public-build.json @@ -0,0 +1,20 @@ +{ + "command": "SandcastleUniversalCommand", + "args": { + "name": "Release public Sonar build", + "oncall": "danielbuechele", + "steps": [ + { + "name": "sonar_release_public_build", + "required": true, + "shell": "cd ../xplat/sonar/scripts && ./public-build.sh" + } + ] + }, + "alias": "sonar_release_public_build", + "capabilities": { + "vcs": "fbcode-fbsource", + "type": "lego" + }, + "hash": "master" +} diff --git a/scripts/public-build.sh b/scripts/public-build.sh new file mode 100755 index 000000000..59b3d6843 --- /dev/null +++ b/scripts/public-build.sh @@ -0,0 +1,46 @@ +#!/bin/bash +TOKEN=$(secrets_tool get SONAR_GITHUB_TOKEN) +GITHUB_ORG="facebook" +GITHUB_REPO="Sonar" + +cd ../../ || exit + +function jsonValue() { + python -c 'import json,sys;obj=json.load(sys.stdin);print obj["'$1'"]' || echo '' +} + +git -c http.proxy=fwdproxy:8080 -c https.proxy=fwdproxy:8080 clone https://github.com/facebook/Sonar.git sonar-public +cp sonar/scripts/sandcastle-build.sh sonar-public/scripts/sandcastle-build.sh +cd sonar-public/scripts && ./sandcastle-build.sh "$(git rev-list HEAD --count || echo 0)" + +VERSION=$(plutil -p ./sonar-public/dist/mac/Sonar.app/Contents/Info.plist | awk '/CFBundleShortVersionString/ {print substr($3, 2, length($3)-2)}') + +RELEASE_JSON=$(curl $(fwdproxy-config curl) --silent --data '{ + "tag_name": "v'$VERSION'", + "target_commitish": "master", + "name": "v'$VERSION'", + "body": "", + "draft": false, + "prerelease": false +}' https://api.github.com/repos/$GITHUB_ORG/$GITHUB_REPO/releases?access_token=$TOKEN) + +RELEASE_ID=$(echo $RELEASE_JSON | jsonValue id) + +if [ -z "${RELEASE_ID}" ]; then + echo $RELEASE_JSON + exit 1 +fi + +echo "Created GitHub release ID: $RELEASE_ID" +UPLOAD_URL=$(echo $RELEASE_JSON | jsonValue upload_url| sed -e 's#{?name,label}##') +ASSET_JSON=$(curl $(fwdproxy-config curl) --silent $UPLOAD_URL'?access_token='$TOKEN'&name=Sonar.zip' --header 'Content-Type: application/zip' --upload-file ./sonar-public/dist/Sonar.zip -X POST) + +DOWNLOAD_URL=$(echo $ASSET_JSON | jsonValue browser_download_url) + +if [ -z "${DOWNLOAD_URL}" ]; then + echo $ASSET_JSON + exit 1 +fi + +echo "Released Sonar v$VERSION" +echo "Download: $DOWNLOAD_URL" diff --git a/scripts/setup-env.sh b/scripts/setup-env.sh new file mode 100755 index 000000000..a3882b90e --- /dev/null +++ b/scripts/setup-env.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +set -e + +main () { + local -r THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + ROOT_DIR=$(cd "$THIS_DIR" && hg root) + + source "$ROOT_DIR/xplat/js/env-utils/setup_env_vars.sh" + + export SONAR_DIR="$ROOT_DIR/xplat/infinity" + export PATH="$SONAR_DIR/node_modules/.bin:$ROOT_DIR/xplat/third-party/node/bin:$ROOT_DIR/xplat/third-party/yarn:$PATH" +} + +main diff --git a/scripts/start-dev-server.js b/scripts/start-dev-server.js new file mode 100644 index 000000000..807d640ff --- /dev/null +++ b/scripts/start-dev-server.js @@ -0,0 +1,176 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +const electronBinary = require('electron'); +const codeFrame = require('babel-code-frame'); +const socketIo = require('socket.io'); +const express = require('express'); +const detect = require('detect-port'); +const child = require('child_process'); +const Convert = require('ansi-to-html'); +const chalk = require('chalk'); +const http = require('http'); +const path = require('path'); +const metro = require('../static/node_modules/metro'); +const fs = require('fs'); + +const convertAnsi = new Convert(); + +const DEFAULT_PORT = process.env.PORT || 3000; +const STATIC_DIR = path.join(__dirname, '..', 'static'); + +function launchElectron({bundleURL, electronURL}) { + const args = [ + path.join(STATIC_DIR, 'index.js'), + '--remote-debugging-port=9222', + '--dynamicPlugins=~/fbsource/xplat/sonar/src/plugins,~/fbsource/xplat/sonar/src/fb/plugins', + ]; + + const proc = child.spawn(electronBinary, args, { + cwd: STATIC_DIR, + env: { + ...process.env, + SONAR_ROOT: process.cwd(), + BUNDLE_URL: bundleURL, + ELECTRON_URL: electronURL, + }, + stdio: 'inherit', + }); + + proc.on('close', () => { + process.exit(); + }); + + process.on('exit', () => { + proc.kill(); + }); +} + +function startMetroServer(port) { + return metro.runServer({ + port, + watch: true, + config: { + getProjectRoots: () => [path.join(__dirname, '..')], + getTransformModulePath: () => + path.join(__dirname, '..', 'static', 'transforms', 'index.js'), + }, + }); +} + +function startAssetServer(port) { + const app = express(); + + app.use((req, res, next) => { + if (knownErrors[req.url] != null) { + delete knownErrors[req.url]; + outputScreen(); + } + next(); + }); + + app.get('/', (req, res) => { + fs.readFile(path.join(STATIC_DIR, 'index.dev.html'), (err, content) => { + res.end(content); + }); + }); + + app.use(express.static(STATIC_DIR)); + + app.use(function(err, req, res, next) { + knownErrors[req.url] = err; + outputScreen(); + res.status(500).send('Something broke, check the console!'); + }); + + const server = http.createServer(app); + + return new Promise((resolve, reject) => { + server.listen(port, () => resolve(server)); + }); +} + +function addWebsocket(server) { + const io = socketIo(server); + + // notify connected clients that there's errors in the console + io.on('connection', client => { + if (hasErrors()) { + client.emit('hasErrors', convertAnsi.toHtml(buildErrorScreen())); + } + }); + + // refresh the app on changes to the src folder + // this can be removed once metroServer notifies us about file changes + fs.watch(path.join(__dirname, '..', 'src'), () => { + io.emit('refresh'); + }); + + return io; +} + +const knownErrors = {}; + +function hasErrors() { + return Object.keys(knownErrors).length > 0; +} + +function buildErrorScreen() { + const lines = [ + chalk.red(`✖ Found ${Object.keys(knownErrors).length} errors`), + '', + ]; + + for (const url in knownErrors) { + const err = knownErrors[url]; + + if (err.filename != null && err.lineNumber != null && err.column != null) { + lines.push(chalk.inverse(err.filename)); + lines.push(); + lines.push(err.message); + lines.push( + codeFrame( + fs.readFileSync(err.filename, 'utf8'), + err.lineNumber, + err.column, + ), + ); + } else { + lines.push(err.stack); + } + + lines.push(''); + } + + return lines.join('\n'); +} + +function outputScreen(socket) { + // output screen + if (hasErrors()) { + const errorScreen = buildErrorScreen(); + console.error(errorScreen); + + // notify live clients of errors + socket.emit('hasErrors', convertAnsi.toHtml(errorScreen)); + } else { + // eslint-disable-next-line no-console + console.log(chalk.green('✔ No known errors')); + } +} + +(async () => { + const assetServerPort = await detect(DEFAULT_PORT); + const assetServer = await startAssetServer(assetServerPort); + const socket = addWebsocket(assetServer); + const metroServerPort = await detect(DEFAULT_PORT + 1); + await startMetroServer(metroServerPort); + outputScreen(socket); + launchElectron({ + bundleURL: `http://localhost:${metroServerPort}/src/init.bundle`, + electronURL: `http://localhost:${assetServerPort}/index.dev.html`, + }); +})(); diff --git a/scripts/yarn-install.js b/scripts/yarn-install.js new file mode 100644 index 000000000..03a6989e1 --- /dev/null +++ b/scripts/yarn-install.js @@ -0,0 +1,48 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +const glob = require('glob'); +const path = require('path'); +const {spawn} = require('child_process'); +const PACKAGES = ['static', 'src/plugins/*', 'src/fb/plugins/*']; +const YARN_PATH = + process.argv.length > 2 ? path.join(__dirname, process.argv[2]) : 'yarn'; + +Promise.all( + PACKAGES.map( + pattern => + new Promise((resolve, reject) => { + glob( + path.join(__dirname, '..', pattern, 'package.json'), + (err, matches) => { + if (err) { + reject(err); + } else { + resolve(matches); + } + }, + ); + }), + ), +) + .then(packages => + Promise.all( + packages.reduce((acc, cv) => acc.concat(cv), []).map( + pkg => + new Promise(resolve => { + const cwd = pkg.replace('/package.json', ''); + const yarn = spawn(YARN_PATH, ['--mutex', 'file'], { + cwd, + }); + yarn.stderr.on('data', e => console.error(e.toString())); + yarn.on('close', code => resolve(code)); + }), + ), + ), + ) + // eslint-disable-next-line + .then(() => console.log('📦 Installed all dependencies!')); diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..f004675f2 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,29 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user guide at https://docs.gradle.org/4.7/userguide/multi_project_builds.html + */ + +rootProject.name = 'sonar-kit' + +include ':android' +include ':folly' +include ':fbjni' +include ':easywsclient' +include ':sonarcpp' +include ':sample' +include ':doubleconversion' +include ':glog' + + +project(':fbjni').projectDir = file('libs/fbjni') +project(':easywsclient').projectDir = file('libs/easywsclient') +project(':sonarcpp').projectDir = file('xplat') +project(':sample').projectDir = file('android/sample') +project(':android').projectDir = file('android') +project(':doubleconversion').projectDir = file('android/build/third-party-ndk/double-conversion/') +project(':glog').projectDir = file('android/build/third-party-ndk/glog/') +project(':folly').projectDir = file('android/build/third-party-ndk/folly/') diff --git a/src/App.js b/src/App.js new file mode 100644 index 000000000..74e0bcbd6 --- /dev/null +++ b/src/App.js @@ -0,0 +1,363 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +import {ErrorBoundary, FlexColumn, FlexRow} from 'sonar'; +import {connect} from 'react-redux'; +import {toggleBugDialogVisible} from './reducers/application.js'; +import {setupMenu, activateMenuItems} from './MenuBar.js'; +import {devicePlugins} from './device-plugins/index.js'; +import WelcomeScreen from './chrome/WelcomeScreen.js'; +import SonarTitleBar from './chrome/SonarTitleBar.js'; +import BaseDevice from './devices/BaseDevice.js'; +import MainSidebar from './chrome/MainSidebar.js'; +import {SonarBasePlugin} from './plugin.js'; +import {Server, Client} from './server.js'; +import * as reducers from './reducers.js'; +import React from 'react'; +import BugReporter from './fb-stubs/BugReporter.js'; +import ErrorReporter from './fb-stubs/ErrorReporter.js'; +import BugReporterDialog from './chrome/BugReporterDialog.js'; +import ErrorBar from './chrome/ErrorBar.js'; +import Logger from './fb-stubs/Logger.js'; +import PluginContainer from './PluginContainer.js'; +import PluginManager from './chrome/PluginManager.js'; +const electron = require('electron'); +const yargs = require('yargs'); + +export type {Client}; + +export type StatePluginInfo = { + plugin: ?SonarBasePlugin<>, + state: Object, +}; + +export type StateClientPlugins = { + [pluginKey: string]: StatePluginInfo, +}; + +export type StatePlugins = { + [appKey: string]: StateClientPlugins, +}; + +export type State = { + activePluginKey: ?string, + activeAppKey: ?string, + plugins: StatePlugins, + error: ?string, + server: Server, +}; + +type Props = { + devices: Array, + leftSidebarVisible: boolean, + bugDialogVisible: boolean, + pluginManagerVisible: boolean, + toggleBugDialogVisible: (visible?: boolean) => void, +}; + +export class App extends React.Component { + constructor() { + performance.mark('init'); + super(); + this.initTracking(); + + this.logger = new Logger(); + + this.state = { + activeAppKey: null, + activePluginKey: null, + error: null, + devices: {}, + plugins: {}, + server: this.initServer(), + }; + + this.errorReporter = new ErrorReporter(this.logger.scribeLogger); + this.bugReporter = new BugReporter(this.logger); + this.commandLineArgs = yargs.parse(electron.remote.process.argv); + + setupMenu(this.sendKeyboardAction); + } + + errorReporter: ErrorReporter; + bugReporter: BugReporter; + logger: Logger; + commandLineArgs: Object; + _hasActivatedPreferredPlugin: boolean = false; + + componentDidMount() { + this.logger.trackTimeSince('init'); + + // close socket before reloading + window.addEventListener('beforeunload', () => { + this.state.server.close(); + }); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.devices !== this.props.devices) { + this.ensurePluginSelected(); + } + } + + toJSON() { + return null; + } + + initServer(): Server { + const server = new Server(this); + + server.addListener('new-client', client => { + client.addListener('close', () => { + this.setState(state => + reducers.TeardownClient(this, state, {appKey: client.id}), + ); + if (this.state.activeAppKey === client.id) { + setTimeout(this.ensurePluginSelected); + } + }); + + client.addListener('plugins-change', () => { + this.setState({}, this.ensurePluginSelected); + }); + }); + + server.addListener('clients-change', () => { + this.setState({}, this.ensurePluginSelected); + }); + + server.addListener('error', err => { + if (err.code === 'EADDRINUSE') { + this.setState({ + error: + "Couldn't start websocket server. " + + 'Looks like you have multiple copies of Sonar running.', + }); + } else { + // unknown error + this.setState({ + error: err.message, + }); + } + }); + + return server; + } + + initTracking = () => { + electron.ipcRenderer.on('trackUsage', () => { + // check if there's a plugin currently active + const {activeAppKey, activePluginKey} = this.state; + if (activeAppKey == null || activePluginKey == null) { + return; + } + + // app plugins + const client = this.getClient(activeAppKey); + if (client) { + this.logger.track('usage', 'ping', { + app: client.query.app, + device: client.query.device, + os: client.query.os, + plugin: activePluginKey, + }); + return; + } + + // device plugins + const device: ?BaseDevice = this.getDevice(activeAppKey); + if (device) { + this.logger.track('usage', 'ping', { + os: device.os, + plugin: activePluginKey, + device: device.title, + }); + } + }); + }; + + sendKeyboardAction = (action: string) => { + const {activeAppKey, activePluginKey} = this.state; + + if (activeAppKey != null && activePluginKey != null) { + const clientPlugins = this.state.plugins[activeAppKey]; + const pluginInfo = clientPlugins && clientPlugins[activePluginKey]; + const plugin = pluginInfo && pluginInfo.plugin; + if (plugin && typeof plugin.onKeyboardAction === 'function') { + plugin.onKeyboardAction(action); + } + } + }; + + getDevice = (id: string): ?BaseDevice => { + this.props.devices.find((device: BaseDevice) => device.serial === id); + }; + + ensurePluginSelected = () => { + // check if we need to rehydrate this client as it may have been previously active + const {activeAppKey, activePluginKey, server} = this.state; + const {devices} = this.props; + + if (!this._hasActivatedPreferredPlugin) { + for (const connection of server.connections.values()) { + const {client} = connection; + const {plugins} = client; + + for (const plugin of plugins) { + if (plugin !== this.commandLineArgs.plugin) { + continue; + } + + this._hasActivatedPreferredPlugin = true; + this.onActivatePlugin(client.id, plugin); + return; + } + } + + if (devices.length > 0) { + const device = devices[0]; + for (const plugin of devicePlugins) { + if (plugin.id !== this.commandLineArgs.plugin) { + continue; + } + + this._hasActivatedPreferredPlugin = true; + this.onActivatePlugin(device.serial, plugin.id); + return; + } + } + } + + if (activeAppKey != null && activePluginKey != null) { + const client = this.getClient(activeAppKey); + if (client != null && client.plugins.includes(activePluginKey)) { + this.onActivatePlugin(client.id, activePluginKey); + return; + } + + const device: ?BaseDevice = this.getDevice(activeAppKey); + if (device != null) { + this.onActivatePlugin(device.serial, activePluginKey); + return; + } + } else { + // No plugin selected, let's select one + const deviceList = ((Object.values(devices): any): Array); + if (deviceList.length > 0) { + const device = deviceList[0]; + this.onActivatePlugin(device.serial, devicePlugins[0].id); + return; + } + + const connections = Array.from(server.connections.values()); + if (connections.length > 0) { + const client = connections[0].client; + const plugins = client.plugins; + if (plugins.length > 0) { + this.onActivatePlugin(client.id, client.plugins[0]); + return; + } + } + } + }; + + getClient(appKey: ?string): ?Client { + if (appKey == null) { + return null; + } + + const info = this.state.server.connections.get(appKey); + if (info != null) { + return info.client; + } + } + + onActivatePlugin = (appKey: string, pluginKey: string) => { + activateMenuItems(pluginKey); + + this.setState(state => + reducers.ActivatePlugin(this, state, { + appKey, + pluginKey, + }), + ); + }; + + render() { + const {state} = this; + const hasDevices = + this.props.devices.length > 0 || state.server.connections.size > 0; + let mainView = null; + + const {activeAppKey, activePluginKey} = state; + if (activeAppKey != null && activePluginKey != null) { + const clientPlugins = state.plugins[activeAppKey]; + const pluginInfo = clientPlugins && clientPlugins[activePluginKey]; + const plugin = pluginInfo && pluginInfo.plugin; + if (plugin) { + mainView = this.props.pluginManagerVisible ? ( + + ) : ( + + + + ); + } + } + + return ( + + + {this.props.bugDialogVisible && ( + this.props.toggleBugDialogVisible(false)} + /> + )} + {hasDevices ? ( + + {this.props.leftSidebarVisible && ( + + )} + {mainView} + + ) : this.props.pluginManagerVisible ? ( + + ) : ( + + )} + + + ); + } +} + +export default connect( + ({ + application: {pluginManagerVisible, bugDialogVisible, leftSidebarVisible}, + devices, + }) => ({ + pluginManagerVisible, + bugDialogVisible, + leftSidebarVisible, + devices, + }), + {toggleBugDialogVisible}, +)(App); diff --git a/src/MenuBar.js b/src/MenuBar.js new file mode 100644 index 000000000..020e49f57 --- /dev/null +++ b/src/MenuBar.js @@ -0,0 +1,361 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type {SonarBasePlugin} from './plugin.js'; + +import {devicePlugins} from './device-plugins/index.js'; +import { + isProduction, + loadsDynamicPlugins, + toggleDynamicPluginLoading, +} from './utils/dynamicPluginLoading.js'; +import plugins from './plugins/index.js'; +import electron from 'electron'; + +export type DefaultKeyboardAction = 'clear' | 'goToBottom' | 'createPaste'; +export type TopLevelMenu = 'Edit' | 'View' | 'Window' | 'Help'; + +type MenuItem = {| + label?: string, + accelerator?: string, + role?: string, + click?: Function, + submenu?: Array, + type?: string, + enabled?: boolean, +|}; + +export type KeyboardAction = {| + action: string, + label: string, + accelerator?: string, + topLevelMenu: TopLevelMenu, +|}; + +const defaultKeyboardActions: Array = [ + { + label: 'Clear', + accelerator: 'CmdOrCtrl+K', + topLevelMenu: 'View', + action: 'clear', + }, + { + label: 'Go To Bottom', + accelerator: 'CmdOrCtrl+B', + topLevelMenu: 'View', + action: 'goToBottom', + }, + { + label: 'Create Paste', + topLevelMenu: 'Edit', + action: 'createPaste', + }, +]; + +export type KeyboardActions = Array; + +const menuItems: Map = new Map(); + +export function setupMenu(actionHandler: (action: string) => void) { + const template = getTemplate(electron.remote.app, electron.remote.shell); + + // collect all keyboard actions from all plugins + const registeredActions: Set = new Set( + [...devicePlugins, ...plugins] + .map((plugin: Class>) => plugin.keyboardActions || []) + .reduce((acc: KeyboardActions, cv) => acc.concat(cv), []) + .map( + (action: DefaultKeyboardAction | KeyboardAction) => + typeof action === 'string' + ? defaultKeyboardActions.find(a => a.action === action) + : action, + ), + ); + + // add keyboard actions to + registeredActions.forEach(keyboardAction => { + if (keyboardAction != null) { + appendMenuItem(template, actionHandler, keyboardAction); + } + }); + + // create actual menu instance + const applicationMenu = electron.remote.Menu.buildFromTemplate(template); + + // add menu items to map, so we can modify them easily later + registeredActions.forEach(keyboardAction => { + if (keyboardAction != null) { + const {topLevelMenu, label, action} = keyboardAction; + const menu = applicationMenu.items.find( + menuItem => menuItem.label === topLevelMenu, + ); + const menuItem = menu.submenu.items.find( + menuItem => menuItem.label === label, + ); + menuItems.set(action, menuItem); + } + }); + + // update menubar + electron.remote.Menu.setApplicationMenu(applicationMenu); +} + +function appendMenuItem( + template: Array, + actionHandler: (action: string) => void, + item: ?KeyboardAction, +) { + const keyboardAction = item; + if (keyboardAction == null) { + return; + } + const itemIndex = template.findIndex( + menu => menu.label === keyboardAction.topLevelMenu, + ); + if (itemIndex > -1 && template[itemIndex].submenu != null) { + template[itemIndex].submenu.push({ + click: () => actionHandler(keyboardAction.action), + label: keyboardAction.label, + accelerator: keyboardAction.accelerator, + enabled: false, + }); + } +} + +export function activateMenuItems(activePluginKey: ?string) { + const activePlugin: ?Class> = [ + ...devicePlugins, + ...plugins, + ].find((plugin: Class>) => plugin.id === activePluginKey); + + // disable all keyboard actions + for (const item of menuItems) { + item[1].enabled = false; + } + + // enable keyboard actions for the current plugin + if (activePlugin != null && activePlugin.keyboardActions != null) { + (activePlugin.keyboardActions || []).forEach(keyboardAction => { + const action = + typeof keyboardAction === 'string' + ? keyboardAction + : keyboardAction.action; + const item = menuItems.get(action); + if (item != null) { + item.enabled = true; + } + }); + } + + // set the application menu again to make sure it updates + electron.remote.Menu.setApplicationMenu( + electron.remote.Menu.getApplicationMenu(), + ); +} + +function getTemplate(app: Object, shell: Object): Array { + const template = [ + { + label: 'Edit', + submenu: [ + { + label: 'Undo', + accelerator: 'CmdOrCtrl+Z', + role: 'undo', + }, + { + label: 'Redo', + accelerator: 'Shift+CmdOrCtrl+Z', + role: 'redo', + }, + { + type: 'separator', + }, + { + label: 'Cut', + accelerator: 'CmdOrCtrl+X', + role: 'cut', + }, + { + label: 'Copy', + accelerator: 'CmdOrCtrl+C', + role: 'copy', + }, + { + label: 'Paste', + accelerator: 'CmdOrCtrl+V', + role: 'paste', + }, + { + label: 'Select All', + accelerator: 'CmdOrCtrl+A', + role: 'selectall', + }, + ], + }, + { + label: 'View', + submenu: [ + { + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + click: function(item: Object, focusedWindow: Object) { + if (focusedWindow) { + focusedWindow.reload(); + } + }, + }, + { + label: 'Toggle Full Screen', + accelerator: (function() { + if (process.platform === 'darwin') { + return 'Ctrl+Command+F'; + } else { + return 'F11'; + } + })(), + click: function(item: Object, focusedWindow: Object) { + if (focusedWindow) { + focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); + } + }, + }, + { + label: 'Toggle Developer Tools', + accelerator: (function() { + if (process.platform === 'darwin') { + return 'Alt+Command+I'; + } else { + return 'Ctrl+Shift+I'; + } + })(), + click: function(item: Object, focusedWindow: Object) { + if (focusedWindow) { + focusedWindow.toggleDevTools(); + } + }, + }, + { + type: 'separator', + }, + ], + }, + { + label: 'Window', + role: 'window', + submenu: [ + { + label: 'Minimize', + accelerator: 'CmdOrCtrl+M', + role: 'minimize', + }, + { + label: 'Close', + accelerator: 'CmdOrCtrl+W', + role: 'close', + }, + ], + }, + { + label: 'Help', + role: 'help', + submenu: [ + { + label: 'Getting started', + click: function() { + shell.openExternal('https://fbsonar.com/docs/getting-started.html'); + }, + }, + { + label: 'Create plugins', + click: function() { + shell.openExternal('https://fbsonar.com/docs/create-plugin.html'); + }, + }, + { + label: 'Report problems', + click: function() { + shell.openExternal('https://github.com/facebook/Sonar/issues'); + }, + }, + ], + }, + ]; + + if (process.platform === 'darwin') { + const name = app.getName(); + template.unshift({ + label: name, + submenu: [ + { + label: 'About ' + name, + role: 'about', + }, + { + type: 'separator', + }, + { + label: 'Services', + role: 'services', + submenu: [], + }, + { + type: 'separator', + }, + { + label: 'Hide ' + name, + accelerator: 'Command+H', + role: 'hide', + }, + { + label: 'Hide Others', + accelerator: 'Command+Shift+H', + role: 'hideothers', + }, + { + label: 'Show All', + role: 'unhide', + }, + { + type: 'separator', + }, + { + label: `Restart in ${ + loadsDynamicPlugins() ? 'Production' : 'Development' + } Mode`, + enabled: isProduction(), + click: function() { + toggleDynamicPluginLoading(); + }, + }, + { + label: 'Quit', + accelerator: 'Command+Q', + click: function() { + app.quit(); + }, + }, + ], + }); + const windowMenu = template.find(function(m: Object) { + return m.role === 'window'; + }); + if (windowMenu) { + windowMenu.submenu.push( + { + type: 'separator', + }, + { + label: 'Bring All to Front', + role: 'front', + }, + ); + } + } + + return template; +} diff --git a/src/PluginContainer.js b/src/PluginContainer.js new file mode 100644 index 000000000..6a0851057 --- /dev/null +++ b/src/PluginContainer.js @@ -0,0 +1,128 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +import {Component, FlexColumn, Sidebar, colors} from 'sonar'; +import Intro from './ui/components/intro/intro.js'; +import {connect} from 'react-redux'; +import { + toggleRightSidebarAvailable, + toggleRightSidebarVisible, +} from './reducers/application.js'; +import type {SonarBasePlugin} from './plugin.js'; +import type LogManager from './fb-stubs/Logger'; + +type Props = { + plugin: SonarBasePlugin<>, + state?: any, + logger: LogManager, + rightSidebarVisible: boolean, + rightSidebarAvailable: boolean, + toggleRightSidebarVisible: (available: ?boolean) => void, + toggleRightSidebarAvailable: (available: ?boolean) => void, +}; + +type State = { + showIntro: boolean, +}; + +class PluginContainer extends Component { + state = { + showIntro: + typeof this.props.plugin.renderIntro === 'function' && + window.localStorage.getItem( + `${this.props.plugin.constructor.id}.introShown`, + ) !== 'true', + }; + + _sidebar: ?React$Node; + + static Container = FlexColumn.extends({ + width: 0, + flexGrow: 1, + flexShrink: 1, + backgroundColor: colors.white, + }); + + componentWillUnmount() { + performance.mark(`init_${this.props.plugin.constructor.id}`); + } + + componentDidMount() { + this.props.logger.trackTimeSince( + `init_${this.props.plugin.constructor.id}`, + ); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.plugin !== this.props.plugin) { + this.props.logger.trackTimeSince( + `init_${this.props.plugin.constructor.id}`, + ); + } + } + + componentWillUpdate(nextProps: Props) { + if (this.props.plugin !== nextProps.plugin) { + performance.mark(`init_${nextProps.plugin.constructor.id}`); + } + let sidebarContent; + if (typeof nextProps.plugin.renderSidebar === 'function') { + sidebarContent = nextProps.plugin.renderSidebar(); + } + + if (sidebarContent == null) { + this._sidebar = null; + nextProps.toggleRightSidebarAvailable(false); + } else { + this._sidebar = ( + + {sidebarContent} + + ); + nextProps.toggleRightSidebarAvailable(true); + } + } + + onDismissIntro = () => { + const {plugin} = this.props; + window.localStorage.setItem(`${plugin.constructor.id}.introShown`, 'true'); + this.setState({ + showIntro: false, + }); + }; + + render() { + const {plugin} = this.props; + + return [ + + {this.state.showIntro ? ( + + {typeof plugin.renderIntro === 'function' && plugin.renderIntro()} + + ) : ( + plugin.render() + )} + , + this.props.rightSidebarVisible === false ? null : this._sidebar, + ]; + } +} + +export default connect( + ({application: {rightSidebarVisible, rightSidebarAvailable}}) => ({ + rightSidebarVisible, + rightSidebarAvailable, + }), + { + toggleRightSidebarAvailable, + toggleRightSidebarVisible, + }, +)(PluginContainer); diff --git a/src/chrome/AutoUpdateVersion.js b/src/chrome/AutoUpdateVersion.js new file mode 100644 index 000000000..9f57d8854 --- /dev/null +++ b/src/chrome/AutoUpdateVersion.js @@ -0,0 +1,103 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {FlexRow, Text, colors, LoadingIndicator, Glyph, Component} from 'sonar'; +import {remote} from 'electron'; +import {isProduction} from '../utils/dynamicPluginLoading'; +import config from '../fb-stubs/config.js'; +const version = remote.app.getVersion(); + +const VersionText = Text.extends({ + color: colors.light50, + marginLeft: 4, + marginTop: 2, +}); + +const Container = FlexRow.extends({ + alignItems: 'center', +}); + +type State = { + updater: + | 'error' + | 'checking-for-update' + | 'update-available' + | 'update-not-available' + | 'update-downloaded', + error?: string, +}; + +export default class AutoUpdateVersion extends Component<{}, State> { + state = { + updater: 'update-not-available', + }; + + componentDidMount() { + if (isProduction()) { + remote.autoUpdater.setFeedURL( + `${config.updateServer}?version=${version}`, + ); + + remote.autoUpdater.on('update-downloaded', () => { + this.setState({updater: 'update-downloaded'}); + + remote.dialog.showMessageBox( + { + title: 'Update available', + message: 'A new version of Sonar is available!', + detail: `You have Sonar ${version} which is outdated. Update to the latest version now.`, + buttons: ['Install and Restart'], + }, + () => { + remote.autoUpdater.quitAndInstall(); + }, + ); + }); + + remote.autoUpdater.on('error', error => { + this.setState({updater: 'error', error: error.toString()}); + }); + + remote.autoUpdater.on('checking-for-update', () => { + this.setState({updater: 'checking-for-update'}); + }); + + remote.autoUpdater.on('update-available', error => { + this.setState({updater: 'update-available'}); + }); + + remote.autoUpdater.on('update-not-available', error => { + this.setState({updater: 'update-not-available'}); + }); + + remote.autoUpdater.checkForUpdates(); + } + } + + render() { + return ( + + {this.state.updater === 'update-available' && ( + + + + )} + {this.state.updater === 'error' && ( + + + + )} + {this.state.updater === 'update-downloaded' && ( + + + + )} + {isProduction() && {version}} + + ); + } +} diff --git a/src/chrome/BugReporterDialog.js b/src/chrome/BugReporterDialog.js new file mode 100644 index 000000000..37c9ba030 --- /dev/null +++ b/src/chrome/BugReporterDialog.js @@ -0,0 +1,239 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type BugReporter from '../fb-stubs/BugReporter.js'; +import {Component} from 'react'; +import { + Button, + colors, + Link, + Input, + FlexColumn, + FlexRow, + Textarea, + Text, + FlexCenter, + styled, +} from 'sonar'; + +const Container = FlexColumn.extends({ + padding: 10, +}); + +const textareaStyle = { + margin: 0, + marginBottom: 10, +}; + +const DialogContainer = styled.view({ + width: 400, + height: 300, + position: 'absolute', + left: '50%', + marginLeft: -200, + top: 40, + zIndex: 999999, + backgroundColor: '#fff', + border: '1px solid #ddd', + borderTop: 'none', + borderBottomLeftRadius: 5, + borderBottomRightRadius: 5, + boxShadow: '0 1px 10px rgba(0, 0, 0, 0.1)', +}); + +const TitleInput = Input.extends({ + ...textareaStyle, + height: 30, +}); + +const DescriptionTextarea = Textarea.extends({ + ...textareaStyle, + flexGrow: 1, +}); + +const SubmitButtonContainer = styled.view({ + marginLeft: 'auto', +}); + +const Footer = FlexRow.extends({ + lineHeight: '24px', +}); + +const CloseDoneButton = Button.extends({ + width: 50, + margin: '10px auto', +}); + +type State = { + description: string, + title: string, + submitting: boolean, + success: false | number, // false if not created, id of bug if it's been created + error: ?string, +}; + +type Props = { + bugReporter: BugReporter, + close: () => void, +}; + +const DEFAULT_DESCRIPTION = `Thanks for taking the time to provide feedback! +Please fill out the following information to make addressing your issue easier. + +What device platform are you using? ios/android +What sort of device are you using? emulator/physical +What app are you trying to use? wilde, fb4a, lite etc +Describe your problem in as much detail as possible: `; + +export default class BugReporterDialog extends Component { + constructor(props: Props) { + super(props); + + this.state = { + description: DEFAULT_DESCRIPTION, + title: '', + submitting: false, + success: false, + error: null, + }; + } + + titleRef: HTMLElement; + descriptionRef: HTMLElement; + + onDescriptionChange = (e: SyntheticInputEvent) => { + this.setState({description: e.target.value}); + }; + + onTitleChange = (e: SyntheticInputEvent) => { + this.setState({title: e.target.value}); + }; + + onSubmit = () => { + // validate fields + const {title, description} = this.state; + if (!title) { + this.setState({ + error: 'Title required.', + }); + this.titleRef.focus(); + return; + } + if (!description) { + this.setState({ + error: 'Description required.', + }); + this.descriptionRef.focus(); + return; + } + + this.setState( + { + error: null, + submitting: true, + }, + () => { + // this will be called before the next repaint + requestAnimationFrame(() => { + // we have to call this again to ensure a repaint has actually happened + // as requestAnimationFrame is called BEFORE a repaint, not after which + // means we have to queue up twice to actually ensure a repaint has + // happened + requestAnimationFrame(() => { + this.props.bugReporter + .report(title, description) + .then((id: number) => { + this.setState({ + submitting: false, + success: id, + }); + }) + .catch(err => { + this.setState({ + error: err.message, + submitting: false, + }); + }); + }); + }); + }, + ); + }; + + setTitleRef = (ref: HTMLElement) => { + this.titleRef = ref; + }; + + setDescriptionRef = (ref: HTMLElement) => { + this.descriptionRef = ref; + }; + + onCancel = () => { + this.props.close(); + }; + + render() { + let content; + + const {title, success, error, description} = this.state; + + if (success) { + content = ( + + + + Bug + + + + {success} + + + + created. Thank you for the report! + + + Close + + + ); + } else { + content = ( + + + + + +

+ {error != null && {error}} + + + + +
+ + ); + } + + return {content}; + } +} diff --git a/src/chrome/DevicesButton.js b/src/chrome/DevicesButton.js new file mode 100644 index 000000000..4f5f420e4 --- /dev/null +++ b/src/chrome/DevicesButton.js @@ -0,0 +1,291 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {Component, styled, Glyph, Button, colors} from 'sonar'; +import {connect} from 'react-redux'; +import BaseDevice from '../devices/BaseDevice.js'; +import child_process from 'child_process'; +import DevicesList from './DevicesList.js'; + +const adb = require('adbkit-fb'); + +const Light = styled.view( + { + width: 10, + height: 10, + borderRadius: '999em', + backgroundColor: props => (props.active ? '#70f754' : colors.light20), + border: props => `1px solid ${props.active ? '#52d936' : colors.light30}`, + }, + { + ignoreAttributes: ['active'], + }, +); + +type Props = {| + devices: Array, +|}; + +type Emulator = {| + name: string, + os?: string, + isRunning: boolean, +|}; + +type State = { + androidEmulators: Array, + iOSSimulators: Array, + popoverVisible: boolean, +}; + +type IOSSimulatorList = { + devices: { + [os: string]: Array<{ + state: 'Shutdown' | 'Booted', + availability: string, + name: string, + udid: string, + os?: string, + }>, + }, +}; + +class DevicesButton extends Component { + state = { + androidEmulators: [], + iOSSimulators: [], + popoverVisible: false, + }; + + client = adb.createClient(); + _iOSSimulatorRefreshInterval: ?number; + + componentDidMount() { + this.updateEmulatorState(this.openMenuWhenNoDevicesConnected); + this.fetchIOSSimulators(); + this._iOSSimulatorRefreshInterval = window.setInterval( + this.fetchIOSSimulators, + 5000, + ); + + this.client.trackDevices().then(tracker => { + tracker.on('add', () => this.updateEmulatorState()); + tracker.on('remove', () => this.updateEmulatorState()); + tracker.on('end', () => this.updateEmulatorState()); + }); + } + + componentWillUnmount() { + if (this._iOSSimulatorRefreshInterval != null) { + window.clearInterval(this._iOSSimulatorRefreshInterval); + } + } + + fetchIOSSimulators = () => { + child_process.exec( + 'xcrun simctl list devices --json', + (err: ?Error, data: ?string) => { + if (data != null && err == null) { + const devicesList: IOSSimulatorList = JSON.parse(data); + const iOSSimulators = Object.keys(devicesList.devices) + .map(os => + devicesList.devices[os].map(device => { + device.os = os; + return device; + }), + ) + .reduce((acc, cv) => acc.concat(cv), []) + .filter(device => device.state === 'Booted') + .map(device => ({ + name: device.name, + os: device.os, + isRunning: true, + })); + this.setState({iOSSimulators}); + } + }, + ); + }; + + openMenuWhenNoDevicesConnected = () => { + const numberOfEmulators = this.state.androidEmulators.filter( + e => e.isRunning, + ).length; + const numberOfDevices = Object.values(this.props.devices).length; + if (numberOfEmulators + numberOfDevices === 0) { + this.setState({popoverVisible: true}); + } + }; + + updateEmulatorState = async (cb?: Function) => { + try { + const devices = await this.getEmulatorNames(); + const ports = await this.getRunningEmulatorPorts(); + const runningDevices = await Promise.all( + ports.map(port => this.getRunningName(port)), + ); + this.setState( + { + androidEmulators: devices.map(name => ({ + name, + isRunning: runningDevices.indexOf(name) > -1, + })), + }, + cb, + ); + } catch (e) { + console.error(e); + } + }; + + getEmulatorNames(): Promise> { + return new Promise((resolve, reject) => { + child_process.exec( + '/opt/android_sdk/tools/emulator -list-avds', + (error: ?Error, data: ?string) => { + if (error == null && data != null) { + resolve(data.split('\n').filter(name => name !== '')); + } else { + reject(error); + } + }, + ); + }); + } + + getRunningEmulatorPorts(): Promise> { + const EMULATOR_PREFIX = 'emulator-'; + return adb + .createClient() + .listDevices() + .then((devices: Array<{id: string}>) => + devices + .filter(d => d.id.startsWith(EMULATOR_PREFIX)) + .map(d => d.id.replace(EMULATOR_PREFIX, '')), + ) + .catch((e: Error) => { + return []; + }); + } + + getRunningName(port: string): Promise { + return new Promise((resolve, reject) => { + child_process.exec( + `echo "avd name" | nc -w 1 localhost ${port}`, + (error: ?Error, data: ?string) => { + if (error == null && data != null) { + const match = data.trim().match(/(.*)\r\nOK$/); + resolve(match != null && match.length > 0 ? match[1] : null); + } else { + reject(error); + } + }, + ); + }); + } + + launchEmulator = (name: string) => { + child_process.exec( + `/opt/android_sdk/tools/emulator @${name}`, + this.updateEmulatorState, + ); + }; + + createEmualtor = () => {}; + + onClick = () => { + this.setState({popoverVisible: !this.state.popoverVisible}); + this.updateEmulatorState(); + this.fetchIOSSimulators(); + }; + + onDismissPopover = () => { + this.setState({popoverVisible: false}); + }; + + render() { + let text = 'No devices running'; + let glyph = 'minus-circle'; + + const runnningEmulators = this.state.androidEmulators.filter( + emulator => emulator.isRunning, + ); + + const numberOfRunningDevices = + runnningEmulators.length + this.state.iOSSimulators.length; + + if (numberOfRunningDevices > 0) { + text = `${numberOfRunningDevices} device${ + numberOfRunningDevices > 1 ? 's' : '' + } running`; + glyph = 'mobile'; + } + + const connectedDevices = this.props.devices; + + return ( + + ); + } +} + +export default connect(({devices}) => ({ + devices, +}))(DevicesButton); diff --git a/src/chrome/DevicesList.js b/src/chrome/DevicesList.js new file mode 100644 index 000000000..388ab385b --- /dev/null +++ b/src/chrome/DevicesList.js @@ -0,0 +1,142 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import { + PureComponent, + FlexRow, + FlexBox, + Text, + Button, + Popover, + styled, + colors, +} from 'sonar'; + +const Heading = Text.extends({ + display: 'block', + backgroundColor: colors.white, + color: colors.light30, + fontSize: 11, + fontWeight: 600, + lineHeight: '21px', + padding: '4px 8px 0', +}); + +const PopoverItem = FlexRow.extends({ + alignItems: 'center', + borderBottom: `1px solid ${colors.light05}`, + height: 50, + '&:last-child': { + borderBottom: 'none', + }, +}); + +const ItemTitle = Text.extends({ + display: 'block', + fontSize: 14, + fontWeight: 400, + lineHeight: '120%', + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', + marginBottom: 1, +}); + +const ItemSubtitle = Text.extends({ + display: 'block', + fontWeight: 400, + fontSize: 11, + color: colors.light30, + lineHeight: '14px', + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', +}); + +const ItemImage = FlexBox.extends({ + alignItems: 'center', + justifyContent: 'center', + width: 40, + flexShrink: 0, +}); + +const ItemContent = styled.view({ + minWidth: 0, + paddingRight: 5, + flexGrow: 1, +}); + +const Section = styled.view({ + maxWidth: 260, + borderBottom: `1px solid ${colors.light05}`, + '&:last-child': { + borderBottom: 'none', + }, +}); + +const Action = Button.extends({ + border: `1px solid ${colors.macOSTitleBarButtonBorder}`, + background: 'transparent', + color: colors.macOSTitleBarIconSelected, + marginRight: 8, + marginLeft: 4, + lineHeight: '22px', + '&:hover': { + background: 'transparent', + }, + '&:active': { + background: 'transparent', + border: `1px solid ${colors.macOSTitleBarButtonBorder}`, + }, +}); + +type Props = {| + sections: Array<{ + title: string, + items: Array<{ + title: string, + subtitle: string, + onClick?: Function, + icon?: React.Element<*>, + }>, + }>, + onDismiss: Function, +|}; + +export default class DevicesList extends PureComponent { + render() { + return ( + + {this.props.sections.map(section => { + if (section.items.length > 0) { + return ( +
+ {section.title} + {section.items.map(item => ( + + {item.icon} + + {item.title} + {item.subtitle} + + {item.onClick && ( + + Run + + )} + + ))} +
+ ); + } else { + return null; + } + })} +
+ ); + } +} diff --git a/src/chrome/ErrorBar.js b/src/chrome/ErrorBar.js new file mode 100644 index 000000000..9bfcfd4e1 --- /dev/null +++ b/src/chrome/ErrorBar.js @@ -0,0 +1,28 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {styled, colors} from 'sonar'; + +const ErrorBarContainer = styled.view({ + backgroundColor: colors.cherry, + bottom: 0, + color: '#fff', + left: 0, + lineHeight: '26px', + position: 'absolute', + right: 0, + textAlign: 'center', + zIndex: 2, +}); + +export default function ErrorBar(props: {|text: ?string|}) { + if (props.text == null) { + return null; + } else { + return {props.text}; + } +} diff --git a/src/chrome/MainSidebar.js b/src/chrome/MainSidebar.js new file mode 100644 index 000000000..699934f8b --- /dev/null +++ b/src/chrome/MainSidebar.js @@ -0,0 +1,305 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type {SonarBasePlugin} from '../plugin.js'; +import type {Client} from '../server.js'; + +import { + Component, + Sidebar, + FlexBox, + ClickableList, + ClickableListItem, + colors, + brandColors, + Text, + Glyph, +} from 'sonar'; +import {devicePlugins} from '../device-plugins/index.js'; +import type BaseDevice from '../devices/BaseDevice.js'; +import PropTypes from 'prop-types'; +import plugins from '../plugins/index.js'; + +const CustomClickableListItem = ClickableListItem.extends({ + paddingLeft: 10, + display: 'flex', + alignItems: 'center', + marginBottom: 2, +}); + +const SidebarHeader = FlexBox.extends({ + display: 'block', + alignItems: 'center', + padding: 3, + color: colors.macOSSidebarSectionTitle, + fontSize: 11, + fontWeight: 500, + marginLeft: 7, + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', + flexShrink: 0, +}); + +const PluginShape = FlexBox.extends( + { + marginRight: 5, + backgroundColor: props => props.backgroundColor, + borderRadius: 3, + flexShrink: 0, + width: 18, + height: 18, + justifyContent: 'center', + alignItems: 'center', + }, + { + ignoreAttributes: ['backgroundColor'], + }, +); + +const PluginName = Text.extends({ + minWidth: 0, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + overflow: 'hidden', +}); + +function PluginIcon({ + backgroundColor, + name, + color, +}: { + backgroundColor: string, + name: string, + color: string, +}) { + return ( + + + + ); +} + +class PluginSidebarListItem extends Component<{ + activePluginKey: ?string, + activeAppKey: ?string, + onActivatePlugin: (appKey: string, pluginKey: string) => void, + appKey: string, + appName?: string, + isActive: boolean, + Plugin: Class>, + windowFocused: boolean, +}> { + onClick = () => { + const {props} = this; + props.onActivatePlugin(props.appKey, props.Plugin.id); + }; + + render() { + const {isActive, Plugin, windowFocused, appKey, appName} = this.props; + + let iconColor; + if (appName != null) { + iconColor = brandColors[appName]; + } + + if (iconColor == null) { + const pluginColors = [ + colors.seaFoam, + colors.teal, + colors.lime, + colors.lemon, + colors.orange, + colors.tomato, + colors.cherry, + colors.pink, + colors.grape, + ]; + + iconColor = pluginColors[parseInt(appKey, 36) % pluginColors.length]; + } + + return ( + + + {Plugin.title} + + ); + } +} + +function PluginSidebarList(props: {| + activePluginKey: ?string, + activeAppKey: ?string, + onActivatePlugin: (appKey: string, pluginKey: string) => void, + appKey: string, + appName?: string, + enabledPlugins: Array>>, + windowFocused: boolean, +|}) { + if (props.enabledPlugins.length === 0) { + return No available plugins for this device; + } + + return ( + + {props.enabledPlugins.map(Plugin => { + const isActive = + props.activeAppKey === props.appKey && + props.activePluginKey === Plugin.id; + return ( + + ); + })} + + ); +} + +function AppSidebarInfo(props: {| + client: Client, + appKey: string, + activePluginKey: ?string, + activeAppKey: ?string, + onActivatePlugin: (appKey: string, pluginKey: string) => void, + windowFocused: boolean, +|}): any { + const {appKey, client, windowFocused} = props; + + let enabledPlugins = []; + for (const Plugin of plugins) { + if (client.supportsPlugin(Plugin)) { + enabledPlugins.push(Plugin); + } + } + enabledPlugins = enabledPlugins.sort((a, b) => { + return (a.title || '').localeCompare(b.title); + }); + + return [ + {`${client.query.app} (${ + client.query.os + }) - ${client.query.device}`}, + , + ]; +} + +type MainSidebarProps = {| + activePluginKey: ?string, + activeAppKey: ?string, + onActivatePlugin: (appKey: string, pluginKey: string) => void, + devices: Array, + server: Server, +|}; + +export default class MainSidebar extends Component { + static contextTypes = { + windowIsFocused: PropTypes.bool, + }; + + render() { + const connections = Array.from(this.props.server.connections.values()).sort( + (a, b) => { + return (a.client.query.app || '').localeCompare(b.client.query.app); + }, + ); + + const sidebarContent = connections.map(conn => { + const {client} = conn; + + return ( + + ); + }); + + let {devices} = this.props; + devices = devices.sort((a, b) => { + return (a.title || '').localeCompare(b.title); + }); + + for (const device of devices) { + let enabledPlugins = []; + for (const DevicePlugin of devicePlugins) { + if (device.supportsPlugin(DevicePlugin)) { + enabledPlugins.push(DevicePlugin); + } + } + enabledPlugins = enabledPlugins.sort((a, b) => { + return (a.title || '').localeCompare(b.title); + }); + + sidebarContent.unshift([ + {device.title}, + , + ]); + } + + return ( + + {sidebarContent} + + ); + } +} diff --git a/src/chrome/PluginManager.js b/src/chrome/PluginManager.js new file mode 100644 index 000000000..d4cc6c23e --- /dev/null +++ b/src/chrome/PluginManager.js @@ -0,0 +1,404 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import { + PureComponent, + Button, + FlexColumn, + FlexBox, + Text, + LoadingIndicator, + ButtonGroup, + colors, + Glyph, + FlexRow, + styled, + Searchable, +} from 'sonar'; +const {spawn} = require('child_process'); +const path = require('path'); +const {app, shell} = require('electron').remote; + +const SONAR_PLUGIN_PATH = path.join(app.getPath('home'), '.sonar'); +const DYNAMIC_PLUGINS = JSON.parse(window.process.env.PLUGINS || '[]'); + +type NPMModule = { + name: string, + version: string, + description?: string, + error?: Object, +}; + +type Status = + | 'installed' + | 'outdated' + | 'install' + | 'remove' + | 'update' + | 'uninstalled' + | 'uptodate'; + +type PluginT = { + name: string, + version?: string, + description?: string, + status: Status, + managed?: boolean, + entry?: string, + rootDir?: string, +}; + +type Props = { + searchTerm: string, +}; +type State = { + plugins: { + [name: string]: PluginT, + }, + restartRequired: boolean, + searchCompleted: boolean, +}; + +const Container = FlexBox.extends({ + width: '100%', + flexGrow: 1, + background: colors.light02, + overflowY: 'scroll', +}); + +const Title = Text.extends({ + fontWeight: 500, +}); + +const Plugin = FlexColumn.extends({ + backgroundColor: colors.white, + borderRadius: 4, + padding: 15, + margin: '0 15px 25px', + boxShadow: '0 1px 2px rgba(0,0,0,0.05)', +}); + +const SectionTitle = styled.text({ + fontWeight: 'bold', + fontSize: 24, + margin: 15, + marginLeft: 20, +}); + +const Loading = FlexBox.extends({ + padding: 50, + alignItems: 'center', + justifyContent: 'center', +}); + +const RestartRequired = FlexBox.extends({ + textAlign: 'center', + justifyContent: 'center', + fontWeight: 500, + color: colors.white, + padding: 12, + backgroundColor: colors.green, + cursor: 'pointer', +}); + +const TitleRow = FlexRow.extends({ + alignItems: 'center', + marginBottom: 10, + fontSize: '1.1em', +}); + +const Description = FlexRow.extends({ + marginBottom: 15, + lineHeight: '130%', +}); + +const PluginGlyph = Glyph.extends({ + marginRight: 5, +}); + +const PluginLoading = LoadingIndicator.extends({ + marginLeft: 5, + marginTop: 5, +}); + +const getLatestVersion = (name: string): Promise => { + return fetch(`http://registry.npmjs.org/${name}/latest`).then(res => + res.json(), + ); +}; + +const getPluginList = (): Promise> => { + return fetch( + 'http://registry.npmjs.org/-/v1/search?text=keywords:sonar&size=250', + ) + .then(res => res.json()) + .then(res => res.objects.map(o => o.package)); +}; + +const sortByName = (a: PluginT, b: PluginT): 1 | -1 => + a.name > b.name ? 1 : -1; + +const INSTALLED = ['installed', 'outdated', 'uptodate']; + +class PluginItem extends PureComponent< + { + plugin: PluginT, + onChangeState: (action: Status) => void, + }, + { + working: boolean, + }, +> { + state = { + working: false, + }; + + npmAction = (action: Status) => { + const {name, status: initialStatus} = this.props.plugin; + this.setState({working: true}); + const npm = spawn('npm', [action, name], { + cwd: SONAR_PLUGIN_PATH, + }); + + npm.stderr.on('data', e => { + console.error(e.toString()); + }); + + npm.on('close', code => { + this.setState({working: false}); + const newStatus = action === 'remove' ? 'uninstalled' : 'uptodate'; + this.props.onChangeState(code !== 0 ? initialStatus : newStatus); + }); + }; + + render() { + const { + entry, + status, + version, + description, + managed, + name, + rootDir, + } = this.props.plugin; + + return ( + + + + {name} +   + {version} + + {description && {description}} + + {managed ? ( + + This plugin is not managed by Sonar, but loaded from{' '} + + {rootDir} + + + ) : ( + + {status === 'outdated' && ( + + )} + {INSTALLED.includes(status) ? ( + + ) : ( + + )} + + + )} + {this.state.working && } + + + ); + } +} + +class PluginManager extends PureComponent { + state = { + plugins: DYNAMIC_PLUGINS.reduce((acc, plugin) => { + acc[plugin.name] = { + ...plugin, + managed: !(plugin.entry, '').startsWith(SONAR_PLUGIN_PATH), + status: 'installed', + }; + return acc; + }, {}), + restartRequired: false, + searchCompleted: false, + }; + + componentDidMount() { + Promise.all( + Object.keys(this.state.plugins) + .filter(name => this.state.plugins[name].managed) + .map(getLatestVersion), + ).then((res: Array) => { + const updates = {}; + res.forEach(plugin => { + if ( + plugin.error == null && + this.state.plugins[plugin.name].version !== plugin.version + ) { + updates[plugin.name] = { + ...plugin, + ...this.state.plugins[plugin.name], + status: 'outdated', + }; + } + }); + this.setState({ + plugins: { + ...this.state.plugins, + ...updates, + }, + }); + }); + + getPluginList().then(pluginList => { + const plugins = {...this.state.plugins}; + pluginList.forEach(plugin => { + if (plugins[plugin.name] != null) { + plugins[plugin.name] = { + ...plugin, + ...plugins[plugin.name], + status: + plugin.version === plugins[plugin.name].version + ? 'uptodate' + : 'outdated', + }; + } else { + plugins[plugin.name] = { + ...plugin, + status: 'uninstalled', + }; + } + }); + this.setState({ + plugins, + searchCompleted: true, + }); + }); + } + + onChangePluginState = (name: string, status: Status) => { + this.setState({ + plugins: { + ...this.state.plugins, + [name]: { + ...this.state.plugins[name], + status, + }, + }, + restartRequired: true, + }); + }; + + relaunch() { + app.relaunch(); + app.exit(0); + } + + render() { + // $FlowFixMe + const plugins: Array = Object.values(this.state.plugins); + const availablePlugins = plugins.filter( + ({status}) => !INSTALLED.includes(status), + ); + return ( + + + {this.state.restartRequired && ( + + +   Restart Required: Click to Restart + + )} + Installed Plugins + {plugins + .filter( + ({status, name}) => + INSTALLED.includes(status) && + name.indexOf(this.props.searchTerm) > -1, + ) + .sort(sortByName) + .map((plugin: PluginT) => ( + + this.onChangePluginState(plugin.name, action) + } + /> + ))} + Available Plugins + {availablePlugins + .filter(({name}) => name.indexOf(this.props.searchTerm) > -1) + .sort(sortByName) + .map((plugin: PluginT) => ( + + this.onChangePluginState(plugin.name, action) + } + /> + ))} + {!this.state.searchCompleted && ( + + + + )} + + + ); + } +} + +const SearchablePluginManager = Searchable(PluginManager); + +export default class extends PureComponent<{}> { + render() { + return ( + + + + ); + } +} diff --git a/src/chrome/Popover.js b/src/chrome/Popover.js new file mode 100644 index 000000000..32d0d5ccd --- /dev/null +++ b/src/chrome/Popover.js @@ -0,0 +1,196 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import { + PureComponent, + FlexColumn, + FlexRow, + FlexBox, + Text, + Button, + styled, + colors, +} from 'sonar'; + +const Anchor = styled.image({ + zIndex: 6, + position: 'absolute', + bottom: 0, + left: '50%', + transform: 'translate(-50%, calc(100% + 2px))', +}); + +const PopoverContainer = FlexColumn.extends({ + backgroundColor: colors.white, + borderRadius: 7, + border: '1px solid rgba(0,0,0,0.3)', + boxShadow: '0 2px 10px 0 rgba(0,0,0,0.3)', + position: 'absolute', + zIndex: 5, + minWidth: 240, + bottom: 0, + marginTop: 15, + left: '50%', + transform: 'translate(-50%, calc(100% + 15px))', + overflow: 'hidden', + '&::before': { + content: '""', + display: 'block', + position: 'absolute', + left: '50%', + transform: 'translateX(-50%)', + height: 13, + top: -13, + width: 26, + backgroundColor: colors.white, + }, +}); + +const Heading = Text.extends({ + display: 'block', + backgroundColor: colors.white, + color: colors.light30, + fontSize: 11, + fontWeight: 600, + lineHeight: '21px', + padding: '4px 8px 0', +}); + +const PopoverItem = FlexRow.extends({ + alignItems: 'center', + borderBottom: `1px solid ${colors.light05}`, + height: 50, + '&:last-child': { + borderBottom: 'none', + }, +}); + +const ItemTitle = Text.extends({ + display: 'block', + fontSize: 14, + fontWeight: 400, + lineHeight: '120%', + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', + marginBottom: 1, +}); + +const ItemSubtitle = Text.extends({ + display: 'block', + fontWeight: 400, + fontSize: 11, + color: colors.light30, + lineHeight: '14px', + textOverflow: 'ellipsis', + overflow: 'hidden', + whiteSpace: 'nowrap', +}); + +const ItemImage = FlexBox.extends({ + alignItems: 'center', + justifyContent: 'center', + width: 40, + flexShrink: 0, +}); + +const ItemContent = styled.view({ + minWidth: 0, + paddingRight: 5, + flexGrow: 1, +}); + +const Section = styled.view({ + borderBottom: `1px solid ${colors.light05}`, + '&:last-child': { + borderBottom: 'none', + }, +}); + +const Action = Button.extends({ + border: `1px solid ${colors.macOSTitleBarButtonBorder}`, + background: 'transparent', + color: colors.macOSTitleBarIconSelected, + marginRight: 8, + lineHeight: '22px', + '&:hover': { + background: 'transparent', + }, + '&:active': { + background: 'transparent', + border: `1px solid ${colors.macOSTitleBarButtonBorder}`, + }, +}); + +type Props = {| + sections: Array<{ + title: string, + items: Array<{ + title: string, + subtitle: string, + onClick?: Function, + icon?: React.Element<*>, + }>, + }>, + onDismiss: Function, +|}; + +export default class Popover extends PureComponent { + _ref: ?Element; + + componentDidMount() { + window.document.addEventListener('click', this.handleClick); + } + + componentWillUnmount() { + window.document.addEventListener('click', this.handleClick); + } + + handleClick = (e: SyntheticMouseEvent<>) => { + // $FlowFixMe + if (this._ref && !this._ref.contains(e.target)) { + this.props.onDismiss(); + } + }; + + _setRef = (ref: ?Element) => { + this._ref = ref; + }; + + render() { + return [ + , + + {this.props.sections.map(section => { + if (section.items.length > 0) { + return ( +
+ {section.title} + {section.items.map(item => ( + + {item.icon} + + {item.title} + {item.subtitle} + + {item.onClick && ( + + Run + + )} + + ))} +
+ ); + } else { + return null; + } + })} +
, + ]; + } +} diff --git a/src/chrome/SonarTitleBar.js b/src/chrome/SonarTitleBar.js new file mode 100644 index 000000000..bfb7ac999 --- /dev/null +++ b/src/chrome/SonarTitleBar.js @@ -0,0 +1,157 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import { + colors, + Button, + ButtonGroup, + FlexRow, + FlexBox, + Component, + Spacer, + Glyph, + GK, +} from 'sonar'; +import { + loadsDynamicPlugins, + dynamicPluginPath, +} from '../utils/dynamicPluginLoading.js'; +import {connect} from 'react-redux'; +import { + toggleBugDialogVisible, + toggleLeftSidebarVisible, + toggleRightSidebarVisible, + togglePluginManagerVisible, +} from '../reducers/application.js'; +import DevicesButton from './DevicesButton.js'; +import Version from './Version.js'; +import AutoUpdateVersion from './AutoUpdateVersion.js'; +import config from '../fb-stubs/config.js'; + +const TitleBar = FlexRow.extends( + { + background: props => + props.focused + ? `linear-gradient(to bottom, ${ + colors.macOSTitleBarBackgroundTop + } 0%, ${colors.macOSTitleBarBackgroundBottom} 100%)` + : colors.macOSTitleBarBackgroundBlur, + borderBottom: props => + `1px solid ${ + props.focused + ? colors.macOSTitleBarBorder + : colors.macOSTitleBarBorderBlur + }`, + height: 38, + flexShrink: 0, + width: '100%', + alignItems: 'center', + paddingLeft: 80, + paddingRight: 10, + justifyContent: 'space-between', + // $FlowFixMe + WebkitAppRegion: 'drag', + }, + { + ignoreAttributes: ['focused'], + }, +); + +const Icon = FlexBox.extends({ + marginRight: 3, +}); + +type Props = {| + windowIsFocused: boolean, + leftSidebarVisible: boolean, + rightSidebarVisible: boolean, + rightSidebarAvailable: boolean, + pluginManagerVisible: boolean, + toggleBugDialogVisible: (visible?: boolean) => void, + toggleLeftSidebarVisible: (visible?: boolean) => void, + toggleRightSidebarVisible: (visible?: boolean) => void, + togglePluginManagerVisible: (visible?: boolean) => void, +|}; + +class SonarTitleBar extends Component { + render() { + return ( + + + + {loadsDynamicPlugins() && ( + + + + )} + {process.platform === 'darwin' ? : } + {config.bugReportButtonVisible && ( + } + /> + + ); + } + }; +} diff --git a/src/device-plugins/cpu/index.js b/src/device-plugins/cpu/index.js new file mode 100644 index 000000000..5d0f83e48 --- /dev/null +++ b/src/device-plugins/cpu/index.js @@ -0,0 +1,323 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {SonarDevicePlugin} from 'sonar'; +var adb = require('adbkit-fb'); + +import { + FlexColumn, + FlexRow, + Button, + Toolbar, + Text, + ManagedTable, + colors, +} from 'sonar'; + +type ADBClient = any; +type AndroidDevice = any; +type TableRows = any; + +// we keep vairable name with underline for to physical path mappings on device +type CPUFrequency = {| + cpu_id: number, + scaling_cur_freq: number, + scaling_min_freq: number, + scaling_max_freq: number, + cpuinfo_max_freq: number, + cpuinfo_min_freq: number, +|}; + +type CPUState = {| + cpuFreq: Array, + cpuCount: number, + monitoring: boolean, +|}; + +type ShellCallBack = (output: string) => void; + +const ColumnSizes = { + cpu_id: '10%', + scaling_cur_freq: 'flex', + scaling_min_freq: 'flex', + scaling_max_freq: 'flex', + cpuinfo_min_freq: 'flex', + cpuinfo_max_freq: 'flex', +}; + +const Columns = { + cpu_id: { + value: 'CPU ID', + resizable: true, + }, + scaling_cur_freq: { + value: 'Scaling Current', + resizable: true, + }, + scaling_min_freq: { + value: 'Scaling MIN', + resizable: true, + }, + scaling_max_freq: { + value: 'Scaling MAX', + resizable: true, + }, + cpuinfo_min_freq: { + value: 'MIN Frequency', + resizable: true, + }, + cpuinfo_max_freq: { + value: 'MAX Frequency', + resizable: true, + }, +}; + +// check if str is a number +function isNormalInteger(str) { + let n = Math.floor(Number(str)); + return String(n) === str && n >= 0; +} + +// format frequency to MHz, GHz +function formatFrequency(freq) { + if (freq == -1) { + return 'N/A'; + } else if (freq == -2) { + return 'off'; + } else if (freq > 1000 * 1000) { + return (freq / 1000 / 1000).toFixed(2) + ' GHz'; + } else { + return freq / 1000 + ' MHz'; + } +} + +export default class CPUFrequencyTable extends SonarDevicePlugin { + static id = 'DeviceCPU'; + static title = 'CPU'; + static icon = 'underline'; + + adbClient: ADBClient; + intervalID: ?IntervalID; + device: AndroidDevice; + + init() { + this.setState({ + cpuFreq: [], + cpuCount: 0, + monitoring: false, + }); + + this.adbClient = this.device.adb; + + // check how many cores we have on this device + this.executeShell((output: string) => { + let idx = output.indexOf('-'); + let cpuFreq = []; + let count = parseInt(output.substring(idx + 1), 10) + 1; + for (let i = 0; i < count; ++i) { + cpuFreq[i] = { + cpu_id: i, + scaling_cur_freq: -1, + scaling_min_freq: -1, + scaling_max_freq: -1, + cpuinfo_min_freq: -1, + cpuinfo_max_freq: -1, + }; + } + this.setState({ + cpuCount: count, + cpuFreq: cpuFreq, + }); + }, 'cat /sys/devices/system/cpu/possible'); + } + + executeShell = (callback: ShellCallBack, command: string) => { + this.adbClient + .shell(this.device.serial, command) + .then(adb.util.readAll) + .then(function(output) { + return callback(output.toString().trim()); + }); + }; + + updateCoreFrequency = (core: number, type: string) => { + this.executeShell((output: string) => { + let cpuFreq = this.state.cpuFreq; + let newFreq = isNormalInteger(output) ? parseInt(output, 10) : -1; + + // update table only if frequency changed + if (cpuFreq[core][type] != newFreq) { + cpuFreq[core][type] = newFreq; + if (type == 'scaling_cur_freq' && cpuFreq[core][type] < 0) { + // cannot find current freq means offline + cpuFreq[core][type] = -2; + } + + this.setState({ + cpuFreq: cpuFreq, + }); + } + }, 'cat /sys/devices/system/cpu/cpu' + core + '/cpufreq/' + type); + }; + + readCoreFrequency = (core: number) => { + let freq = this.state.cpuFreq[core]; + if (freq.cpuinfo_max_freq < 0) { + this.updateCoreFrequency(core, 'cpuinfo_max_freq'); + } + if (freq.cpuinfo_min_freq < 0) { + this.updateCoreFrequency(core, 'cpuinfo_min_freq'); + } + this.updateCoreFrequency(core, 'scaling_cur_freq'); + this.updateCoreFrequency(core, 'scaling_min_freq'); + this.updateCoreFrequency(core, 'scaling_max_freq'); + }; + + onStartMonitor = () => { + if (this.intervalID) { + return; + } + + this.intervalID = setInterval(() => { + for (let i = 0; i < this.state.cpuCount; ++i) { + this.readCoreFrequency(i); + } + }, 500); + + this.setState({ + monitoring: true, + }); + }; + + onStopMonitor = () => { + if (!this.intervalID) { + return; + } else { + clearInterval(this.intervalID); + this.intervalID = null; + this.setState({ + monitoring: false, + }); + this.cleanup(); + } + }; + + cleanup = () => { + let cpuFreq = this.state.cpuFreq; + for (let i = 0; i < this.state.cpuCount; ++i) { + cpuFreq[i].scaling_cur_freq = -1; + cpuFreq[i].scaling_min_freq = -1; + cpuFreq[i].scaling_max_freq = -1; + // we don't cleanup cpuinfo_min_freq, cpuinfo_max_freq + // because usually they are fixed (hardware) + } + this.setState({ + cpuFreq: cpuFreq, + }); + }; + + teardown = () => { + this.cleanup(); + }; + + buildRow = (freq: CPUFrequency) => { + let style = {}; + if (freq.scaling_cur_freq == -2) { + style = { + style: { + backgroundColor: colors.blueTint30, + color: colors.white, + fontWeight: 700, + }, + }; + } else if ( + freq.scaling_min_freq != freq.cpuinfo_min_freq && + freq.scaling_min_freq > 0 && + freq.cpuinfo_min_freq > 0 + ) { + style = { + style: { + backgroundColor: colors.redTint, + color: colors.red, + fontWeight: 700, + }, + }; + } else if ( + freq.scaling_max_freq != freq.cpuinfo_max_freq && + freq.scaling_max_freq > 0 && + freq.cpuinfo_max_freq > 0 + ) { + style = { + style: { + backgroundColor: colors.yellowTint, + color: colors.yellow, + fontWeight: 700, + }, + }; + } + + return { + columns: { + cpu_id: {value: CPU_{freq.cpu_id}}, + scaling_cur_freq: { + value: {formatFrequency(freq.scaling_cur_freq)}, + }, + scaling_min_freq: { + value: {formatFrequency(freq.scaling_min_freq)}, + }, + scaling_max_freq: { + value: {formatFrequency(freq.scaling_max_freq)}, + }, + cpuinfo_min_freq: { + value: {formatFrequency(freq.cpuinfo_min_freq)}, + }, + cpuinfo_max_freq: { + value: {formatFrequency(freq.cpuinfo_max_freq)}, + }, + }, + key: freq.cpu_id, + style, + }; + }; + + frequencyRows = (cpuFreqs: Array): TableRows => { + let rows = []; + for (const cpuFreq of cpuFreqs) { + rows.push(this.buildRow(cpuFreq)); + } + return rows; + }; + + render() { + return ( + + + + {this.state.monitoring ? ( + + ) : ( + + )} + + + + + ); + } +} diff --git a/src/device-plugins/index.js b/src/device-plugins/index.js new file mode 100644 index 000000000..8d831f5d7 --- /dev/null +++ b/src/device-plugins/index.js @@ -0,0 +1,22 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +import {GK} from 'sonar'; +import logs from './logs/index.js'; +import cpu from './cpu/index.js'; +import screen from './screen/index.js'; + +const plugins = [logs]; + +if (GK.get('sonar_uiperf')) { + plugins.push(cpu); +} + +if (GK.get('sonar_screen_plugin')) { + plugins.push(screen); +} + +export const devicePlugins = plugins; diff --git a/src/device-plugins/logs/LogTable.js b/src/device-plugins/logs/LogTable.js new file mode 100644 index 000000000..919c1a735 --- /dev/null +++ b/src/device-plugins/logs/LogTable.js @@ -0,0 +1,562 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type { + TableBodyRow, + TableColumnOrder, + TableColumnSizes, + TableColumns, +} from 'sonar'; +import type {Counter} from './LogWatcher.js'; +import type {DeviceLogEntry} from '../../devices/BaseDevice.js'; + +import { + Text, + ManagedTable, + Button, + colors, + FlexCenter, + LoadingIndicator, + ContextMenu, + FlexColumn, + Glyph, +} from 'sonar'; +import {SonarDevicePlugin, SearchableTable} from 'sonar'; +import textContent from '../../utils/textContent.js'; +import createPaste from '../../utils/createPaste.js'; +import LogWatcher from './LogWatcher'; + +const LOG_WATCHER_LOCAL_STORAGE_KEY = 'LOG_WATCHER_LOCAL_STORAGE_KEY'; + +type Entries = Array<{ + row: TableBodyRow, + entry: DeviceLogEntry, +}>; + +type LogsState = {| + initialising: boolean, + rows: Array, + entries: Entries, + key2entry: {[key: string]: DeviceLogEntry}, + highlightedRows: Array, + counters: Array, +|}; + +const Icon = Glyph.extends({ + marginTop: 5, +}); + +function getLineCount(str: string): number { + let count = 1; + for (let i = 0; i < str.length; i++) { + if (str[i] === '\n') { + count++; + } + } + return count; +} + +function keepKeys(obj, keys) { + const result = {}; + for (const key in obj) { + if (keys.includes(key)) { + result[key] = obj[key]; + } + } + return result; +} + +const COLUMN_SIZE = { + type: 32, + time: 120, + pid: 60, + tid: 60, + tag: 120, + app: 200, + message: 'flex', +}; + +const COLUMNS = { + type: { + value: '', + }, + time: { + value: 'Time', + }, + pid: { + value: 'PID', + }, + tid: { + value: 'TID', + }, + tag: { + value: 'Tag', + }, + app: { + value: 'App', + }, + message: { + value: 'Message', + }, +}; + +const INITIAL_COLUMN_ORDER = [ + { + key: 'type', + visible: true, + }, + { + key: 'time', + visible: false, + }, + { + key: 'pid', + visible: false, + }, + { + key: 'tid', + visible: false, + }, + { + key: 'tag', + visible: true, + }, + { + key: 'app', + visible: true, + }, + { + key: 'message', + visible: true, + }, +]; + +const LOG_TYPES: { + [level: string]: { + label: string, + color: string, + icon?: React.Node, + style?: Object, + }, +} = { + verbose: { + label: 'Verbose', + color: colors.purple, + }, + debug: { + label: 'Debug', + color: colors.grey, + }, + info: { + label: 'Info', + icon: , + color: colors.cyan, + }, + warn: { + label: 'Warn', + style: { + backgroundColor: colors.yellowTint, + color: colors.yellow, + fontWeight: 500, + }, + icon: , + color: colors.yellow, + }, + error: { + label: 'Error', + style: { + backgroundColor: colors.redTint, + color: colors.red, + fontWeight: 500, + }, + icon: , + color: colors.red, + }, + fatal: { + label: 'Fatal', + style: { + backgroundColor: colors.redTint, + color: colors.red, + fontWeight: 700, + }, + icon: , + color: colors.red, + }, +}; + +const DEFAULT_FILTERS = [ + { + type: 'enum', + enum: Object.keys(LOG_TYPES).map(value => ({ + label: LOG_TYPES[value].label, + value, + })), + key: 'type', + value: [], + persistent: true, + }, +]; + +const NonSelectableText = Text.extends({ + alignSelf: 'baseline', + userSelect: 'none', + lineHeight: '130%', + marginTop: 6, +}); + +const LogCount = NonSelectableText.extends( + { + backgroundColor: props => props.color, + borderRadius: '999em', + fontSize: 11, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + marginTop: 4, + width: 16, + height: 16, + color: colors.white, + }, + { + ignoreAttributes: ['color'], + }, +); + +const HiddenScrollText = NonSelectableText.extends({ + '&::-webkit-scrollbar': { + display: 'none', + }, +}); + +function pad(chunk: mixed, len: number): string { + let str = String(chunk); + while (str.length < len) { + str = `0${str}`; + } + return str; +} + +export default class LogTable extends SonarDevicePlugin { + static id = 'DeviceLogs'; + static title = 'Logs'; + static icon = 'arrow-right'; + static keyboardActions = ['clear', 'goToBottom', 'createPaste']; + + onKeyboardAction = (action: string) => { + if (action === 'clear') { + this.clearLogs(); + } else if (action === 'goToBottom') { + this.goToBottom(); + } else if (action === 'createPaste') { + this.createPaste(); + } + }; + + restoreSavedCounters = (): Array => { + const savedCounters = + window.localStorage.getItem(LOG_WATCHER_LOCAL_STORAGE_KEY) || '[]'; + return JSON.parse(savedCounters).map((counter: Counter) => ({ + ...counter, + expression: new RegExp(counter.label, 'gi'), + count: 0, + })); + }; + + state = { + rows: [], + entries: [], + key2entry: {}, + initialising: true, + highlightedRows: [], + counters: this.restoreSavedCounters(), + }; + + tableRef: ?ManagedTable; + columns: TableColumns; + columnSizes: TableColumnSizes; + columnOrder: TableColumnOrder; + + init() { + let batch: Entries = []; + let queued = false; + let counter = 0; + + const supportedColumns = this.device.supportedColumns(); + this.columns = keepKeys(COLUMNS, supportedColumns); + this.columnSizes = keepKeys(COLUMN_SIZE, supportedColumns); + this.columnOrder = INITIAL_COLUMN_ORDER.filter(obj => + supportedColumns.includes(obj.key), + ); + + this.device.addLogListener((entry: DeviceLogEntry) => { + const {icon, style} = + LOG_TYPES[(entry.type: string)] || LOG_TYPES.verbose; + + // clean message + const message = entry.message.trim(); + entry.type === 'error'; + + let counterUpdated = false; + const counters = this.state.counters.map(counter => { + if (message.match(counter.expression)) { + counterUpdated = true; + if (counter.notify) { + new window.Notification(`${counter.label}`, { + body: 'The watched log message appeared', + }); + } + return { + ...counter, + count: counter.count + 1, + }; + } else { + return counter; + } + }); + if (counterUpdated) { + this.setState({counters}); + } + + // build the item, it will either be batched or added straight away + const item = { + entry, + row: { + columns: { + type: { + value: icon, + }, + time: { + value: ( + + {entry.date.toTimeString().split(' ')[0] + + '.' + + pad(entry.date.getMilliseconds(), 3)} + + ), + }, + message: { + value: {message}, + }, + tag: { + value: ( + {entry.tag} + ), + isFilterable: true, + }, + pid: { + value: ( + + {String(entry.pid)} + + ), + isFilterable: true, + }, + tid: { + value: ( + + {String(entry.tid)} + + ), + isFilterable: true, + }, + app: { + value: ( + {entry.app} + ), + isFilterable: true, + }, + }, + height: getLineCount(message) * 15 + 10, // 15px per line height + 8px padding + style, + type: entry.type, + filterValue: entry.message, + key: String(counter++), + }, + }; + + // batch up logs to be processed every 250ms, if we have lots of log + // messages coming in, then calling an setState 200+ times is actually + // pretty expensive + batch.push(item); + + if (!queued) { + queued = true; + + setTimeout(() => { + const thisBatch = batch; + batch = []; + queued = false; + + // update rows/entries + this.setState(state => { + const rows = [...state.rows]; + const entries = [...state.entries]; + const key2entry = {...state.key2entry}; + + for (let i = 0; i < thisBatch.length; i++) { + const {entry, row} = thisBatch[i]; + entries.push({row, entry}); + key2entry[row.key] = entry; + + let previousEntry: ?DeviceLogEntry = null; + + if (i > 0) { + previousEntry = thisBatch[i - 1].entry; + } else if (state.rows.length > 0 && state.entries.length > 0) { + previousEntry = state.entries[state.entries.length - 1].entry; + } + + this.addRowIfNeeded(rows, row, entry, previousEntry); + } + + return { + entries, + rows, + key2entry, + }; + }); + }, 100); + } + }); + + setTimeout(() => { + this.setState({ + initialising: false, + }); + }, 2000); + } + + addRowIfNeeded( + rows: Array, + row: TableBodyRow, + entry: DeviceLogEntry, + previousEntry: ?DeviceLogEntry, + ) { + const previousRow = rows.length > 0 ? rows[rows.length - 1] : null; + if ( + previousRow && + previousEntry && + entry.message === previousEntry.message && + entry.tag === previousEntry.tag && + previousRow.type != null + ) { + const count = (previousRow.columns.time.value.props.count || 1) + 1; + previousRow.columns.type.value = ( + {count} + ); + } else { + rows.push(row); + } + } + + clearLogs = () => { + this.setState({ + entries: [], + rows: [], + highlightedRows: [], + key2entry: {}, + counters: this.state.counters.map(counter => ({ + ...counter, + count: 0, + })), + }); + }; + + createPaste = () => { + let paste = ''; + const mapFn = row => + Object.keys(COLUMNS) + .map(key => textContent(row.columns[key].value)) + .join('\t'); + + if (this.state.highlightedRows.length > 0) { + // create paste from selection + paste = this.state.rows + .filter(row => this.state.highlightedRows.indexOf(row.key) > -1) + .map(mapFn) + .join('\n'); + } else { + // create paste with all rows + paste = this.state.rows.map(mapFn).join('\n'); + } + createPaste(paste); + }; + + setTableRef = (ref: React.ElementRef<*>) => { + this.tableRef = ref; + }; + + goToBottom = () => { + if (this.tableRef != null) { + this.tableRef.scrollToBottom(); + } + }; + + onRowHighlighted = (highlightedRows: Array) => { + this.setState({ + ...this.state, + highlightedRows, + }); + }; + + renderSidebar = () => { + return ( + + this.setState({counters}, () => + window.localStorage.setItem( + LOG_WATCHER_LOCAL_STORAGE_KEY, + JSON.stringify(this.state.counters), + ), + ) + } + /> + ); + }; + + static ContextMenu = ContextMenu.extends({ + flex: 1, + }); + + render() { + const {initialising, highlightedRows, rows} = this.state; + + const contextMenuItems = [ + { + type: 'separator', + }, + { + label: 'Clear all', + click: this.clearLogs, + }, + ]; + return initialising ? ( + + + + ) : ( + + Clear Logs} + /> + + ); + } +} diff --git a/src/device-plugins/logs/LogWatcher.js b/src/device-plugins/logs/LogWatcher.js new file mode 100644 index 000000000..5c005a348 --- /dev/null +++ b/src/device-plugins/logs/LogWatcher.js @@ -0,0 +1,216 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import { + PureComponent, + FlexColumn, + Panel, + Input, + Toolbar, + Text, + ManagedTable, + Button, + colors, +} from 'sonar'; + +export type Counter = { + expression: RegExp, + count: number, + notify: boolean, + label: string, +}; + +type Props = {| + onChange: (counters: Array) => void, + counters: Array, +|}; + +type State = { + input: string, + highlightedRow: ?string, +}; + +const ColumnSizes = { + expression: '70%', + count: '15%', + notify: 'flex', +}; + +const Columns = { + expression: { + value: 'Expression', + resizable: false, + }, + count: { + value: 'Count', + resizable: false, + }, + notify: { + value: 'Notify', + resizable: false, + }, +}; + +const Count = Text.extends({ + alignSelf: 'center', + background: colors.macOSHighlightActive, + color: colors.white, + fontSize: 12, + fontWeight: 500, + textAlign: 'center', + borderRadius: '999em', + padding: '4px 9px 3px', + lineHeight: '100%', + marginLeft: 'auto', +}); + +const Checkbox = Input.extends({ + lineHeight: '100%', + padding: 0, + margin: 0, + height: 'auto', + alignSelf: 'center', +}); + +const ExpressionInput = Input.extends({ + flexGrow: 1, +}); + +const WatcherPanel = Panel.extends({ + minHeight: 200, +}); + +export default class LogWatcher extends PureComponent { + state = { + input: '', + highlightedRow: null, + }; + + _inputRef: ?HTMLInputElement; + + onAdd = () => { + if ( + this.props.counters.findIndex(({label}) => label === this.state.input) > + -1 + ) { + // prevent duplicates + return; + } + this.props.onChange([ + ...this.props.counters, + { + label: this.state.input, + expression: new RegExp(this.state.input, 'gi'), + notify: false, + count: 0, + }, + ]); + this.setState({input: ''}); + }; + + onChange = (e: SyntheticInputEvent) => { + this.setState({ + input: e.target.value, + }); + }; + + resetCount = (index: number) => { + const newCounters = [...this.props.counters]; + newCounters[index] = { + ...newCounters[index], + count: 0, + }; + this.props.onChange(newCounters); + }; + + buildRows = () => { + return this.props.counters.map(({label, count, notify}, i) => ({ + columns: { + expression: { + value: {label}, + }, + count: { + value: this.resetCount(i)}>{count}, + }, + notify: { + value: ( + this.setNotification(i, !notify)} + /> + ), + }, + }, + key: label, + })); + }; + + setNotification = (index: number, notify: boolean) => { + const newCounters: Array = [...this.props.counters]; + newCounters[index] = { + ...newCounters[index], + notify, + }; + this.props.onChange(newCounters); + }; + + onRowHighlighted = (rows: Array) => { + this.setState({ + highlightedRow: rows.length === 1 ? rows[0] : null, + }); + }; + + onKeyDown = (e: SyntheticKeyboardEvent<>) => { + if ( + (e.key === 'Delete' || e.key === 'Backspace') && + this.state.highlightedRow != null + ) { + this.props.onChange( + this.props.counters.filter( + ({label}) => label !== this.state.highlightedRow, + ), + ); + } + }; + + onSubmit = (e: SyntheticKeyboardEvent<>) => { + if (e.key === 'Enter') { + this.onAdd(); + } + }; + + render() { + return ( + + + + + + + + + + ); + } +} diff --git a/src/device-plugins/logs/index.js b/src/device-plugins/logs/index.js new file mode 100644 index 000000000..262450342 --- /dev/null +++ b/src/device-plugins/logs/index.js @@ -0,0 +1,8 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +import LogTable from './LogTable.js'; +export default LogTable; diff --git a/src/device-plugins/screen/index.js b/src/device-plugins/screen/index.js new file mode 100644 index 000000000..086f566da --- /dev/null +++ b/src/device-plugins/screen/index.js @@ -0,0 +1,282 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {SonarDevicePlugin} from 'sonar'; + +import { + Button, + FlexColumn, + FlexRow, + LoadingIndicator, + styled, + colors, + Component, +} from 'sonar'; + +const os = require('os'); +const fs = require('fs'); +const adb = require('adbkit-fb'); +const path = require('path'); +const exec = require('child_process').exec; +const SCREENSHOT_FILE_NAME = 'screen.png'; +const VIDEO_FILE_NAME = 'video.mp4'; +const SCREENSHOT_PATH = path.join( + os.homedir(), + '/.sonar/', + SCREENSHOT_FILE_NAME, +); +const VIDEO_PATH = path.join(os.homedir(), '.sonar', VIDEO_FILE_NAME); + +type AndroidDevice = any; +type AdbClient = any; +type PullTransfer = any; + +type State = {| + pullingData: boolean, + recording: boolean, + recordingEnabled: boolean, + capturingScreenshot: boolean, +|}; + +const BigButton = Button.extends({ + height: 200, + width: 200, + flexGrow: 1, + fontSize: 24, +}); + +const ButtonContainer = FlexRow.extends({ + alignItems: 'center', + justifyContent: 'space-around', + padding: 20, +}); + +const LoadingSpinnerContainer = FlexRow.extends({ + flexGrow: 1, + padding: 24, + justifyContent: 'center', + alignItems: 'center', +}); + +const LoadingSpinnerText = styled.text({ + fontSize: 24, + marginLeft: 12, + color: colors.grey, +}); + +class LoadingSpinner extends Component<{}, {}> { + render() { + return ( + + + Pulling files from device... + + ); + } +} + +function openFile(path: string): Promise<*> { + return new Promise((resolve, reject) => { + exec(`${getOpenCommand()} ${path}`, (error, stdout, stderr) => { + if (error) { + reject(error); + } else { + resolve(path); + } + }); + }); +} + +function getOpenCommand(): string { + //TODO: TESTED ONLY ON MAC! + switch (os.platform()) { + case 'win32': + return 'start'; + case 'linux': + return 'xdg-open'; + default: + return 'open'; + } +} + +function writePngStreamToFile(stream: PullTransfer): Promise { + return new Promise((resolve, reject) => { + stream.on('end', () => { + resolve(SCREENSHOT_PATH); + }); + stream.on('error', reject); + stream.pipe(fs.createWriteStream(SCREENSHOT_PATH)); + }); +} + +export default class ScreenPlugin extends SonarDevicePlugin { + static id = 'DeviceScreen'; + static title = 'Screen'; + static icon = 'mobile'; + + device: AndroidDevice; + adbClient: AdbClient; + + init() { + this.adbClient = this.device.adb; + + this.executeShell( + `[ ! -f /system/bin/screenrecord ] && echo "File does not exist"`, + ).then(output => { + if (output) { + console.error( + 'screenrecord util does not exist. Most likely it is an emulator which does not support screen recording via adb', + ); + this.setState({ + recordingEnabled: false, + }); + } else { + this.setState({ + recordingEnabled: true, + }); + } + }); + } + + captureScreenshot = () => { + return this.adbClient + .screencap(this.device.serial) + .then(writePngStreamToFile) + .then(openFile) + .catch(error => { + //TODO: proper logging? + console.error(error); + }); + }; + + pullFromDevice = (src: string, dst: string): Promise => { + return new Promise((resolve, reject) => { + return this.adbClient.pull(this.device.serial, src).then(stream => { + stream.on('end', () => { + resolve(dst); + }); + stream.on('error', reject); + stream.pipe(fs.createWriteStream(dst)); + }); + }); + }; + + onRecordingClicked = () => { + if (this.state.recording) { + this.stopRecording(); + } else { + this.startRecording(); + } + }; + + onScreenshotClicked = () => { + var self = this; + this.setState({ + capturingScreenshot: true, + }); + this.captureScreenshot().then(() => { + self.setState({ + capturingScreenshot: false, + }); + }); + }; + + startRecording = () => { + const self = this; + this.setState({ + recording: true, + }); + this.executeShell(`screenrecord --bugreport /sdcard/${VIDEO_FILE_NAME}`) + .then(output => { + if (output) { + throw output; + } + }) + .then(() => { + self.setState({ + recording: false, + pullingData: true, + }); + }) + .then((): Promise => { + return self.pullFromDevice(`/sdcard/${VIDEO_FILE_NAME}`, VIDEO_PATH); + }) + .then(openFile) + .then(() => { + self.executeShell(`rm /sdcard/${VIDEO_FILE_NAME}`); + }) + .then(() => { + self.setState({ + pullingData: false, + }); + }) + .catch(error => { + console.error(`unable to capture video: ${error}`); + self.setState({ + recording: false, + pullingData: false, + }); + }); + }; + + stopRecording = () => { + this.executeShell(`pgrep 'screenrecord' -L 2`); + }; + + executeShell = (command: string): Promise => { + return this.adbClient + .shell(this.device.serial, command) + .then(adb.util.readAll) + .then(output => { + return new Promise((resolve, reject) => { + resolve(output.toString().trim()); + }); + }); + }; + + getLoadingSpinner = () => { + return this.state.pullingData ? : null; + }; + + render() { + const recordingEnabled = + this.state.recordingEnabled && + !this.state.capturingScreenshot && + !this.state.pullingData; + const screenshotEnabled = + !this.state.recording && + !this.state.capturingScreenshot && + !this.state.pullingData; + return ( + + + + {!this.state.recording ? 'Record screen' : 'Stop recording'} + + + Take screenshot + + + {this.getLoadingSpinner()} + + ); + } +} diff --git a/src/devices/AndroidDevice.js b/src/devices/AndroidDevice.js new file mode 100644 index 000000000..bb37e88f7 --- /dev/null +++ b/src/devices/AndroidDevice.js @@ -0,0 +1,95 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type {DeviceType, DeviceShell, DeviceLogListener} from './BaseDevice.js'; + +import {Priority} from 'adbkit-logcat-fb'; +import child_process from 'child_process'; + +// TODO +import BaseDevice from './BaseDevice.js'; + +type ADBClient = any; + +export default class AndroidDevice extends BaseDevice { + constructor( + serial: string, + deviceType: DeviceType, + title: string, + adb: ADBClient, + ) { + super(serial, deviceType, title); + this.adb = adb; + if (deviceType == 'physical') { + this.supportedPlugins.push('DeviceCPU'); + } + } + + supportedPlugins = [ + 'DeviceLogs', + 'DeviceShell', + 'DeviceFiles', + 'DeviceScreen', + ]; + icon = 'icons/android.svg'; + os = 'Android'; + adb: ADBClient; + pidAppMapping: {[key: number]: string} = {}; + + supportedColumns(): Array { + return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time']; + } + + addLogListener(callback: DeviceLogListener) { + this.adb.openLogcat(this.serial).then(reader => { + reader.on('entry', async entry => { + let type = 'unknown'; + if (entry.priority === Priority.VERBOSE) { + type = 'verbose'; + } + if (entry.priority === Priority.DEBUG) { + type = 'debug'; + } + if (entry.priority === Priority.INFO) { + type = 'info'; + } + if (entry.priority === Priority.WARN) { + type = 'warn'; + } + if (entry.priority === Priority.ERROR) { + type = 'error'; + } + if (entry.priority === Priority.FATAL) { + type = 'fatal'; + } + + callback({ + tag: entry.tag, + pid: entry.pid, + tid: entry.tid, + message: entry.message, + date: entry.date, + type, + }); + }); + }); + } + + reverse(): Promise { + if (this.deviceType === 'physical') { + return this.adb + .reverse(this.serial, 'tcp:8088', 'tcp:8088') + .then(_ => this.adb.reverse(this.serial, 'tcp:8089', 'tcp:8089')); + } else { + return Promise.resolve(); + } + } + + spawnShell(): DeviceShell { + return child_process.spawn('adb', ['-s', this.serial, 'shell', '-t', '-t']); + } +} diff --git a/src/devices/BaseDevice.js b/src/devices/BaseDevice.js new file mode 100644 index 000000000..effb9d06a --- /dev/null +++ b/src/devices/BaseDevice.js @@ -0,0 +1,78 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type stream from 'stream'; +import {SonarDevicePlugin} from 'sonar'; + +export type DeviceLogEntry = { + date: Date, + pid: number, + tid: number, + app?: string, + type: 'unknown' | 'verbose' | 'debug' | 'info' | 'warn' | 'error' | 'fatal', + tag: string, + message: string, +}; + +export type DeviceShell = { + stdout: stream.Readable, + stderr: stream.Readable, + stdin: stream.Writable, +}; + +export type DeviceLogListener = (entry: DeviceLogEntry) => void; + +export type DeviceType = 'emulator' | 'physical'; + +export default class BaseDevice { + constructor(serial: string, deviceType: DeviceType, title: string) { + this.serial = serial; + this.title = title; + this.deviceType = deviceType; + } + + // operating system of this device + os: string; + + // human readable name for this device + title: string; + + // type of this device + deviceType: DeviceType; + + // serial number for this device + serial: string; + + // supported device plugins for this platform + supportedPlugins: Array = []; + + // possible src of icon to display next to the device title + icon: ?string; + + supportsPlugin(DevicePlugin: Class>) { + return this.supportedPlugins.includes(DevicePlugin.id); + } + + // ensure that we don't serialise devices + toJSON() { + return null; + } + + teardown() {} + + supportedColumns(): Array { + throw new Error('unimplemented'); + } + + addLogListener(listener: DeviceLogListener) { + throw new Error('unimplemented'); + } + + spawnShell(): DeviceShell { + throw new Error('unimplemented'); + } +} diff --git a/src/devices/IOSDevice.js b/src/devices/IOSDevice.js new file mode 100644 index 000000000..03fda8456 --- /dev/null +++ b/src/devices/IOSDevice.js @@ -0,0 +1,162 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type { + DeviceType, + DeviceLogEntry, + DeviceLogListener, +} from './BaseDevice.js'; +import child_process from 'child_process'; +import BaseDevice from './BaseDevice.js'; +import JSONStream from 'JSONStream'; +import {Transform} from 'stream'; + +type RawLogEntry = { + activityID: string, // Number in string format + eventMessage: string, + eventType: string, + machTimestamp: number, + processID: number, + processImagePath: string, + processImageUUID: string, + processUniqueID: number, + senderImagePath: string, + senderImageUUID: string, + senderProgramCounter: number, + threadID: number, + timestamp: string, // "2017-09-27 16:21:15.771213-0400" + timezoneName: string, + traceID: string, +}; + +export default class IOSDevice extends BaseDevice { + supportedPlugins = ['DeviceLogs']; + icon = 'icons/ios.svg'; + os = 'iOS'; + + log: any; + buffer: string; + + constructor(serial: string, deviceType: DeviceType, title: string) { + super(serial, deviceType, title); + + this.buffer = ''; + this.log = null; + } + + teardown() { + if (this.log) { + this.log.kill(); + } + } + + supportedColumns(): Array { + return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time']; + } + + addLogListener(callback: DeviceLogListener) { + if (!this.log) { + this.log = child_process.spawn( + 'xcrun', + [ + 'simctl', + 'spawn', + 'booted', + 'log', + 'stream', + '--style', + 'json', + '--predicate', + 'senderImagePath contains "Containers"', + '--info', + '--debug', + ], + {}, + ); + + this.log.on('error', err => { + console.error(err); + }); + + this.log.stderr.on('data', data => { + console.error(data.toString()); + }); + + this.log.on('exit', () => { + this.log = null; + }); + } + + this.log.stdout + .pipe(new StripLogPrefix()) + .pipe(JSONStream.parse('*')) + .on('data', (data: RawLogEntry) => { + callback(IOSDevice.parseLogEntry(data)); + }); + } + + static parseLogEntry(entry: RawLogEntry): DeviceLogEntry { + let type = 'unknown'; + if (entry.eventMessage.indexOf('[debug]') !== -1) { + type = 'debug'; + } else if (entry.eventMessage.indexOf('[info]') !== -1) { + type = 'info'; + } else if (entry.eventMessage.indexOf('[warn]') !== -1) { + type = 'warn'; + } else if (entry.eventMessage.indexOf('[error]') !== -1) { + type = 'error'; + } + + // remove timestamp in front of message + entry.eventMessage = entry.eventMessage.replace( + /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} /, + '', + ); + + // remove type from mesage + entry.eventMessage = entry.eventMessage.replace( + /^\[(debug|info|warn|error)\]/, + '', + ); + + const tags = entry.processImagePath.split('/'); + const tag = tags[tags.length - 1]; + + return { + date: new Date(entry.timestamp), + pid: entry.processID, + tid: entry.threadID, + tag, + message: entry.eventMessage, + type, + }; + } +} + +// Used to strip the initial output of the logging utility where it prints out settings. +// We know the log stream is json so it starts with an open brace. +class StripLogPrefix extends Transform { + passedPrefix = false; + + _transform( + data: any, + encoding: string, + callback: (err?: Error, data?: any) => void, + ) { + if (this.passedPrefix) { + this.push(data); + } else { + const dataString = data.toString(); + const index = dataString.indexOf('['); + if (index >= 0) { + this.push(dataString.substring(index)); + this.passedPrefix = true; + } + } + callback(); + } +} diff --git a/src/devices/OculusDevice.js b/src/devices/OculusDevice.js new file mode 100644 index 000000000..ad549541f --- /dev/null +++ b/src/devices/OculusDevice.js @@ -0,0 +1,142 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type { + DeviceType, + DeviceLogEntry, + DeviceLogListener, +} from './BaseDevice.js'; + +import fs from 'fs-extra'; +import os from 'os'; +import path from 'path'; + +import BaseDevice from './BaseDevice.js'; + +function getLogsPath(fileName: ?string): string { + const dir = '/AppData/Local/Oculus/'; + if (fileName) { + return path.join(os.homedir(), dir, fileName); + } + return path.join(os.homedir(), dir); +} + +export default class OculusDevice extends BaseDevice { + supportedPlugins = ['DeviceLogs']; + icon = 'icons/oculus.png'; + os = 'Oculus'; + + watcher: any; + processedFileMap: {}; + watchedFile: ?string; + timer: TimeoutID; + + constructor(serial: string, deviceType: DeviceType, title: string) { + super(serial, deviceType, title); + + this.watcher = null; + this.processedFileMap = {}; + } + + teardown() { + clearTimeout(this.timer); + const file = this.watchedFile; + if (file) { + fs.unwatchFile(path.join(getLogsPath(), file)); + } + } + + supportedColumns(): Array { + return ['date', 'tag', 'message', 'type', 'time']; + } + + mapLogLevel(type: string): $PropertyType { + switch (type) { + case 'WARNING': + return 'warn'; + case '!ERROR!': + return 'error'; + case 'DEBUG': + return 'debug'; + case 'INFO': + return 'info'; + default: + return 'verbose'; + } + } + + processText(text: Buffer, callback: DeviceLogListener) { + text + .toString() + .split('\r\n') + .forEach(line => { + const regex = /(.*){(\S+)}\s*\[([\w :.\\]+)\](.*)/; + const match = regex.exec(line); + if (match && match.length === 5) { + callback({ + tid: 0, + pid: 0, + date: new Date(Date.parse(match[1])), + type: this.mapLogLevel(match[2]), + tag: match[3], + message: match[4], + }); + } else if (line.trim() === '') { + // skip + } else { + callback({ + tid: 0, + pid: 0, + date: new Date(), + type: 'verbose', + tag: 'failed-parse', + message: line, + }); + } + }); + } + + addLogListener = (callback: DeviceLogListener) => { + this.setupListener(callback); + }; + + async setupListener(callback: DeviceLogListener) { + const files = await fs.readdir(getLogsPath()); + this.watchedFile = files + .filter(file => file.startsWith('Service_')) + .sort() + .pop(); + this.watch(callback); + this.timer = setTimeout(() => this.checkForNewLog(callback), 5000); + } + + watch(callback: DeviceLogListener) { + const filePath = getLogsPath(this.watchedFile); + fs.watchFile(filePath, async (current, previous) => { + const readLen = current.size - previous.size; + const buffer = new Buffer(readLen); + const fd = await fs.open(filePath, 'r'); + await fs.read(fd, buffer, 0, readLen, previous.size); + this.processText(buffer, callback); + }); + } + + async checkForNewLog(callback: DeviceLogListener) { + const files = await fs.readdir(getLogsPath()); + const latestLog = files + .filter(file => file.startsWith('Service_')) + .sort() + .pop(); + if (this.watchedFile !== latestLog) { + const oldFilePath = getLogsPath(this.watchedFile); + fs.unwatchFile(oldFilePath); + this.watchedFile = latestLog; + this.watch(callback); + } + this.timer = setTimeout(() => this.checkForNewLog(callback), 5000); + } +} diff --git a/src/dispatcher/androidDevice.js b/src/dispatcher/androidDevice.js new file mode 100644 index 000000000..5a9cc0eee --- /dev/null +++ b/src/dispatcher/androidDevice.js @@ -0,0 +1,97 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import AndroidDevice from '../devices/AndroidDevice'; +import type {Store} from '../reducers/index.js'; +import type BaseDevice from '../devices/BaseDevice'; +const adb = require('adbkit-fb'); + +function createDecive(client, device): Promise { + return new Promise((resolve, reject) => { + const type = + device.type !== 'device' || device.id.startsWith('emulator') + ? 'emulator' + : 'physical'; + client.getProperties(device.id).then(props => { + const androidDevice = new AndroidDevice( + device.id, + type, + props['ro.product.model'], + client, + ); + androidDevice.reverse(); + resolve(androidDevice); + }); + }); +} + +export default (store: Store) => { + const client = adb.createClient(); + + client + .trackDevices() + .then(tracker => { + tracker.on('error', err => { + if (err.message === 'Connection closed') { + // adb server has shutdown, remove all android devices + const {devices} = store.getState(); + const deviceIDsToRemove: Array = devices + .filter((device: BaseDevice) => device instanceof AndroidDevice) + .map((device: BaseDevice) => device.serial); + + store.dispatch({ + type: 'UNREGISTER_DEVICES', + payload: new Set(deviceIDsToRemove), + }); + console.error( + 'adb server shutdown. Run `adb start-server` and restart Sonar.', + ); + } else { + throw err; + } + }); + + tracker.on('add', async device => { + const androidDevice = await createDecive(client, device); + if (device.type !== 'offline') { + store.dispatch({ + type: 'REGISTER_DEVICE', + payload: androidDevice, + }); + } + }); + + tracker.on('change', async device => { + if (device.type === 'offline') { + store.dispatch({ + type: 'UNREGISTER_DEVICES', + payload: new Set([device.id]), + }); + } else { + const androidDevice = await createDecive(client, device); + store.dispatch({ + type: 'REGISTER_DEVICE', + payload: androidDevice, + }); + } + }); + + tracker.on('remove', device => { + store.dispatch({ + type: 'UNREGISTER_DEVICES', + payload: new Set([device.id]), + }); + }); + }) + .catch(err => { + if (err.code === 'ECONNREFUSED') { + // adb server isn't running + } else { + throw err; + } + }); +}; diff --git a/src/dispatcher/application.js b/src/dispatcher/application.js new file mode 100644 index 000000000..0a4190347 --- /dev/null +++ b/src/dispatcher/application.js @@ -0,0 +1,25 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {remote} from 'electron'; +import type {Store} from '../reducers/index.js'; + +export default (store: Store) => { + const currentWindow = remote.getCurrentWindow(); + currentWindow.on('focus', () => + store.dispatch({ + type: 'windowIsFocused', + payload: true, + }), + ); + currentWindow.on('blur', () => + store.dispatch({ + type: 'windowIsFocused', + payload: false, + }), + ); +}; diff --git a/src/dispatcher/iOSDevice.js b/src/dispatcher/iOSDevice.js new file mode 100644 index 000000000..74070c24e --- /dev/null +++ b/src/dispatcher/iOSDevice.js @@ -0,0 +1,99 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type {ChildProcess} from 'child_process'; +import type {Store} from '../reducers/index.js'; +import child_process from 'child_process'; +import IOSDevice from '../devices/IOSDevice'; + +type iOSSimulatorDevice = {| + state: 'Booted' | 'Shutdown' | 'Shutting Down', + availability: string, + name: string, + udid: string, +|}; + +type IOSDeviceMap = {[id: string]: Array}; + +// start port forwarding server for real device connections +const portForwarder: ChildProcess = child_process.exec( + 'PortForwardingMacApp.app/Contents/MacOS/PortForwardingMacApp -portForward=8088 -multiplexChannelPort=8078', +); +window.addEventListener('beforeunload', () => { + portForwarder.kill(); +}); + +function querySimulatorDevices(): Promise { + return new Promise((resolve, reject) => { + child_process.execFile( + 'xcrun', + ['simctl', 'list', 'devices', '--json'], + {encoding: 'utf8'}, + (err, stdout) => { + if (err) { + console.error('Failed to load iOS devices', err); + return resolve({}); + } + + try { + const {devices} = JSON.parse(stdout.toString()); + resolve(devices); + } catch (err) { + console.error('Failed to parse iOS device list', err); + resolve({}); + } + }, + ); + }); +} + +export default (store: Store) => { + // monitoring iOS devices only available on MacOS. + if (process.platform !== 'darwin') { + return; + } + setInterval(() => { + const {devices} = store.getState(); + querySimulatorDevices().then((simulatorDevices: IOSDeviceMap) => { + const simulators: Array = Object.values( + simulatorDevices, + // $FlowFixMe + ).reduce((acc, cv) => acc.concat(cv), []); + + const currentDeviceIDs: Set = new Set( + devices + .filter(device => device instanceof IOSDevice) + .map(device => device.serial), + ); + + const deviceIDsToRemove = new Set(); + simulators.forEach((simulator: iOSSimulatorDevice) => { + const isRunning = + simulator.state === 'Booted' && + simulator.availability === '(available)'; + + if (isRunning && !currentDeviceIDs.has(simulator.udid)) { + // create device + store.dispatch({ + type: 'REGISTER_DEVICE', + payload: new IOSDevice(simulator.udid, 'emulator', simulator.name), + }); + } else if (!isRunning && currentDeviceIDs.has(simulator.udid)) { + deviceIDsToRemove.add(simulator.udid); + // delete device + } + }); + + if (deviceIDsToRemove.size > 0) { + store.dispatch({ + type: 'UNREGISTER_DEVICES', + payload: deviceIDsToRemove, + }); + } + }); + }, 3000); +}; diff --git a/src/dispatcher/index.js b/src/dispatcher/index.js new file mode 100644 index 000000000..dc22d6de5 --- /dev/null +++ b/src/dispatcher/index.js @@ -0,0 +1,14 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import androidDevice from './androidDevice'; +import iOSDevice from './iOSDevice'; +import application from './application'; +import type {Store} from '../reducers/index.js'; + +export default (store: Store) => + [application, androidDevice, iOSDevice].forEach(fn => fn(store)); diff --git a/src/fb-stubs/BugReporter.js b/src/fb-stubs/BugReporter.js new file mode 100644 index 000000000..ac7d2961c --- /dev/null +++ b/src/fb-stubs/BugReporter.js @@ -0,0 +1,15 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import LogManager from './Logger'; + +export default class BugReporter { + constructor(logManager: LogManager) {} + async report(title: string, body: string): Promise { + return Promise.resolve(-1); + } +} diff --git a/src/fb-stubs/ErrorReporter.js b/src/fb-stubs/ErrorReporter.js new file mode 100644 index 000000000..330a43aab --- /dev/null +++ b/src/fb-stubs/ErrorReporter.js @@ -0,0 +1,21 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +export function cleanStack(stack: string, loc: ?string) {} +import type ScribeLogger from './ScribeLogger'; + +export type ObjectError = + | Error + | { + message: string, + stack?: string, + }; + +export default class ErrorReporter { + constructor(scribeLogger: ScribeLogger) {} + report(err: ObjectError) {} +} diff --git a/src/fb-stubs/GK.js b/src/fb-stubs/GK.js new file mode 100644 index 000000000..e41b6b81c --- /dev/null +++ b/src/fb-stubs/GK.js @@ -0,0 +1,16 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +export type GKID = string; + +export default class GK { + static init() {} + + static get(id: GKID): boolean { + return false; + } +} diff --git a/src/fb-stubs/Logger.js b/src/fb-stubs/Logger.js new file mode 100644 index 000000000..a426239dc --- /dev/null +++ b/src/fb-stubs/Logger.js @@ -0,0 +1,40 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +export type LogTypes = 'error' | 'warn' | 'info' | 'debug'; +export type TrackType = 'duration' | 'usage' | 'performance'; +import ScribeLogger from './ScribeLogger'; + +export default class LogManager { + constructor() { + this.scribeLogger = new ScribeLogger(this); + } + + scribeLogger: ScribeLogger; + + track(type: TrackType, event: string, data: ?any) {} + + trackTimeSince(mark: string, eventName: ?string) {} + + info(data: any, category: string) { + // eslint-disable-next-line + console.info(data, category); + } + + warn(data: any, category: string) { + console.warn(data, category); + } + + error(data: any, category: string) { + console.error(data, category); + } + + debug(data: any, category: string) { + // eslint-disable-next-line + console.debug(data, category); + } +} diff --git a/src/fb-stubs/ScribeLogger.js b/src/fb-stubs/ScribeLogger.js new file mode 100644 index 000000000..84b308388 --- /dev/null +++ b/src/fb-stubs/ScribeLogger.js @@ -0,0 +1,18 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +export type ScribeMessage = {| + category: string, + message: string, +|}; + +import type Logger from './Logger.js'; + +export default class ScribeLogger { + constructor(logger: Logger) {} + send(message: ScribeMessage) {} +} diff --git a/src/fb-stubs/config.js b/src/fb-stubs/config.js new file mode 100644 index 000000000..3b1bd3181 --- /dev/null +++ b/src/fb-stubs/config.js @@ -0,0 +1,11 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +export default { + updateServer: 'https://www.facebook.com/sonar/public/latest.json', + bugReportButtonVisible: false, +}; diff --git a/src/fb-stubs/constants.js b/src/fb-stubs/constants.js new file mode 100644 index 000000000..8e7d04fba --- /dev/null +++ b/src/fb-stubs/constants.js @@ -0,0 +1,19 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +export const GRAPH_APP_ID = ''; +export const GRAPH_CLIENT_TOKEN = ''; +export const GRAPH_ACCESS_TOKEN = ''; + +// this provides elevated access to scribe. we really shouldn't be exposing this. +// need to investigate how to abstract the scribe logging so it's safe. +export const GRAPH_SECRET = ''; +export const GRAPH_SECRET_ACCESS_TOKEN = ''; + +// Provides access to Insights Validation ednpoint on interngraph +export const INSIGHT_INTERN_APP_ID = ''; +export const INSIGHT_INTERN_APP_TOKEN = ''; diff --git a/src/index.js b/src/index.js new file mode 100644 index 000000000..776db0f76 --- /dev/null +++ b/src/index.js @@ -0,0 +1,21 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +export {default as styled} from './ui/styled/index.js'; +export * from './ui/index.js'; +export * from './utils/index.js'; + +export {default as GK} from './fb-stubs/GK.js'; +export {SonarBasePlugin, SonarPlugin, SonarDevicePlugin} from './plugin.js'; +export {createTablePlugin} from './createTablePlugin.js'; + +export * from './init.js'; +export {default} from './init.js'; + +export {default as AndroidDevice} from './devices/AndroidDevice.js'; +export {default as Device} from './devices/BaseDevice.js'; +export {default as IOSDevice} from './devices/IOSDevice.js'; diff --git a/src/init.js b/src/init.js new file mode 100644 index 000000000..97f52223b --- /dev/null +++ b/src/init.js @@ -0,0 +1,48 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {Provider} from 'react-redux'; +import ReactDOM from 'react-dom'; +import {ContextMenuProvider} from 'sonar'; +import {precachedIcons} from './utils/icons.js'; +import GK from './fb-stubs/GK.js'; +import App from './App.js'; +import {createStore} from 'redux'; +import reducers from './reducers/index.js'; +import dispatcher from './dispatcher/index.js'; +const path = require('path'); + +const store = createStore( + reducers, + window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), +); + +dispatcher(store); + +GK.init(); + +const AppFrame = () => ( + + + + + +); + +// $FlowFixMe: this element exists! +ReactDOM.render(, document.getElementById('root')); +// $FlowFixMe: service workers exist! +navigator.serviceWorker + .register( + process.env.NODE_ENV === 'production' + ? path.join(__dirname, 'serviceWorker.js') + : './serviceWorker.js', + ) + .then(r => { + (r.installing || r.active).postMessage({precachedIcons}); + }) + .catch(console.error); diff --git a/src/plugin.js b/src/plugin.js new file mode 100644 index 000000000..f9d5be19e --- /dev/null +++ b/src/plugin.js @@ -0,0 +1,241 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type {KeyboardActions} from './MenuBar.js'; +import type {App} from './App.js'; +import type {Client} from './server.js'; + +import BaseDevice from './devices/BaseDevice.js'; +import {AndroidDevice, IOSDevice} from 'sonar'; + +const invariant = require('invariant'); + +export type PluginClient = {| + send: (method: string, params?: Object) => void, + call: (method: string, params?: Object) => Promise, + subscribe: (method: string, callback: (params: any) => void) => void, +|}; + +type PluginTarget = BaseDevice | Client; + +/** + * This is a wrapper for a plugin instance and state. We have a special toJSON method that removes the plugin + * instance and any state if it's not set to be persisted. + */ +export class PluginStateContainer { + constructor(plugin: SonarBasePlugin<>, state: Object) { + this.plugin = plugin; + this.state = state; + } + + plugin: ?SonarBasePlugin<>; + state: Object; + + toJSON() { + return { + plugin: null, + state: this.plugin != null ? this.state : null, + }; + } +} + +export class SonarBasePlugin { + constructor() { + // $FlowFixMe: this is fine + this.state = {}; + } + + static title: string = 'Unknown'; + static id: string = 'Unknown'; + static icon: string = 'apps'; + static persist: boolean = true; + static keyboardActions: ?KeyboardActions; + static screenshot: ?string; + + // forbid instance properties that should be static + title: empty; + id: empty; + persist: empty; + + namespaceKey: string; + reducers: { + [actionName: string]: (state: State, actionData: Object) => $Shape, + } = {}; + app: App; + state: State; + renderSidebar: ?() => ?React.Element<*>; + renderIntro: ?() => ?React.Element<*>; + onKeyboardAction: ?(action: string) => void; + + toJSON() { + return null; + } + + // methods to be overriden by plugins + init(): void {} + teardown(): void {} + // methods to be overridden by subclasses + _init(): void {} + _teardown(): void {} + _setup(target: PluginTarget, app: App) { + this.app = app; + } + + setState( + state: $Shape | ((state: State) => $Shape), + callback?: () => void, + ) { + if (typeof state === 'function') { + state = state(this.state); + } + this.state = Object.assign({}, this.state, state); + + const pluginKey = this.constructor.id; + const namespaceKey = this.namespaceKey; + const appState = this.app.state; + + // update app state + this.app.setState( + { + plugins: { + ...appState.plugins, + [namespaceKey]: { + ...(appState.plugins[namespaceKey] || {}), + [pluginKey]: new PluginStateContainer(this, this.state), + }, + }, + }, + callback, + ); + } + + dispatchAction(actionData: Actions) { + // $FlowFixMe + const action = this.reducers[actionData.type]; + if (!action) { + // $FlowFixMe + throw new ReferenceError(`Unknown action ${actionData.type}`); + } + + if (typeof action === 'function') { + this.setState(action.call(this, this.state, actionData)); + } else { + // $FlowFixMe + throw new TypeError(`Reducer ${actionData.type} isn't a function`); + } + } + + render(): any { + return null; + } +} + +export class SonarDevicePlugin< + State: Object = any, + Actions = any, +> extends SonarBasePlugin { + device: BaseDevice; + + _setup(target: PluginTarget, app: App) { + invariant(target instanceof BaseDevice, 'expected instanceof Client'); + const device: BaseDevice = target; + + this.namespaceKey = device.serial; + this.device = device; + super._setup(device, app); + } + + _teardown() { + this.teardown(); + } + + _init() { + this.init(); + } +} + +export class SonarPlugin< + State: Object = any, + Actions = any, +> extends SonarBasePlugin { + constructor() { + super(); + this.subscriptions = []; + } + + subscriptions: Array<{ + method: string, + callback: Function, + }>; + + client: PluginClient; + realClient: Client; + + getDevice(): ?BaseDevice { + return this.realClient.getDevice(); + } + + getAndroidDevice(): AndroidDevice { + const device = this.getDevice(); + invariant( + device != null && device instanceof AndroidDevice, + 'expected android device', + ); + return device; + } + + getIOSDevice() { + const device = this.getDevice(); + invariant( + device != null && device instanceof IOSDevice, + 'expected ios device', + ); + return device; + } + + _setup(target: any, app: App) { + /* We have to type the above as `any` since if we import the actual Client we have an + unresolvable dependency cycle */ + + const realClient: Client = target; + const id: string = this.constructor.id; + + this.namespaceKey = realClient.id; + this.realClient = realClient; + this.client = { + call: (method, params) => realClient.call(id, method, params), + send: (method, params) => realClient.send(id, method, params), + subscribe: (method, callback) => { + this.subscriptions.push({ + method, + callback, + }); + realClient.subscribe(id, method, callback); + }, + }; + + super._setup(realClient, app); + } + + _teardown() { + // automatically unsubscribe subscriptions + for (const {method, callback} of this.subscriptions) { + this.realClient.unsubscribe(this.constructor.id, method, callback); + } + + // run plugin teardown + this.teardown(); + if (this.realClient.connected) { + this.realClient.rawSend('deinit', {plugin: this.constructor.id}); + } + } + + _init() { + this.realClient.rawSend('init', {plugin: this.constructor.id}); + this.init(); + } +} diff --git a/src/plugins/index.js b/src/plugins/index.js new file mode 100644 index 000000000..1c9c3b377 --- /dev/null +++ b/src/plugins/index.js @@ -0,0 +1,72 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {GK} from 'sonar'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import * as Sonar from 'sonar'; +import {SonarBasePlugin} from '../plugin.js'; + +const plugins = new Map(); + +// expose Sonar and exact globally for dynamically loaded plugins +window.React = React; +window.ReactDOM = ReactDOM; +window.Sonar = Sonar; + +const addIfNotAdded = plugin => { + if (!plugins.has(plugin.name)) { + plugins.set(plugin.name, plugin); + } +}; + +let disabledPlugins = []; +try { + disabledPlugins = + JSON.parse(window.process.env.CONFIG || '{}').disabledPlugins || []; +} catch (e) { + console.error(e); +} + +// Load dynamic plugins +try { + JSON.parse(window.process.env.PLUGINS || '[]').forEach(addIfNotAdded); +} catch (e) { + console.error(e); +} + +// DefaultPlugins that are included in the bundle. +// List of defaultPlugins is written at build time +let bundledPlugins = []; +try { + bundledPlugins = window.electronRequire('./defaultPlugins/index.json'); +} catch (e) {} +bundledPlugins + .map(plugin => ({ + ...plugin, + out: './' + plugin.out, + })) + .forEach(addIfNotAdded); + +export default Array.from(plugins.values()) + .map(plugin => { + if ( + (plugin.gatekeeper && !GK.get(plugin.gatekeeper)) || + disabledPlugins.indexOf(plugin.name) > -1 + ) { + return null; + } else { + try { + return window.electronRequire(plugin.out); + } catch (e) { + console.error(plugin, e); + return null; + } + } + }) + .filter(Boolean) + .filter(plugin => plugin.prototype instanceof SonarBasePlugin); diff --git a/src/plugins/layout/index.js b/src/plugins/layout/index.js new file mode 100644 index 000000000..65da5dd74 --- /dev/null +++ b/src/plugins/layout/index.js @@ -0,0 +1,576 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type {ElementID, Element, ElementSearchResultSet} from 'sonar'; +import { + colors, + Glyph, + GK, + FlexRow, + FlexColumn, + Toolbar, + SonarPlugin, + ElementsInspector, + InspectorSidebar, + LoadingIndicator, + styled, + Component, + SearchBox, + SearchInput, + SearchIcon, +} from 'sonar'; + +// $FlowFixMe +import debounce from 'lodash.debounce'; + +export type InspectorState = {| + initialised: boolean, + selected: ?ElementID, + root: ?ElementID, + elements: {[key: ElementID]: Element}, + isSearchActive: boolean, + searchResults: ?ElementSearchResultSet, + outstandingSearchQuery: ?string, +|}; + +type SelectElementArgs = {| + key: ElementID, +|}; + +type ExpandElementArgs = {| + key: ElementID, + expand: boolean, +|}; + +type ExpandElementsArgs = {| + elements: Array, +|}; + +type UpdateElementsArgs = {| + elements: Array<$Shape>, +|}; + +type SetRootArgs = {| + root: ElementID, +|}; + +type GetNodesResult = {| + elements: Array, +|}; + +type SearchResultTree = {| + id: string, + isMatch: Boolean, + children: ?Array, + element: Element, +|}; + +const LoadingSpinner = LoadingIndicator.extends({ + marginRight: 4, + marginLeft: 3, + marginTop: -1, +}); + +const Center = FlexRow.extends({ + alignItems: 'center', + justifyContent: 'center', +}); + +const SearchIconContainer = styled.view({ + marginRight: 9, + marginTop: -3, + marginLeft: 4, +}); + +class LayoutSearchInput extends Component< + { + onSubmit: string => void, + }, + { + value: string, + }, +> { + static TextInput = styled.textInput({ + width: '100%', + marginLeft: 6, + }); + + state = { + value: '', + }; + + timer: TimeoutID; + + onChange = (e: SyntheticInputEvent<>) => { + clearTimeout(this.timer); + this.setState({ + value: e.target.value, + }); + this.timer = setTimeout(() => this.props.onSubmit(this.state.value), 200); + }; + + onKeyDown = (e: SyntheticKeyboardEvent<>) => { + if (e.key === 'Enter') { + this.props.onSubmit(this.state.value); + } + }; + + render() { + return ( + + ); + } +} + +export default class Layout extends SonarPlugin { + static title = 'Layout'; + static id = 'Inspector'; + static icon = 'target'; + + state = { + elements: {}, + initialised: false, + isSearchActive: false, + root: null, + selected: null, + searchResults: null, + outstandingSearchQuery: null, + }; + + reducers = { + SelectElement(state: InspectorState, {key}: SelectElementArgs) { + return { + selected: key, + }; + }, + + ExpandElement(state: InspectorState, {expand, key}: ExpandElementArgs) { + return { + elements: { + ...state.elements, + [key]: { + ...state.elements[key], + expanded: expand, + }, + }, + }; + }, + + ExpandElements(state: InspectorState, {elements}: ExpandElementsArgs) { + const expandedSet = new Set(elements); + const newState = { + elements: { + ...state.elements, + }, + }; + for (const key of Object.keys(state.elements)) { + newState.elements[key] = { + ...newState.elements[key], + expanded: expandedSet.has(key), + }; + } + return newState; + }, + + UpdateElements(state: InspectorState, {elements}: UpdateElementsArgs) { + const updatedElements = state.elements; + + for (const element of elements) { + const current = updatedElements[element.id] || {}; + // $FlowFixMe + updatedElements[element.id] = { + ...current, + ...element, + }; + } + + return {elements: updatedElements}; + }, + + SetRoot(state: InspectorState, {root}: SetRootArgs) { + return {root}; + }, + + SetSearchActive( + state: InspectorState, + {isSearchActive}: {isSearchActive: boolean}, + ) { + return {isSearchActive}; + }, + }; + + search(query: string) { + if (!query) { + return; + } + this.setState({ + outstandingSearchQuery: query, + }); + this.client + .call('getSearchResults', {query: query}) + .then(response => this.displaySearchResults(response)); + } + + executeCommand(command: string) { + return this.client.call('executeCommand', { + command: command, + context: this.state.selected, + }); + } + + /** + * When opening the inspector for the first time, expand all elements that contain only 1 child + * recursively. + */ + async performInitialExpand(element: Element): Promise { + if (!element.children.length) { + // element has no children so we're as deep as we can be + return; + } + + this.dispatchAction({expand: true, key: element.id, type: 'ExpandElement'}); + + return this.getChildren(element.id).then((elements: Array) => { + this.dispatchAction({elements, type: 'UpdateElements'}); + + if (element.children.length >= 2) { + // element has two or more children so we can stop expanding + return; + } + + return this.performInitialExpand( + this.state.elements[element.children[0]], + ); + }); + } + + displaySearchResults({ + results, + query, + }: { + results: SearchResultTree, + query: string, + }) { + const elements = this.getElementsFromSearchResultTree(results); + const idsToExpand = elements + .filter(x => x.hasChildren) + .map(x => x.element.id); + + const finishedSearching = query === this.state.outstandingSearchQuery; + + this.dispatchAction({ + elements: elements.map(x => x.element), + type: 'UpdateElements', + }); + this.dispatchAction({ + elements: idsToExpand, + type: 'ExpandElements', + }); + this.setState({ + searchResults: { + matches: new Set( + elements.filter(x => x.isMatch).map(x => x.element.id), + ), + query: query, + }, + outstandingSearchQuery: finishedSearching + ? null + : this.state.outstandingSearchQuery, + }); + } + + getElementsFromSearchResultTree(tree: SearchResultTree) { + if (!tree) { + return []; + } + var elements = [ + { + id: tree.id, + isMatch: tree.isMatch, + hasChildren: Boolean(tree.children), + element: tree.element, + }, + ]; + if (tree.children) { + for (const child of tree.children) { + elements = elements.concat(this.getElementsFromSearchResultTree(child)); + } + } + return elements; + } + + init() { + performance.mark('LayoutInspectorInitialize'); + this.client.call('getRoot').then((element: Element) => { + this.dispatchAction({elements: [element], type: 'UpdateElements'}); + this.dispatchAction({root: element.id, type: 'SetRoot'}); + this.performInitialExpand(element).then(() => { + this.app.logger.trackTimeSince('LayoutInspectorInitialize'); + this.setState({initialised: true}); + }); + }); + + this.client.subscribe( + 'invalidate', + ({nodes}: {nodes: Array<{id: ElementID}>}) => { + this.invalidate(nodes.map(node => node.id)).then( + (elements: Array) => { + this.dispatchAction({elements, type: 'UpdateElements'}); + }, + ); + }, + ); + + this.client.subscribe('select', ({path}: {path: Array}) => { + this.getNodesAndDirectChildren(path).then((elements: Array) => { + const selected = path[path.length - 1]; + + this.dispatchAction({elements, type: 'UpdateElements'}); + this.dispatchAction({key: selected, type: 'SelectElement'}); + this.dispatchAction({isSearchActive: false, type: 'SetSearchActive'}); + + for (const key of path) { + this.dispatchAction({expand: true, key, type: 'ExpandElement'}); + } + + this.client.send('setHighlighted', {id: selected}); + this.client.send('setSearchActive', {active: false}); + }); + }); + } + + invalidate(ids: Array): Promise> { + if (ids.length === 0) { + return Promise.resolve([]); + } + + return this.getNodes(ids, true).then((elements: Array) => { + const children = elements + .filter(element => { + const prev = this.state.elements[element.id]; + return prev && prev.expanded; + }) + .map(element => element.children) + .reduce((acc, val) => acc.concat(val), []); + + return Promise.all([elements, this.invalidate(children)]).then(arr => { + return arr.reduce((acc, val) => acc.concat(val), []); + }); + }); + } + + getNodesAndDirectChildren(ids: Array): Promise> { + return this.getNodes(ids, false).then((elements: Array) => { + const children = elements + .map(element => element.children) + .reduce((acc, val) => acc.concat(val), []); + + return Promise.all([elements, this.getNodes(children, false)]).then( + arr => { + return arr.reduce((acc, val) => acc.concat(val), []); + }, + ); + }); + } + + getChildren(key: ElementID): Promise> { + return this.getNodes(this.state.elements[key].children, false); + } + + getNodes( + ids: Array = [], + force: boolean, + ): Promise> { + if (!force) { + ids = ids.filter(id => { + return this.state.elements[id] === undefined; + }); + } + + if (ids.length > 0) { + performance.mark('LayoutInspectorGetNodes'); + return this.client + .call('getNodes', {ids}) + .then(({elements}: GetNodesResult) => { + this.app.logger.trackTimeSince('LayoutInspectorGetNodes'); + return Promise.resolve(elements); + }); + } else { + return Promise.resolve([]); + } + } + + isExpanded(key: ElementID): boolean { + return this.state.elements[key].expanded; + } + + expandElement = (key: ElementID): Promise> => { + const expand = !this.isExpanded(key); + return this.setElementExpanded(key, expand); + }; + + setElementExpanded = ( + key: ElementID, + expand: boolean, + ): Promise> => { + this.dispatchAction({expand, key, type: 'ExpandElement'}); + performance.mark('LayoutInspectorExpandElement'); + if (expand) { + return this.getChildren(key).then((elements: Array) => { + this.app.logger.trackTimeSince('LayoutInspectorExpandElement'); + this.dispatchAction({elements, type: 'UpdateElements'}); + return Promise.resolve(elements); + }); + } else { + return Promise.resolve([]); + } + }; + + deepExpandElement = async (key: ElementID) => { + const expand = !this.isExpanded(key); + if (!expand) { + // we never deep unexpand + return this.setElementExpanded(key, false); + } + + // queue of keys to open + const keys = [key]; + + // amount of elements we've expanded, we stop at 100 just to be safe + let count = 0; + + while (keys.length && count < 100) { + const key = keys.shift(); + + // expand current element + const children = await this.setElementExpanded(key, true); + + // and add it's children to the queue + for (const child of children) { + keys.push(child.id); + } + + count++; + } + }; + + onElementExpanded = (key: ElementID, deep: boolean) => { + if (deep) { + this.deepExpandElement(key); + } else { + this.expandElement(key); + } + this.app.logger.track('usage', 'layout:element-expanded', { + id: key, + deep: deep, + }); + }; + + onFindClick = () => { + const isSearchActive = !this.state.isSearchActive; + this.dispatchAction({isSearchActive, type: 'SetSearchActive'}); + this.client.send('setSearchActive', {active: isSearchActive}); + }; + + onElementSelected = debounce((key: ElementID) => { + this.dispatchAction({key, type: 'SelectElement'}); + this.client.send('setHighlighted', {id: key}); + this.getNodes([key], true).then((elements: Array) => { + this.dispatchAction({elements, type: 'UpdateElements'}); + }); + }); + + onElementHovered = debounce((key: ?ElementID) => { + this.client.send('setHighlighted', {id: key}); + }); + + onDataValueChanged = (path: Array, value: any) => { + this.client.send('setData', {id: this.state.selected, path, value}); + this.app.logger.track('usage', 'layout:value-changed', { + id: this.state.selected, + value: value, + path: path, + }); + }; + + renderSidebar = () => { + return this.state.selected != null ? ( + + ) : null; + }; + + render() { + const { + initialised, + selected, + root, + elements, + isSearchActive, + outstandingSearchQuery, + } = this.state; + + return ( + + + + + + {GK.get('sonar_layout_search') && ( + + + + {outstandingSearchQuery && } + + )} + + + {initialised ? ( + + ) : ( +
+ +
+ )} +
+
+ ); + } +} diff --git a/src/plugins/layout/package.json b/src/plugins/layout/package.json new file mode 100644 index 000000000..8f1b4378c --- /dev/null +++ b/src/plugins/layout/package.json @@ -0,0 +1,9 @@ +{ + "name": "sonar-plugin-layout", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "lodash.debounce": "^4.0.8" + } +} diff --git a/src/plugins/layout/yarn.lock b/src/plugins/layout/yarn.lock new file mode 100644 index 000000000..d1a61d5bd --- /dev/null +++ b/src/plugins/layout/yarn.lock @@ -0,0 +1,7 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" diff --git a/src/plugins/network/RequestDetails.js b/src/plugins/network/RequestDetails.js new file mode 100644 index 000000000..736b71d92 --- /dev/null +++ b/src/plugins/network/RequestDetails.js @@ -0,0 +1,539 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +// $FlowFixMe +import pako from 'pako'; +import type {Request, Response, Header} from './index.js'; + +import { + Component, + FlexColumn, + ManagedTable, + ManagedDataInspector, + Text, + Panel, + styled, + colors, +} from 'sonar'; +import {getHeaderValue} from './index.js'; + +import querystring from 'querystring'; + +const WrappingText = Text.extends({ + wordWrap: 'break-word', + width: '100%', + lineHeight: '125%', + padding: '3px 0', +}); + +const KeyValueColumnSizes = { + key: '30%', + value: 'flex', +}; + +const KeyValueColumns = { + key: { + value: 'Key', + resizable: false, + }, + value: { + value: 'Value', + resizable: false, + }, +}; + +type RequestDetailsProps = { + request: Request, + response: ?Response, +}; + +function decodeBody(container: Request | Response): string { + if (!container.data) { + return ''; + } + const b64Decoded = atob(container.data); + const encodingHeader = container.headers.find( + header => header.key === 'Content-Encoding', + ); + + return encodingHeader && encodingHeader.value === 'gzip' + ? decompress(b64Decoded) + : b64Decoded; +} + +function decompress(body: string): string { + const charArray = body.split('').map(x => x.charCodeAt(0)); + + const byteArray = new Uint8Array(charArray); + + let data; + try { + if (body) { + data = pako.inflate(byteArray); + } else { + return body; + } + } catch (e) { + // Sometimes Content-Encoding is 'gzip' but the body is already decompressed. + // Assume this is the case when decompression fails. + return body; + } + + return String.fromCharCode.apply(null, new Uint8Array(data)); +} + +export default class RequestDetails extends Component { + static Container = FlexColumn.extends({ + height: '100%', + overflow: 'auto', + }); + + urlColumns = (url: URL) => { + return [ + { + columns: { + key: {value: Full URL}, + value: { + value: {url.href}, + }, + }, + copyText: url.href, + key: 'url', + }, + { + columns: { + key: {value: Host}, + value: { + value: {url.host}, + }, + }, + copyText: url.host, + key: 'host', + }, + { + columns: { + key: {value: Path}, + value: { + value: {url.pathname}, + }, + }, + copyText: url.pathname, + key: 'path', + }, + { + columns: { + key: {value: Query String}, + value: { + value: {url.search}, + }, + }, + copyText: url.search, + key: 'query', + }, + ]; + }; + + render() { + const {request, response} = this.props; + const url = new URL(request.url); + + return ( + + + + + + {url.search ? ( + + + + ) : null} + + {request.headers.length > 0 ? ( + + + + ) : null} + + {request.data != null ? ( + + + + ) : null} + + {response + ? [ + response.headers.length > 0 ? ( + + + + ) : null, + + + , + ] + : null} + + ); + } +} + +class QueryInspector extends Component<{queryParams: URLSearchParams}> { + render() { + const {queryParams} = this.props; + + const rows = []; + for (const kv of queryParams.entries()) { + rows.push({ + columns: { + key: { + value: {kv[0]}, + }, + value: { + value: {kv[1]}, + }, + }, + copyText: kv[1], + key: kv[0], + }); + } + + return rows.length > 0 ? ( + + ) : null; + } +} + +type HeaderInspectorProps = { + headers: Array
, +}; + +type HeaderInspectorState = { + computedHeaders: Object, +}; + +class HeaderInspector extends Component< + HeaderInspectorProps, + HeaderInspectorState, +> { + render() { + const computedHeaders = this.props.headers.reduce((sum, header) => { + return {...sum, [header.key]: header.value}; + }, {}); + + const rows = []; + for (const key in computedHeaders) { + rows.push({ + columns: { + key: { + value: {key}, + }, + value: { + value: {computedHeaders[key]}, + }, + }, + copyText: computedHeaders[key], + key, + }); + } + + return rows.length > 0 ? ( + + ) : null; + } +} + +const BodyContainer = styled.view({ + paddingTop: 10, + paddingBottom: 20, +}); + +type BodyFormatter = { + formatRequest?: (request: Request) => any, + formatResponse?: (request: Request, response: Response) => any, +}; + +class RequestBodyInspector extends Component<{ + request: Request, +}> { + render() { + const {request} = this.props; + let component; + try { + for (const formatter of BodyFormatters) { + if (formatter.formatRequest) { + component = formatter.formatRequest(request); + if (component) { + break; + } + } + } + } catch (e) {} + + if (component == null && request.data != null) { + component = {decodeBody(request)}; + } + + if (component == null) { + return null; + } + + return {component}; + } +} + +class ResponseBodyInspector extends Component<{ + response: Response, + request: Request, +}> { + render() { + const {request, response} = this.props; + + let component; + try { + for (const formatter of BodyFormatters) { + if (formatter.formatResponse) { + component = formatter.formatResponse(request, response); + if (component) { + break; + } + } + } + } catch (e) {} + + component = component || {decodeBody(response)}; + + return {component}; + } +} + +const MediaContainer = FlexColumn.extends({ + alignItems: 'center', + justifyContent: 'center', + width: '100%', +}); + +type ImageWithSizeProps = { + src: string, +}; + +type ImageWithSizeState = { + width: number, + height: number, +}; + +class ImageWithSize extends Component { + static Image = styled.image({ + objectFit: 'scale-down', + maxWidth: 500, + maxHeight: 500, + marginBottom: 10, + }); + + static Text = Text.extends({ + color: colors.dark70, + fontSize: 14, + }); + + constructor(props, context) { + super(props, context); + this.state = { + width: 0, + height: 0, + }; + } + + componentDidMount() { + const image = new Image(); + image.src = this.props.src; + image.onload = () => { + image.width; + image.height; + this.setState({ + width: image.width, + height: image.height, + }); + }; + } + + render() { + return ( + + + + {this.state.width} x {this.state.height} + + + ); + } +} + +class ImageFormatter { + formatResponse = (request: Request, response: Response) => { + if (getHeaderValue(response.headers, 'content-type').startsWith('image')) { + return ; + } + }; +} + +class VideoFormatter { + static Video = styled.customHTMLTag('video', { + maxWidth: 500, + maxHeight: 500, + }); + + formatResponse = (request: Request, response: Response) => { + const contentType = getHeaderValue(response.headers, 'content-type'); + if (contentType.startsWith('video')) { + return ( + + + + + + ); + } + }; +} + +class JSONFormatter { + formatRequest = (request: Request) => { + return this.format( + decodeBody(request), + getHeaderValue(request.headers, 'content-type'), + ); + }; + + formatResponse = (request: Request, response: Response) => { + return this.format( + decodeBody(response), + getHeaderValue(response.headers, 'content-type'), + ); + }; + + format = (body: string, contentType: string) => { + if ( + contentType.startsWith('application/json') || + contentType.startsWith('text/javascript') || + contentType.startsWith('application/x-fb-flatbuffer') + ) { + try { + const data = JSON.parse(body); + return ( + + ); + } catch (SyntaxError) { + // Multiple top level JSON roots, map them one by one + const roots = body.split('\n'); + return ( + JSON.parse(json))} + /> + ); + } + } + }; +} + +class LogEventFormatter { + formatRequest = (request: Request) => { + if (request.url.indexOf('logging_client_event') > 0) { + const data = querystring.parse(decodeBody(request)); + if (data.message) { + data.message = JSON.parse(data.message); + } + return ; + } + }; +} + +class GraphQLBatchFormatter { + formatRequest = (request: Request) => { + if (request.url.indexOf('graphqlbatch') > 0) { + const data = querystring.parse(decodeBody(request)); + if (data.queries) { + data.queries = JSON.parse(data.queries); + } + return ; + } + }; +} + +class GraphQLFormatter { + formatRequest = (request: Request) => { + if (request.url.indexOf('graphql') > 0) { + const data = querystring.parse(decodeBody(request)); + if (data.variables) { + data.variables = JSON.parse(data.variables); + } + if (data.query_params) { + data.query_params = JSON.parse(data.query_params); + } + return ; + } + }; +} + +class FormUrlencodedFormatter { + formatRequest = (request: Request) => { + const contentType = getHeaderValue(request.headers, 'content-type'); + if (contentType.startsWith('application/x-www-form-urlencoded')) { + return ( + + ); + } + }; +} + +const BodyFormatters: Array = [ + new ImageFormatter(), + new VideoFormatter(), + new LogEventFormatter(), + new GraphQLBatchFormatter(), + new GraphQLFormatter(), + new JSONFormatter(), + new FormUrlencodedFormatter(), +]; diff --git a/src/plugins/network/index.js b/src/plugins/network/index.js new file mode 100644 index 000000000..316d41668 --- /dev/null +++ b/src/plugins/network/index.js @@ -0,0 +1,411 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type {TableHighlightedRows, TableRows} from 'sonar'; + +import { + ContextMenu, + FlexColumn, + Button, + Text, + Glyph, + colors, + PureComponent, +} from 'sonar'; + +import {SonarPlugin, SearchableTable} from 'sonar'; +import RequestDetails from './RequestDetails.js'; + +import {URL} from 'url'; +// $FlowFixMe +import sortBy from 'lodash.sortby'; + +type RequestId = string; + +type State = {| + requests: {[id: RequestId]: Request}, + responses: {[id: RequestId]: Response}, + selectedIds: Array, +|}; + +export type Request = {| + id: RequestId, + timestamp: number, + method: string, + url: string, + headers: Array
, + data: ?string, +|}; + +export type Response = {| + id: RequestId, + timestamp: number, + status: number, + reason: string, + headers: Array
, + data: ?string, +|}; + +export type Header = {| + key: string, + value: string, +|}; + +const COLUMN_SIZE = { + domain: 'flex', + method: 100, + status: 70, + size: 100, + duration: 100, +}; + +const COLUMNS = { + domain: { + value: 'Domain', + }, + method: { + value: 'Method', + }, + status: { + value: 'Status', + }, + size: { + value: 'Size', + }, + duration: { + value: 'Duration', + }, +}; + +export function getHeaderValue(headers: Array
, key: string) { + for (const header of headers) { + if (header.key.toLowerCase() === key.toLowerCase()) { + return header.value; + } + } + return ''; +} + +export function formatBytes(count: number): string { + if (count > 1024 * 1024) { + return (count / (1024.0 * 1024)).toFixed(1) + ' MB'; + } + if (count > 1024) { + return (count / 1024.0).toFixed(1) + ' kB'; + } + return count + ' B'; +} + +const TextEllipsis = Text.extends({ + overflowX: 'hidden', + textOverflow: 'ellipsis', + maxWidth: '100%', + lineHeight: '18px', + paddingTop: 4, +}); + +export default class extends SonarPlugin { + static title = 'Network'; + static id = 'Network'; + static icon = 'internet'; + static keyboardActions = ['clear']; + + onKeyboardAction = (action: string) => { + if (action === 'clear') { + this.clearLogs(); + } + }; + + state = { + requests: {}, + responses: {}, + selectedIds: [], + }; + + init() { + this.client.subscribe('newRequest', (request: Request) => { + this.dispatchAction({request, type: 'NewRequest'}); + }); + this.client.subscribe('newResponse', (response: Response) => { + this.dispatchAction({response, type: 'NewResponse'}); + }); + } + + reducers = { + NewRequest(state: State, {request}: {request: Request}) { + return { + requests: {...state.requests, [request.id]: request}, + responses: state.responses, + }; + }, + + NewResponse(state: State, {response}: {response: Response}) { + return { + requests: state.requests, + responses: {...state.responses, [response.id]: response}, + }; + }, + + Clear(state: State) { + return { + requests: {}, + responses: {}, + }; + }, + }; + + onRowHighlighted = (selectedIds: Array) => + this.setState({selectedIds}); + + clearLogs = () => { + this.setState({selectedIds: []}); + this.dispatchAction({type: 'Clear'}); + }; + + renderSidebar = () => { + const {selectedIds, requests, responses} = this.state; + const selectedId = selectedIds.length === 1 ? selectedIds[0] : null; + + return selectedId != null ? ( + + ) : null; + }; + + render() { + return ( + + + + ); + } +} + +type NetworkTableProps = {| + requests: {[id: RequestId]: Request}, + responses: {[id: RequestId]: Response}, + clear: () => void, + onRowHighlighted: (keys: TableHighlightedRows) => void, +|}; + +type NetworkTableState = {| + sortedRows: TableRows, +|}; + +class NetworkTable extends PureComponent { + static ContextMenu = ContextMenu.extends({ + flex: 1, + }); + + state = { + sortedRows: [], + }; + + componentWillReceiveProps(nextProps: NetworkTableProps) { + if (Object.keys(nextProps.requests).length === 0) { + // cleared + this.setState({sortedRows: []}); + } else if (this.props.requests !== nextProps.requests) { + // new request + for (const requestId in nextProps.requests) { + if (this.props.requests[requestId] == null) { + this.buildRow(nextProps.requests[requestId], null); + break; + } + } + } else if (this.props.responses !== nextProps.responses) { + // new response + for (const responseId in nextProps.responses) { + if (this.props.responses[responseId] == null) { + this.buildRow( + nextProps.requests[responseId], + nextProps.responses[responseId], + ); + break; + } + } + } + } + + buildRow(request: Request, response: ?Response) { + if (request == null) { + return; + } + const url = new URL(request.url); + const domain = url.host + url.pathname; + const friendlyName = getHeaderValue(request.headers, 'X-FB-Friendly-Name'); + + const newRow = { + columns: { + domain: { + value: ( + {friendlyName ? friendlyName : domain} + ), + isFilterable: true, + }, + method: { + value: {request.method}, + isFilterable: true, + }, + status: { + value: ( + + {response ? response.status : undefined} + + ), + isFilterable: true, + }, + size: { + value: , + }, + duration: { + value: , + }, + }, + key: request.id, + filterValue: `${request.method} ${request.url}`, + sortKey: request.timestamp, + copyText: request.url, + highlightOnHover: true, + }; + + let rows; + if (response == null) { + rows = [...this.state.sortedRows, newRow]; + } else { + const index = this.state.sortedRows.findIndex(r => r.key === request.id); + if (index > -1) { + rows = [...this.state.sortedRows]; + rows[index] = newRow; + } + } + + this.setState({ + sortedRows: sortBy(rows, x => x.sortKey), + }); + } + + contextMenuItems = [ + { + type: 'separator', + }, + { + label: 'Clear all', + click: this.props.clear, + }, + ]; + + render() { + return ( + + Clear Table} + /> + + ); + } +} + +const Icon = Glyph.extends({ + marginTop: -3, + marginRight: 3, +}); + +class StatusColumn extends PureComponent<{ + children?: number, +}> { + render() { + const {children} = this.props; + let glyph; + + if (children != null && children >= 400 && children < 600) { + glyph = ; + } + + return ( + + {glyph} + {children} + + ); + } +} + +class DurationColumn extends PureComponent<{ + request: Request, + response: ?Response, +}> { + static Text = Text.extends({ + flex: 1, + textAlign: 'right', + paddingRight: 10, + }); + + render() { + const {request, response} = this.props; + const duration = response + ? response.timestamp - request.timestamp + : undefined; + return ( + + {duration != null ? duration.toLocaleString() + 'ms' : ''} + + ); + } +} + +class SizeColumn extends PureComponent<{ + response: ?Response, +}> { + static Text = Text.extends({ + flex: 1, + textAlign: 'right', + paddingRight: 10, + }); + + render() { + const {response} = this.props; + if (response) { + const text = formatBytes(this.getResponseLength(response)); + return {text}; + } else { + return null; + } + } + + getResponseLength(response) { + let length = 0; + const lengthString = response.headers + ? getHeaderValue(response.headers, 'content-length') + : undefined; + if (lengthString != null && lengthString != '') { + length = parseInt(lengthString, 10); + } else if (response.data) { + length = atob(response.data).length; + } + return length; + } +} diff --git a/src/plugins/network/package.json b/src/plugins/network/package.json new file mode 100644 index 000000000..eec4a9850 --- /dev/null +++ b/src/plugins/network/package.json @@ -0,0 +1,10 @@ +{ + "name": "sonar-plugin-network", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "pako": "^1.0.6" + } +} diff --git a/src/plugins/network/yarn.lock b/src/plugins/network/yarn.lock new file mode 100644 index 000000000..3ff62dddc --- /dev/null +++ b/src/plugins/network/yarn.lock @@ -0,0 +1,11 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + +pako@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" diff --git a/src/reducers.js b/src/reducers.js new file mode 100644 index 000000000..9fa1df651 --- /dev/null +++ b/src/reducers.js @@ -0,0 +1,184 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type { + App, + State, + StatePluginInfo, + StatePlugins, + StateClientPlugins, +} from './App.js'; +import type {SonarBasePlugin} from 'sonar'; +import {devicePlugins} from './device-plugins/index.js'; +import {SonarPlugin, SonarDevicePlugin} from 'sonar'; +import {PluginStateContainer} from './plugin.js'; +import BaseDevice from './devices/BaseDevice.js'; +import plugins from './plugins/index.js'; +import {Client} from './server.js'; + +const invariant = require('invariant'); + +type ActivatePluginAction = {| + appKey: string, + pluginKey: string, +|}; + +type TeardownClientAction = {| + appKey: string, +|}; + +export function ActivatePlugin( + app: App, + state: State, + {appKey, pluginKey}: ActivatePluginAction, +) { + const {activePluginKey, activeAppKey} = state; + + // get currently active plugin + const activeClientPlugins: ?StateClientPlugins = + activeAppKey != null ? state.plugins[activeAppKey] : null; + const activePluginInfo: ?StatePluginInfo = + activePluginKey != null && activeClientPlugins + ? activeClientPlugins[activePluginKey] + : null; + + // check if this plugin is already active + if ( + activePluginKey === pluginKey && + activeAppKey === appKey && + activePluginInfo && + activePluginInfo.plugin + ) { + // this is a noop + return state; + } + + // produce new plugins object + const newPluginsState: StatePlugins = { + ...state.plugins, + }; + + // check if the currently active plugin needs to be torn down after being deactivated + if ( + activeAppKey != null && + activePluginKey != null && + activePluginInfo && + activeClientPlugins + ) { + const activePlugin: ?SonarBasePlugin<> = activePluginInfo.plugin; + if (activePlugin && !activePlugin.constructor.persist) { + // teardown the currently active plugin + activePlugin._teardown(); + + // and remove it's plugin instance so next time it's made active it'll be reloaded + newPluginsState[activeAppKey] = { + ...activeClientPlugins, + [activePluginKey]: { + plugin: null, + state: activePluginInfo.state, + }, + }; + } + } + + // get the plugin state associated with the new client + const newClientPluginsState: StateClientPlugins = { + ...(newPluginsState[appKey] || {}), + }; + newPluginsState[appKey] = newClientPluginsState; + + // find the Plugin constructor with this key + let Plugin: Class>; + for (const FindPlugin of plugins) { + if (FindPlugin.id === pluginKey) { + Plugin = FindPlugin; + } + } + for (const FindPlugin of devicePlugins) { + if (FindPlugin.id === pluginKey) { + Plugin = FindPlugin; + } + } + invariant(Plugin, 'expected plugin'); + + // get target, this could be an app connection or a device + const clientInfo = state.server.connections.get(appKey); + let target: void | Client | BaseDevice; + if (clientInfo) { + target = clientInfo.client; + invariant( + // $FlowFixMe prototype not known + Plugin.prototype instanceof SonarPlugin, + 'expected plugin to be an app Plugin', + ); + } else { + target = app.props.devices.find( + (device: BaseDevice) => device.serial === appKey, + ); + invariant( + // $FlowFixMe prototype not known + Plugin.prototype instanceof SonarDevicePlugin, + 'expected plugin to be DevicePlugin', + ); + } + invariant(target, 'expected target'); + + // initialise the client if it hasn't alreadu been + const thisPluginState: ?StatePluginInfo = newClientPluginsState[pluginKey]; + if (!thisPluginState || !thisPluginState.plugin) { + const plugin = new Plugin(); + + // setup plugin, this is to avoid consumers having to pass args to super + plugin._setup(target, app); + + // if we already have state for this plugin then rehydrate it + if (thisPluginState && thisPluginState.state) { + plugin.state = thisPluginState.state; + } + + // init plugin - setup broadcasts, initial messages etc + plugin._init(); + + newClientPluginsState[pluginKey] = new PluginStateContainer( + plugin, + plugin.state, + ); + } + + return { + activeAppKey: appKey, + activePluginKey: pluginKey, + plugins: newPluginsState, + }; +} + +export function TeardownClient( + app: App, + state: State, + {appKey}: TeardownClientAction, +) { + const allPlugins: StatePlugins = {...state.plugins}; + + // teardown all plugins + const clientPlugins: StateClientPlugins = allPlugins[appKey]; + for (const pluginKey in clientPlugins) { + const {plugin} = clientPlugins[pluginKey]; + if (plugin) { + plugin._teardown(); + } + } + + // remove this client + delete allPlugins[appKey]; + + return { + activeAppKey: state.activeAppKey === appKey ? null : state.activeAppKey, + activePluginKey: + state.activeAppKey === appKey ? null : state.activePluginKey, + plugins: allPlugins, + }; +} diff --git a/src/reducers/application.js b/src/reducers/application.js new file mode 100644 index 000000000..357dccff4 --- /dev/null +++ b/src/reducers/application.js @@ -0,0 +1,88 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {remote} from 'electron'; + +export type State = { + leftSidebarVisible: boolean, + rightSidebarVisible: boolean, + rightSidebarAvailable: boolean, + bugDialogVisible: boolean, + windowIsFocused: boolean, + pluginManagerVisible: boolean, +}; + +type ActionType = + | 'leftSidebarVisible' + | 'rightSidebarVisible' + | 'rightSidebarAvailable' + | 'bugDialogVisible' + | 'windowIsFocused' + | 'pluginManagerVisible'; + +export type Action = { + type: ActionType, + payload?: boolean, +}; + +const INITAL_STATE: State = { + leftSidebarVisible: true, + rightSidebarVisible: true, + rightSidebarAvailable: false, + bugDialogVisible: false, + windowIsFocused: remote.getCurrentWindow().isFocused(), + pluginManagerVisible: false, +}; + +export default function reducer( + state: State = INITAL_STATE, + action: Action, +): State { + const newValue = + typeof action.payload === 'undefined' + ? !state[action.type] + : action.payload; + if (state[action.type] === newValue) { + // value hasn't changed, do nothing + return state; + } else { + return { + ...state, + [action.type]: newValue, + }; + } +} + +export const toggleAction = (type: ActionType, payload?: boolean): Action => ({ + type, + payload, +}); + +export const toggleBugDialogVisible = (payload?: boolean): Action => ({ + type: 'bugDialogVisible', + payload, +}); + +export const toggleLeftSidebarVisible = (payload?: boolean): Action => ({ + type: 'leftSidebarVisible', + payload, +}); + +export const toggleRightSidebarVisible = (payload?: boolean): Action => ({ + type: 'rightSidebarVisible', + payload, +}); + +export const toggleRightSidebarAvailable = (payload?: boolean): Action => ({ + type: 'rightSidebarAvailable', + payload, +}); + +export const togglePluginManagerVisible = (payload?: boolean): Action => ({ + type: 'pluginManagerVisible', + payload, +}); diff --git a/src/reducers/devices.js b/src/reducers/devices.js new file mode 100644 index 000000000..17e2a8d05 --- /dev/null +++ b/src/reducers/devices.js @@ -0,0 +1,39 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type BaseDevice from '../devices/BaseDevice'; +export type State = Array; + +export type Action = + | { + type: 'UNREGISTER_DEVICES', + payload: Set, + } + | { + type: 'REGISTER_DEVICE', + payload: BaseDevice, + }; + +const INITAL_STATE: State = []; + +export default function reducer( + state: State = INITAL_STATE, + action: Action, +): State { + switch (action.type) { + case 'REGISTER_DEVICE': { + const {payload} = action; + return state.concat(payload); + } + case 'UNREGISTER_DEVICES': { + const {payload} = action; + return state.filter((device: BaseDevice) => !payload.has(device.serial)); + } + default: + return state; + } +} diff --git a/src/reducers/index.js b/src/reducers/index.js new file mode 100644 index 000000000..de34adb89 --- /dev/null +++ b/src/reducers/index.js @@ -0,0 +1,29 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import {combineReducers} from 'redux'; +import application from './application.js'; +import devices from './devices.js'; +import type { + State as ApplicationState, + Action as ApplicationAction, +} from './application.js'; +import type { + State as DevicesState, + Action as DevicesAction, +} from './devices.js'; +import type {Store as ReduxStore} from 'redux'; + +export type Store = ReduxStore< + { + application: ApplicationState, + devices: DevicesState, + }, + ApplicationAction | DevicesAction, +>; + +export default combineReducers({application, devices}); diff --git a/src/server.js b/src/server.js new file mode 100644 index 000000000..664905b23 --- /dev/null +++ b/src/server.js @@ -0,0 +1,520 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type BaseDevice from './devices/BaseDevice.js'; +import type {App} from './App.js'; +import type {SonarPlugin} from './plugin.js'; +import plugins from './plugins/index.js'; +import CertificateProvider from './utils/CertificateProvider'; +import type {SecureServerConfig} from './utils/CertificateProvider'; + +import {RSocketServer, ReactiveSocket, PartialResponder} from 'rsocket-core'; +import RSocketTCPServer from 'rsocket-tcp-server'; +const tls = require('tls'); +const net = require('net'); + +const EventEmitter = (require('events'): any); + +const invariant = require('invariant'); + +const SECURE_PORT = 8088; +const INSECURE_PORT = 8089; + +type RSocket = {| + fireAndForget(payload: {data: string}): void, + connectionStatus(): any, + close(): void, +|}; + +type ClientInfo = {| + connection: ?ReactiveSocket, + client: Client, +|}; + +type Plugins = Array; + +type ClientQuery = {| + app: string, + os: string, + device: string, + device_id: ?string, +|}; + +type RequestMetadata = {method: string, id: number, params: ?Object}; + +export class Client extends EventEmitter { + constructor(app: App, id: string, query: ClientQuery, conn: ReactiveSocket) { + super(); + + this.connected = true; + this.plugins = []; + this.connection = conn; + this.id = id; + this.query = query; + this.messageIdCounter = 0; + this.app = app; + + this.broadcastCallbacks = new Map(); + this.requestCallbacks = new Map(); + + const client = this; + this.responder = { + fireAndForget: (payload: {data: string}) => { + client.onMessage(payload.data); + }, + }; + + conn.connectionStatus().subscribe({ + onNext(payload) { + if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') { + client.connected = false; + } + }, + onSubscribe(subscription) { + subscription.request(Number.MAX_SAFE_INTEGER); + }, + }); + } + + on: ((event: 'plugins-change', callback: () => void) => void) & + ((event: 'close', callback: () => void) => void); + + app: App; + connected: boolean; + id: string; + query: ClientQuery; + messageIdCounter: number; + plugins: Plugins; + connection: ReactiveSocket; + responder: PartialResponder; + + broadcastCallbacks: Map>>; + + requestCallbacks: Map< + number, + {| + resolve: (data: any) => void, + reject: (err: Error) => void, + metadata: RequestMetadata, + |}, + >; + + getDevice(): ?BaseDevice { + const {device_id} = this.query; + + if (device_id == null) { + return null; + } else { + return this.app.getDevice(device_id); + } + } + + supportsPlugin(Plugin: Class>): boolean { + return this.plugins.includes(Plugin.id); + } + + getFirstSupportedPlugin(): ?string { + for (const Plugin of plugins) { + if (this.supportsPlugin(Plugin)) { + return Plugin.id; + } + } + } + + async init() { + await this.getPlugins(); + } + + // get the supported plugins + async getPlugins(): Promise { + const plugins = await this.rawCall('getPlugins').then(data => data.plugins); + this.plugins = plugins; + return plugins; + } + + // get the plugins, and update the UI + async refreshPlugins() { + await this.getPlugins(); + this.emit('plugins-change'); + } + + onMessage(msg: string) { + if (typeof msg !== 'string') { + return; + } + + let rawData; + try { + rawData = JSON.parse(msg); + } catch (err) { + this.app.logger.error(`Invalid JSON: ${msg}`, 'clientMessage'); + return; + } + + const data: {| + id?: number, + method?: string, + params?: Object, + success?: Object, + error?: Object, + |} = rawData; + + this.app.logger.info(data, 'message:receive'); + + const {id, method} = data; + + if (id == null) { + const {error} = data; + if (error != null) { + this.app.logger.error(error.stacktrace || error.message, 'deviceError'); + this.app.errorReporter.report({ + message: error.message, + stack: error.stacktrace, + }); + } else if (method === 'refreshPlugins') { + this.refreshPlugins(); + } else if (method === 'execute') { + const params = data.params; + invariant(params, 'expected params'); + + const apiCallbacks = this.broadcastCallbacks.get(params.api); + if (!apiCallbacks) { + return; + } + + const methodCallbacks: ?Set = apiCallbacks.get(params.method); + if (methodCallbacks) { + for (const callback of methodCallbacks) { + callback(params.params); + } + } + } + return; + } + + const callbacks = this.requestCallbacks.get(id); + if (!callbacks) { + return; + } + this.requestCallbacks.delete(id); + this.finishTimingRequestResponse(callbacks.metadata); + + if (data.success) { + callbacks.resolve(data.success); + } else if (data.error) { + callbacks.reject(data.error); + } else { + // ??? + } + } + + toJSON() { + return null; + } + + subscribe( + api: ?string = null, + method: string, + callback: (params: Object) => void, + ) { + let apiCallbacks = this.broadcastCallbacks.get(api); + if (!apiCallbacks) { + apiCallbacks = new Map(); + this.broadcastCallbacks.set(api, apiCallbacks); + } + + let methodCallbacks = apiCallbacks.get(method); + if (!methodCallbacks) { + methodCallbacks = new Set(); + apiCallbacks.set(method, methodCallbacks); + } + methodCallbacks.add(callback); + } + + unsubscribe(api: ?string = null, method: string, callback: Function) { + const apiCallbacks = this.broadcastCallbacks.get(api); + if (!apiCallbacks) { + return; + } + + const methodCallbacks = apiCallbacks.get(method); + if (!methodCallbacks) { + return; + } + methodCallbacks.delete(callback); + } + + rawCall(method: string, params?: Object): Promise { + return new Promise((resolve, reject) => { + const id = this.messageIdCounter++; + const metadata: RequestMetadata = { + method, + id, + params, + }; + this.requestCallbacks.set(id, {reject, resolve, metadata}); + + const data = { + id, + method, + params, + }; + + this.app.logger.info(data, 'message:call'); + this.startTimingRequestResponse({method, id, params}); + this.connection.fireAndForget({data: JSON.stringify(data)}); + }); + } + + startTimingRequestResponse(data: RequestMetadata) { + performance.mark(this.getPerformanceMark(data)); + } + + finishTimingRequestResponse(data: RequestMetadata) { + const mark = this.getPerformanceMark(data); + const logEventName = this.getLogEventName(data); + this.app.logger.trackTimeSince(mark, logEventName); + } + + getPerformanceMark(data: RequestMetadata): string { + const {method, id} = data; + return `request_response_${method}_${id}`; + } + + getLogEventName(data: RequestMetadata): string { + const {method, params} = data; + return params && params.api && params.method + ? `request_response_${method}_${params.api}_${params.method}` + : `request_response_${method}`; + } + + rawSend(method: string, params?: Object): void { + const data = { + method, + params, + }; + this.app.logger.info(data, 'message:send'); + this.connection.fireAndForget({data: JSON.stringify(data)}); + } + + call(api: string, method: string, params?: Object): Promise { + return this.rawCall('execute', {api, method, params}); + } + + send(api: string, method: string, params?: Object): void { + return this.rawSend('execute', {api, method, params}); + } +} + +export class Server extends EventEmitter { + connections: Map; + secureServer: RSocketServer; + insecureServer: RSocketServer; + certificateProvider: CertificateProvider; + app: App; + + constructor(app: App) { + super(); + this.app = app; + this.connections = new Map(); + this.certificateProvider = new CertificateProvider(this, app.logger); + this.init(); + } + + on: ((event: 'new-client', callback: (client: Client) => void) => void) & + ((event: 'error', callback: (err: Error) => void) => void) & + ((event: 'clients-change', callback: () => void) => void); + + init() { + if (process.env.NODE_ENV === 'test') { + this.app.logger.warn( + "rsocket server has not been started as we're in test mode", + 'server', + ); + return; + } + + this.certificateProvider + .loadSecureServerConfig() + .then( + options => (this.secureServer = this.startServer(SECURE_PORT, options)), + ); + this.insecureServer = this.startServer(INSECURE_PORT); + } + + startServer(port: number, sslConfig?: SecureServerConfig) { + const server = this; + const serverFactory = onConnect => { + const transportServer = sslConfig + ? tls.createServer(sslConfig, socket => { + onConnect(socket); + }) + : net.createServer(onConnect); + transportServer + .on('error', err => { + server.emit('error', err); + server.app.logger.error( + `Error opening server on port ${port}`, + 'server', + ); + }) + .on('listening', () => { + server.app.logger.warn( + `${ + sslConfig ? 'Secure' : 'Certificate' + } server started on port ${port}`, + 'server', + ); + }); + return transportServer; + }; + const rsServer = new RSocketServer({ + getRequestHandler: sslConfig + ? this._trustedRequestHandler + : this._untrustedRequestHandler, + transport: new RSocketTCPServer({ + port: port, + serverFactory: serverFactory, + }), + }); + + rsServer.start(); + return rsServer; + } + + _trustedRequestHandler = (conn: RSocket, connectRequest: {data: string}) => { + const server = this; + + const client = this.addConnection(conn, connectRequest.data); + + conn.connectionStatus().subscribe({ + onNext(payload) { + if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') { + server.app.logger.warn( + `Device disconnected ${client.id}`, + 'connection', + ); + server.removeConnection(client.id); + } + }, + onSubscribe(subscription) { + subscription.request(Number.MAX_SAFE_INTEGER); + }, + }); + + return client.responder; + }; + + _untrustedRequestHandler = ( + conn: RSocket, + connectRequest: {data: string}, + ) => { + const connectionParameters = JSON.parse(connectRequest.data); + + return { + fireAndForget: (payload: {data: string}) => { + if (typeof payload.data !== 'string') { + return; + } + + let rawData; + try { + rawData = JSON.parse(payload.data); + } catch (err) { + this.app.logger.error( + `Invalid JSON: ${payload.data}`, + 'clientMessage', + ); + return; + } + + const json: {| + method: 'signCertificate', + csr: string, + destination: string, + |} = rawData; + if (json.method === 'signCertificate') { + this.app.logger.warn('CSR received from device', 'server'); + const {csr, destination} = json; + this.certificateProvider.processCertificateSigningRequest( + csr, + connectionParameters.os, + destination + ); + } + }, + }; + }; + + close() { + this.secureServer.stop(); + this.insecureServer.stop(); + } + + toJSON() { + return null; + } + + addConnection(conn: ReactiveSocket, queryString: string): Client { + const query = JSON.parse(queryString); + invariant(query, 'expected query'); + + this.app.logger.warn(`Device connected: ${queryString}`, 'connection'); + + const id = `${query.app}-${query.os}-${query.device}`; + const client = new Client(this.app, id, query, conn); + + const info = { + client, + connection: conn, + }; + + client.init().then(() => { + this.app.logger.info( + `Device client initialised: ${id}. Supported plugins: ${client.plugins.join( + ', ', + )}`, + 'connection', + ); + + /* If a device gets disconnected without being cleaned up properly, + * sonar won't be aware until it attempts to reconnect. + * When it does we need to terminate the zombie connection. + */ + if (this.connections.has(id)) { + const connectionInfo = this.connections.get(id); + connectionInfo && + connectionInfo.connection && + connectionInfo.connection.close(); + this.removeConnection(id); + } + + this.connections.set(id, info); + this.emit('new-client', client); + this.emit('clients-change'); + client.emit('plugins-change'); + }); + + return client; + } + + attachFakeClient(client: Client) { + this.connections.set(client.id, { + client, + connection: null, + }); + } + + removeConnection(id: string) { + const info = this.connections.get(id); + if (info) { + info.client.emit('close'); + this.connections.delete(id); + this.emit('clients-change'); + } + } +} diff --git a/src/ui/components/Block.js b/src/ui/components/Block.js new file mode 100644 index 000000000..4c7eec6f9 --- /dev/null +++ b/src/ui/components/Block.js @@ -0,0 +1,12 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import styled from '../styled/index.js'; + +export default styled.view({ + display: 'block', +}); diff --git a/src/ui/components/Box.js b/src/ui/components/Box.js new file mode 100644 index 000000000..e0712484e --- /dev/null +++ b/src/ui/components/Box.js @@ -0,0 +1,15 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import FlexBox from './FlexBox.js'; + +export default FlexBox.extends({ + height: '100%', + overflow: 'auto', + position: 'relative', + width: '100%', +}); diff --git a/src/ui/components/Button.js b/src/ui/components/Button.js new file mode 100644 index 000000000..384d1b2e8 --- /dev/null +++ b/src/ui/components/Button.js @@ -0,0 +1,363 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import Glyph from './Glyph.js'; +import styled from '../styled/index.js'; +import type {StyledComponent} from '../styled/index.js'; +import {findDOMNode} from 'react-dom'; +import PropTypes from 'prop-types'; +import {colors} from './colors.js'; +import {connect} from 'react-redux'; +import electron from 'electron'; + +const borderColor = props => { + if (!props.windowIsFocused) { + return colors.macOSTitleBarButtonBorderBlur; + } else if (props.type === 'danger') { + return colors.red; + } else { + return colors.macOSTitleBarButtonBorder; + } +}; +const borderBottomColor = props => { + if (!props.windowIsFocused) { + return colors.macOSTitleBarButtonBorderBlur; + } else if (props.type === 'danger') { + return colors.red; + } else { + return colors.macOSTitleBarButtonBorderBottom; + } +}; + +const StyledButton = styled.view( + { + backgroundColor: props => { + if (!props.windowIsFocused) { + return colors.macOSTitleBarButtonBackgroundBlur; + } else { + return colors.white; + } + }, + backgroundImage: props => + props.windowIsFocused + ? `linear-gradient(to bottom, transparent 0%,${ + colors.macOSTitleBarButtonBackground + } 100%)` + : 'none', + borderStyle: 'solid', + borderWidth: 1, + borderColor, + borderBottomColor, + fontSize: props => (props.compact === true ? 11 : '1em'), + color: props => { + if (props.type === 'danger' && props.windowIsFocused) { + return colors.red; + } else if (props.disabled) { + return colors.macOSTitleBarIconBlur; + } else { + return colors.light50; + } + }, + borderRadius: 4, + position: 'relative', + padding: '0 6px', + height: props => (props.compact === true ? 24 : 28), + margin: 0, + marginLeft: props => (props.inButtonGroup === true ? 0 : 10), + minWidth: 34, + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, + + boxShadow: props => + props.pulse && props.windowIsFocused + ? `0 0 0 ${colors.macOSTitleBarIconSelected}` + : '', + animation: props => + props.pulse && props.windowIsFocused ? 'pulse 1s infinite' : '', + + '&:not(:first-child)': { + borderTopLeftRadius: props => (props.inButtonGroup === true ? 0 : 4), + borderBottomLeftRadius: props => (props.inButtonGroup === true ? 0 : 4), + }, + + '&:not(:last-child)': { + borderTopRightRadius: props => (props.inButtonGroup === true ? 0 : 4), + borderBottomRightRadius: props => (props.inButtonGroup === true ? 0 : 4), + borderRight: props => (props.inButtonGroup === true ? 0 : ''), + }, + + '&:first-of-type': { + marginLeft: 0, + }, + + '&:active': { + borderColor: colors.macOSTitleBarButtonBorder, + borderBottomColor: colors.macOSTitleBarButtonBorderBottom, + background: `linear-gradient(to bottom, ${ + colors.macOSTitleBarButtonBackgroundActiveHighlight + } 1px, ${colors.macOSTitleBarButtonBackgroundActive} 0%, ${ + colors.macOSTitleBarButtonBorderBlur + } 100%)`, + }, + + '&:disabled': { + borderColor, + borderBottomColor, + pointerEvents: 'none', + }, + + '&:hover::before': { + content: props => (props.dropdown ? "''" : ''), + position: 'absolute', + bottom: 1, + right: 2, + borderStyle: 'solid', + borderWidth: '4px 3px 0 3px', + borderColor: props => + `${colors.macOSTitleBarIcon} transparent transparent transparent`, + }, + }, + { + ignoreAttributes: [ + 'dispatch', + 'compact', + 'large', + 'windowIsFocused', + 'inButtonGroup', + 'danger', + 'pulse', + ], + }, +); + +const Icon = Glyph.extends( + { + marginRight: props => (props.hasText ? 3 : 0), + }, + { + ignoreAttributes: ['hasText', 'type'], + }, +); + +type Props = { + /** + * onClick handler. + */ + onClick?: (event: SyntheticMouseEvent<>) => void, + /** + * Whether this button is disabled. + */ + disabled?: boolean, + /** + * Whether this button is large. Increases padding and line-height. + */ + large?: boolean, + /** + * Whether this button is compact. Decreases padding and line-height. + */ + compact?: boolean, + /** + * Type of button. + */ + type?: 'primary' | 'success' | 'warning' | 'danger', + /** + * Children. + */ + children?: React$Node, + /** + * Dropdown menu template shown on click. + */ + dropdown?: Array, + /** + * Name of the icon dispalyed next to the text + */ + icon?: string, + iconSize?: number, + /** + * For toggle buttons, if the button is selected + */ + selected?: boolean, + /** + * Button is pulsing + */ + pulse?: boolean, + /** + * URL to open in the browser on click + */ + href?: string, +}; + +type State = { + active: boolean, +}; + +/** + * Simple button. + * + * **Usage** + * + * ```jsx + * import {Button} from 'sonar'; + * + * ``` + * + * @example Default button + * + * @example Primary button + * + * @example Success button + * + * @example Warning button + * + * @example Danger button + * + * @example Default solid button + * + * @example Primary solid button + * + * @example Success solid button + * + * @example Warning solid button + * + * @example Danger solid button + * + * @example Compact button + * + * @example Large button + * + * @example Disabled button + * + */ +class Button extends styled.StylableComponent< + Props & {windowIsFocused: boolean}, + State, +> { + static contextTypes = { + inButtonGroup: PropTypes.bool, + }; + + state = { + active: false, + }; + + _ref: ?Element | ?Text; + + onMouseDown = () => this.setState({active: true}); + onMouseUp = () => this.setState({active: false}); + + onClick = (e: SyntheticMouseEvent<>) => { + if (this.props.disabled === true) { + return; + } + if (this.props.dropdown) { + const menu = electron.remote.Menu.buildFromTemplate(this.props.dropdown); + const position = {}; + if (this._ref != null && this._ref instanceof Element) { + const {left, bottom} = this._ref.getBoundingClientRect(); + position.x = parseInt(left, 10); + position.y = parseInt(bottom + 6, 10); + } + menu.popup(electron.remote.getCurrentWindow(), { + async: true, + ...position, + }); + } + if (this.props.onClick) { + this.props.onClick(e); + } + if (this.props.href != null) { + electron.shell.openExternal(this.props.href); + } + }; + + setRef = (ref: ?React.ElementRef) => { + this._ref = findDOMNode(ref); + }; + + render() { + const { + icon, + children, + selected, + iconSize, + windowIsFocused, + ...props + } = this.props; + const {active} = this.state; + + let color = colors.macOSTitleBarIcon; + if (props.disabled === true) { + color = colors.macOSTitleBarIconBlur; + } else if (windowIsFocused && selected === true) { + color = colors.macOSTitleBarIconSelected; + } else if (!windowIsFocused && (selected == null || selected === false)) { + color = colors.macOSTitleBarIconBlur; + } else if (!windowIsFocused && selected === true) { + color = colors.macOSTitleBarIconSelectedBlur; + } else if (selected == null && active) { + color = colors.macOSTitleBarIconActive; + } else if (props.type === 'danger') { + color = colors.red; + } + + let iconComponent; + if (icon != null) { + iconComponent = ( + + ); + } + + return ( + + {iconComponent} + {children} + {this.props.pulse === true && ( + \ No newline at end of file diff --git a/static/icons/componentkit-logo.png b/static/icons/componentkit-logo.png new file mode 100644 index 000000000..3b675db66 Binary files /dev/null and b/static/icons/componentkit-logo.png differ diff --git a/static/icons/componentscript-logo.png b/static/icons/componentscript-logo.png new file mode 100755 index 000000000..49cd42c5c Binary files /dev/null and b/static/icons/componentscript-logo.png differ diff --git a/static/icons/ios.svg b/static/icons/ios.svg new file mode 100644 index 000000000..8635d3a3e --- /dev/null +++ b/static/icons/ios.svg @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/static/icons/litho-logo.png b/static/icons/litho-logo.png new file mode 100644 index 000000000..16ac3a2ef Binary files /dev/null and b/static/icons/litho-logo.png differ diff --git a/static/icons/oculus.png b/static/icons/oculus.png new file mode 100644 index 000000000..4ae24d938 Binary files /dev/null and b/static/icons/oculus.png differ diff --git a/static/icons/sidebar_bottom.svg b/static/icons/sidebar_bottom.svg new file mode 100644 index 000000000..8dec72ea8 --- /dev/null +++ b/static/icons/sidebar_bottom.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/static/icons/sidebar_left.svg b/static/icons/sidebar_left.svg new file mode 100644 index 000000000..89eb13ed5 --- /dev/null +++ b/static/icons/sidebar_left.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/static/icons/sidebar_right.svg b/static/icons/sidebar_right.svg new file mode 100644 index 000000000..a24c3734f --- /dev/null +++ b/static/icons/sidebar_right.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/static/index.dev.html b/static/index.dev.html new file mode 100644 index 000000000..4d3c64525 --- /dev/null +++ b/static/index.dev.html @@ -0,0 +1,110 @@ + + + + + + + + + Sonar + + + + +
+ +
+
Loading...
+
+ + + + + + + diff --git a/static/index.html b/static/index.html new file mode 100644 index 000000000..a2e80969a --- /dev/null +++ b/static/index.html @@ -0,0 +1,18 @@ + + + + + + + + + Sonar + + +
+ + + + diff --git a/static/index.js b/static/index.js new file mode 100644 index 000000000..0c33d8c5e --- /dev/null +++ b/static/index.js @@ -0,0 +1,156 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +const {app, BrowserWindow} = require('electron'); +const path = require('path'); +const url = require('url'); +const fs = require('fs'); +const yargs = require('yargs'); +const compilePlugins = require('./compilePlugins.js'); + +// ensure .sonar folder and config exist +const sonarDir = path.join(require('os').homedir(), '.sonar'); +if (!fs.existsSync(sonarDir)) { + fs.mkdirSync(sonarDir); +} + +const configPath = path.join(sonarDir, 'config.json'); +let config = {pluginPaths: [], disabledPlugins: [], lastWindowPosition: {}}; + +try { + config = { + ...config, + ...JSON.parse(fs.readFileSync(configPath)), + }; +} catch (e) { + fs.writeFileSync(configPath, JSON.stringify(config)); +} + +const pluginPaths = config.pluginPaths.concat( + (yargs.argv.dynamicPlugins || '').split(',').filter(Boolean), +); + +process.env.CONFIG = JSON.stringify({ + ...config, + pluginPaths, +}); + +// possible reference to main app window +let win; +let appReady = false; +let pluginsCompiled = false; + +// tracking +setInterval(() => { + if (win && win.isFocused()) { + win.webContents.send('trackUsage'); + } +}, 60 * 1000); + +compilePlugins( + () => { + if (win) { + win.reload(); + } + }, + pluginPaths, + path.join(require('os').homedir(), '.sonar', 'plugins'), +).then(dynamicPlugins => { + process.env.PLUGINS = JSON.stringify(dynamicPlugins); + pluginsCompiled = true; + tryCreateWindow(); +}); + +// check if we already have an instance of this app open +const isSecondInstance = app.makeSingleInstance( + (commandLine, workingDirectory) => { + // someone tried to run a second instance, we should focus our window + if (win) { + if (win.isMinimized()) { + win.restore(); + } + + win.focus(); + } + }, +); + +// if this is a second instance then quit the app to prevent collisions +if (isSecondInstance) { + app.quit(); +} + +// quit app once all windows are closed +app.on('window-all-closed', () => { + appReady = false; + app.quit(); +}); + +app.on('ready', function() { + appReady = true; + app.commandLine.appendSwitch('scroll-bounce'); + tryCreateWindow(); + // if in development install the react devtools extension + if (process.env.NODE_ENV === 'development') { + const { + default: installExtension, + REACT_DEVELOPER_TOOLS, + REDUX_DEVTOOLS, + } = require('electron-devtools-installer'); + installExtension(REACT_DEVELOPER_TOOLS.id); + installExtension(REDUX_DEVTOOLS.id); + } +}); +function tryCreateWindow() { + if (appReady && pluginsCompiled) { + win = new BrowserWindow({ + show: false, + title: 'Sonar', + width: config.lastWindowPosition.width || 1400, + height: config.lastWindowPosition.height || 1000, + minWidth: 800, + minHeight: 600, + center: true, + fullscreenable: false, + backgroundThrottling: false, + titleBarStyle: 'hiddenInset', + webPreferences: { + webSecurity: false, + scrollBounce: true, + experimentalFeatures: true, + }, + }); + win.once('ready-to-show', () => win.show()); + win.once('close', ({sender}) => { + const [x, y] = sender.getPosition(); + const [width, height] = sender.getSize(); + // save window position and size + fs.writeFileSync( + configPath, + JSON.stringify({ + ...config, + lastWindowPosition: { + x, + y, + width, + height, + }, + }), + ); + }); + if (config.lastWindowPosition.x && config.lastWindowPosition.y) { + win.setPosition(config.lastWindowPosition.x, config.lastWindowPosition.y); + } + const entryUrl = + process.env.ELECTRON_URL || + url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true, + }); + win.loadURL(entryUrl); + } +} diff --git a/static/package.json b/static/package.json new file mode 100644 index 000000000..77c68eaf0 --- /dev/null +++ b/static/package.json @@ -0,0 +1,17 @@ +{ + "name": "sonar-static", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.0.0-beta.40", + "@babel/generator": "^7.0.0-beta.40", + "babel-plugin-transform-class-properties": "^7.0.0-beta.3", + "babel-plugin-transform-es2015-modules-commonjs": "^7.0.0-beta.3", + "babel-plugin-transform-flow-strip-types": "^7.0.0-beta.3", + "babel-plugin-transform-object-rest-spread": "^7.0.0-beta.3", + "babel-preset-react": "^7.0.0-beta.3", + "babylon": "^7.0.0-beta.40", + "metro": "^0.28.0" + } +} diff --git a/static/pattern.gif b/static/pattern.gif new file mode 100644 index 000000000..ea19bf64c Binary files /dev/null and b/static/pattern.gif differ diff --git a/static/serviceWorker.js b/static/serviceWorker.js new file mode 100644 index 000000000..db48e3abe --- /dev/null +++ b/static/serviceWorker.js @@ -0,0 +1,35 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +const CACHE_NAME = 'v1'; + +self.addEventListener('message', e => { + if (e.data.precachedIcons) { + caches.open(CACHE_NAME).then(cache => cache.addAll(e.data.precachedIcons)); + } +}); + +self.addEventListener('fetch', function(event) { + if (event.request.url.startsWith('https://external.xx.fbcdn.net/assets/')) { + event.respondWith( + // Cache falling back to the network + caches.match(event.request).then(cacheResponse => { + return ( + cacheResponse || + fetch(event.request).then(response => { + const clone = response.clone(); + // write to cache + caches + .open(CACHE_NAME) + .then(cache => cache.put(event.request, clone)); + return response; + }) + ); + }), + ); + } +}); diff --git a/static/style.css b/static/style.css new file mode 100644 index 000000000..57a1ce4d6 --- /dev/null +++ b/static/style.css @@ -0,0 +1,158 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +*:active, +*:focus { + outline: none; +} +/**/ + +html, +body, +#root { + height: 100%; + width: 100%; +} + +body { + font-family: system-ui; + font-size: 13px; + user-select: none; + -webkit-user-select: none; + cursor: default; + overflow: hidden; +} + +* { + box-sizing: border-box; +} + +#root { + overflow: hidden; +} diff --git a/static/transforms/dynamic-requires.js b/static/transforms/dynamic-requires.js new file mode 100644 index 000000000..c78f54379 --- /dev/null +++ b/static/transforms/dynamic-requires.js @@ -0,0 +1,31 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +function isDynamicRequire(node) { + return ( + node.type === 'CallExpression' && + node.callee.type === 'Identifier' && + node.callee.name === 'require' && + (node.arguments.length !== 1 || node.arguments[0].type !== 'StringLiteral') + ); +} + +module.exports = function(babel) { + const t = babel.types; + + return { + name: 'replace-dynamic-requires', + visitor: { + CallExpression(path) { + if (!isDynamicRequire(path.node)) { + return; + } + + path.replaceWith(t.identifier('triggerDynamicRequireError')); + }, + }, + }; +}; diff --git a/static/transforms/electron-requires.js b/static/transforms/electron-requires.js new file mode 100644 index 000000000..26a12ebdb --- /dev/null +++ b/static/transforms/electron-requires.js @@ -0,0 +1,87 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +const BUILTINS = [ + 'electron', + 'buffer', + 'child_process', + 'crypto', + 'dgram', + 'dns', + 'fs', + 'http', + 'https', + 'net', + 'os', + 'readline', + 'stream', + 'string_decoder', + 'tls', + 'tty', + 'zlib', + 'constants', + 'events', + 'url', + 'assert', + 'util', + 'path', + 'punycode', + 'querystring', + 'cluster', + 'console', + 'module', + 'process', + 'vm', + 'domain', + 'v8', + 'repl', + 'timers', +]; + +const IGNORED_MODULES = [ + 'bufferutil', + 'utf-8-validate', + 'spawn-sync', + './src/logcat', + './src/monkey', + './src/adb', +]; + +function isRequire(node) { + return ( + node.type === 'CallExpression' && + node.callee.type === 'Identifier' && + node.callee.name === 'require' && + node.arguments.length === 1 && + node.arguments[0].type === 'StringLiteral' + ); +} + +module.exports = function(babel) { + const t = babel.types; + + return { + name: 'infinity-import-react', + visitor: { + CallExpression(path) { + if (!isRequire(path.node)) { + return; + } + + const source = path.node.arguments[0].value; + + if (BUILTINS.includes(source)) { + path.node.callee.name = 'electronRequire'; + } + + if (IGNORED_MODULES.includes(source)) { + path.replaceWith(t.identifier('triggerReferenceError')); + } + }, + }, + }; +}; diff --git a/static/transforms/fb-stubs.js b/static/transforms/fb-stubs.js new file mode 100644 index 000000000..e106ad3d4 --- /dev/null +++ b/static/transforms/fb-stubs.js @@ -0,0 +1,45 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +const fs = require('fs'); +const path = require('path'); +const replaceFBStubs = fs.existsSync( + path.join(__dirname, '..', '..', 'src', 'fb'), +); + +const requireFromFolder = (folder, path) => + new RegExp(folder + '/[A-Za-z0-9.-_]+(.js)?$', 'g').test(path); + +module.exports = function(babel) { + return { + name: 'replace-dynamic-requires', + visitor: { + CallExpression(path) { + if ( + replaceFBStubs && + path.node.type === 'CallExpression' && + path.node.callee.type === 'Identifier' && + path.node.callee.name === 'require' && + path.node.arguments.length > 0 + ) { + if (requireFromFolder('fb', path.node.arguments[0].value)) { + throw new Error( + 'Do not requrie directly from fb/, but rather from fb-stubs/ to not break flow-typing and make sure stubs are uptodate.', + ); + } else if ( + requireFromFolder('fb-stubs', path.node.arguments[0].value) + ) { + path.node.arguments[0].value = path.node.arguments[0].value.replace( + '/fb-stubs/', + '/fb/', + ); + } + } + }, + }, + }; +}; diff --git a/static/transforms/import-react.js b/static/transforms/import-react.js new file mode 100644 index 000000000..002e22528 --- /dev/null +++ b/static/transforms/import-react.js @@ -0,0 +1,51 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +module.exports = function(babel) { + const t = babel.types; + + return { + name: 'infinity-import-react', + visitor: { + Program: { + exit(path, state) { + if (state.get('NEEDS_REACT')) { + path.unshiftContainer('body', [ + t.variableDeclaration('var', [ + t.variableDeclarator( + t.identifier('React'), + t.callExpression(t.identifier('require'), [ + t.stringLiteral('react'), + ]), + ), + ]), + ]); + } + }, + }, + + ReferencedIdentifier(path, state) { + // mark react as needing to be imported + if (path.node.name === 'React' && !path.scope.getBinding('React')) { + state.set('NEEDS_REACT', true); + } + + // replace Buffer with require('buffer') + if (path.node.name === 'Buffer' && !path.scope.getBinding('Buffer')) { + path.replaceWith( + t.memberExpression( + t.callExpression(t.identifier('require'), [ + t.stringLiteral('buffer'), + ]), + t.identifier('Buffer'), + ), + ); + } + }, + }, + }; +}; diff --git a/static/transforms/index.js b/static/transforms/index.js new file mode 100644 index 000000000..09b9f3081 --- /dev/null +++ b/static/transforms/index.js @@ -0,0 +1,64 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +const generate = require('@babel/generator').default; +const babylon = require('babylon'); +const babel = require('@babel/core'); +const metro = require('metro'); + +exports.transform = function({filename, options, src}) { + const presets = [require('../node_modules/babel-preset-react')]; + const isSonarPlugin = !__dirname.startsWith(options.projectRoot); + + let ast = babylon.parse(src, { + filename, + plugins: ['jsx', 'flow', 'classProperties', 'objectRestSpread'], + sourceType: 'module', + }); + + // run babel + const plugins = [ + require('../node_modules/babel-plugin-transform-object-rest-spread'), + require('../node_modules/babel-plugin-transform-class-properties'), + require('../node_modules/babel-plugin-transform-flow-strip-types'), + require('./electron-requires.js'), + require('./fb-stubs.js'), + require('./dynamic-requires.js'), + ]; + if (isSonarPlugin) { + plugins.push(require('./sonar-requires.js')); + } else { + plugins.push(require('./import-react.js')); + } + plugins.unshift(require('babel-plugin-transform-es2015-modules-commonjs')); + + ast = babel.transformFromAst(ast, src, { + babelrc: !filename.includes('node_modules'), + code: false, + comments: false, + compact: false, + filename, + plugins, + presets, + sourceMaps: true, + }).ast; + const result = generate( + ast, + { + filename, + sourceFileName: filename, + sourceMaps: true, + }, + src, + ); + return { + ast, + code: result.code, + filename, + map: result.rawMappings.map(metro.sourceMaps.compactMapping), + }; +}; diff --git a/static/transforms/sonar-requires.js b/static/transforms/sonar-requires.js new file mode 100644 index 000000000..33466f85a --- /dev/null +++ b/static/transforms/sonar-requires.js @@ -0,0 +1,64 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +// do not apply this transform for these paths +const EXCLUDE_PATHS = [ + '/node_modules/react-devtools-core/', + 'relay-devtools/DevtoolsUI', +]; + +function isExcludedPath(path) { + for (const epath of EXCLUDE_PATHS) { + if (path.indexOf(epath) > -1) { + return true; + } + } + return false; +} // $FlowFixMe +module.exports = ({types: t}) => ({ + visitor: { + // $FlowFixMe + CallExpression(path, state) { + if (isExcludedPath(state.file.opts.filename)) { + return; + } + const node = path.node; + const args = node.arguments || []; + if ( + node.callee.name === 'require' && + args.length === 1 && + t.isStringLiteral(args[0]) && + args[0].value === 'sonar' + ) { + path.replaceWith(t.identifier('window.Sonar')); + } else if ( + node.callee.name === 'require' && + args.length > 0 && + t.isStringLiteral(args[0]) && + args[0].value === 'react' + ) { + path.replaceWith(t.identifier('window.React')); + } else if ( + node.callee.name === 'require' && + args.length > 0 && + t.isStringLiteral(args[0]) && + args[0].value === 'react-dom' + ) { + path.replaceWith(t.identifier('window.ReactDOM')); + } + }, + Identifier(path, state) { + if ( + path.node.name === 'React' && + path.parentPath.node.id !== path.node && + !isExcludedPath(state.file.opts.filename) + ) { + path.replaceWith(t.identifier('window.React')); + } + }, + }, +}); diff --git a/static/uiperf/screenshot.png b/static/uiperf/screenshot.png new file mode 100644 index 000000000..5cbc1bad0 Binary files /dev/null and b/static/uiperf/screenshot.png differ diff --git a/static/vis/vis.min.css b/static/vis/vis.min.css new file mode 100644 index 000000000..f345ff532 --- /dev/null +++ b/static/vis/vis.min.css @@ -0,0 +1 @@ +.vis .overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis [class*=span]{min-height:0;width:auto}div.vis-configuration{position:relative;display:block;float:left;font-size:12px}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration-wrapper::after{clear:both;content:"";display:block}div.vis-configuration.vis-config-option-container{display:block;width:495px;background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;margin-top:20px;left:10px;padding-left:5px}div.vis-configuration.vis-config-button{display:block;width:495px;height:25px;vertical-align:middle;line-height:25px;background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;margin-top:20px;left:10px;padding-left:5px;cursor:pointer;margin-bottom:30px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;width:495px;height:25px;vertical-align:middle;line-height:25px}div.vis-configuration.vis-config-item.vis-config-s2{left:10px;background-color:#f7f8fa;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s3{left:20px;background-color:#e4e9f0;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-item.vis-config-s4{left:30px;background-color:#cfd8e6;padding-left:5px;border-radius:3px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{width:120px;height:25px;line-height:25px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{top:1px;width:30px;height:19px;border:1px solid #444;border-radius:2px;padding:0;margin:0;cursor:pointer}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{position:relative;top:-5px;width:60px;padding:1px;margin:0;pointer-events:none}input.vis-configuration.vis-config-range{-webkit-appearance:none;border:0 solid #fff;background-color:rgba(0,0,0,0);width:300px;height:20px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{width:300px;height:5px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;border:1px solid #14334b;height:17px;width:17px;border-radius:50%;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2 0,#385380 100%);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(to bottom,#3876c2 0,#385380 100%);box-shadow:#111927 0 0 1px 0;margin-top:-7px}input.vis-configuration.vis-config-range:focus{outline:0}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(to bottom,#9d9d9d 0,#c8c8c8 99%)}input.vis-configuration.vis-config-range::-moz-range-track{width:300px;height:10px;background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(to bottom,#dedede 0,#c8c8c8 99%);border:1px solid #999;box-shadow:#aaa 0 0 3px 0;border-radius:3px}input.vis-configuration.vis-config-range::-moz-range-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{width:300px;height:5px;background:0 0;border-color:transparent;border-width:6px 0;color:transparent}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{border:none;height:16px;width:16px;border-radius:50%;background:#385380}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{position:absolute;background:rgba(57,76,89,.85);border:2px solid #f2faff;line-height:30px;height:30px;width:150px;text-align:center;color:#fff;font-size:14px;border-radius:4px;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.vis-configuration-popup:after,.vis-configuration-popup:before{left:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}.vis-configuration-popup:after{border-color:rgba(136,183,213,0);border-left-color:rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0);border-left-color:#f2faff;border-width:12px;margin-top:-12px}div.vis-tooltip{position:absolute;visibility:hidden;padding:5px;white-space:nowrap;font-family:verdana;font-size:14px;color:#000;background-color:#f5f4ed;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;border:1px solid #808074;box-shadow:3px 3px 10px rgba(0,0,0,.2);pointer-events:none;z-index:5}div.vis-color-picker{position:absolute;top:0;left:30px;margin-top:-140px;margin-left:30px;width:310px;height:444px;z-index:1;padding:10px;border-radius:15px;background-color:#fff;display:none;box-shadow:rgba(0,0,0,.5) 0 0 10px 0}div.vis-color-picker div.vis-arrow{position:absolute;top:147px;left:5px}div.vis-color-picker div.vis-arrow::after,div.vis-color-picker div.vis-arrow::before{right:100%;top:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.vis-color-picker div.vis-arrow:after{border-color:rgba(255,255,255,0);border-right-color:#fff;border-width:30px;margin-top:-30px}div.vis-color-picker div.vis-color{position:absolute;width:289px;height:289px;cursor:pointer}div.vis-color-picker div.vis-brightness{position:absolute;top:313px}div.vis-color-picker div.vis-opacity{position:absolute;top:350px}div.vis-color-picker div.vis-selector{position:absolute;top:137px;left:137px;width:15px;height:15px;border-radius:15px;border:1px solid #fff;background:#4c4c4c;background:-moz-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#4c4c4c),color-stop(12%,#595959),color-stop(25%,#666),color-stop(39%,#474747),color-stop(50%,#2c2c2c),color-stop(51%,#000),color-stop(60%,#111),color-stop(76%,#2b2b2b),color-stop(91%,#1c1c1c),color-stop(100%,#131313));background:-webkit-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-o-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:-ms-linear-gradient(top,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%);background:linear-gradient(to bottom,#4c4c4c 0,#595959 12%,#666 25%,#474747 39%,#2c2c2c 50%,#000 51%,#111 60%,#2b2b2b 76%,#1c1c1c 91%,#131313 100%)}div.vis-color-picker div.vis-new-color{position:absolute;width:140px;height:20px;border:1px solid rgba(0,0,0,.1);border-radius:5px;top:380px;left:159px;text-align:right;padding-right:2px;font-size:10px;color:rgba(0,0,0,.4);vertical-align:middle;line-height:20px}div.vis-color-picker div.vis-initial-color{position:absolute;width:140px;height:20px;border:1px solid rgba(0,0,0,.1);border-radius:5px;top:380px;left:10px;text-align:left;padding-left:2px;font-size:10px;color:rgba(0,0,0,.4);vertical-align:middle;line-height:20px}div.vis-color-picker div.vis-label{position:absolute;width:300px;left:10px}div.vis-color-picker div.vis-label.vis-brightness{top:300px}div.vis-color-picker div.vis-label.vis-opacity{top:338px}div.vis-color-picker div.vis-button{position:absolute;width:68px;height:25px;border-radius:10px;vertical-align:middle;text-align:center;line-height:25px;top:410px;border:2px solid #d9d9d9;background-color:#f7f7f7;cursor:pointer}div.vis-color-picker div.vis-button.vis-cancel{left:5px}div.vis-color-picker div.vis-button.vis-load{left:82px}div.vis-color-picker div.vis-button.vis-apply{left:159px}div.vis-color-picker div.vis-button.vis-save{left:236px}div.vis-color-picker input.vis-range{width:290px;height:20px}div.vis-network div.vis-manipulation{box-sizing:content-box;border-width:0;border-bottom:1px;border-style:solid;border-color:#d6d9d8;background:#fff;background:-moz-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(48%,#fcfcfc),color-stop(50%,#fafafa),color-stop(100%,#fcfcfc));background:-webkit-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-o-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:-ms-linear-gradient(top,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);background:linear-gradient(to bottom,#fff 0,#fcfcfc 48%,#fafafa 50%,#fcfcfc 100%);padding-top:4px;position:absolute;left:0;top:0;width:100%;height:28px}div.vis-network div.vis-edit-mode{position:absolute;left:0;top:5px;height:30px}div.vis-network div.vis-close{position:absolute;right:0;top:0;width:30px;height:30px;background-position:20px 3px;background-repeat:no-repeat;background-image:url(img/network/cross.png);cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-close:hover{opacity:.6}div.vis-network div.vis-edit-mode div.vis-button,div.vis-network div.vis-manipulation div.vis-button{float:left;font-family:verdana;font-size:12px;-moz-border-radius:15px;border-radius:15px;display:inline-block;background-position:0 0;background-repeat:no-repeat;height:24px;margin-left:10px;cursor:pointer;padding:0 8px 0 8px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-manipulation div.vis-button:hover{box-shadow:1px 1px 8px rgba(0,0,0,.2)}div.vis-network div.vis-manipulation div.vis-button:active{box-shadow:1px 1px 8px rgba(0,0,0,.5)}div.vis-network div.vis-manipulation div.vis-button.vis-back{background-image:url(img/network/backIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-none:hover{box-shadow:1px 1px 8px transparent;cursor:default}div.vis-network div.vis-manipulation div.vis-button.vis-none:active{box-shadow:1px 1px 8px transparent}div.vis-network div.vis-manipulation div.vis-button.vis-none{padding:0}div.vis-network div.vis-manipulation div.notification{margin:2px;font-weight:700}div.vis-network div.vis-manipulation div.vis-button.vis-add{background-image:url(img/network/addNodeIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit,div.vis-network div.vis-manipulation div.vis-button.vis-edit{background-image:url(img/network/editIcon.png)}div.vis-network div.vis-edit-mode div.vis-button.vis-edit.vis-edit-mode{background-color:#fcfcfc;border:1px solid #ccc}div.vis-network div.vis-manipulation div.vis-button.vis-connect{background-image:url(img/network/connectIcon.png)}div.vis-network div.vis-manipulation div.vis-button.vis-delete{background-image:url(img/network/deleteIcon.png)}div.vis-network div.vis-edit-mode div.vis-label,div.vis-network div.vis-manipulation div.vis-label{margin:0 0 0 23px;line-height:25px}div.vis-network div.vis-manipulation div.vis-separator-line{float:left;display:inline-block;width:1px;height:21px;background-color:#bdbdbd;margin:0 7px 0 15px}div.vis-network div.vis-navigation div.vis-button{width:34px;height:34px;-moz-border-radius:17px;border-radius:17px;position:absolute;display:inline-block;background-position:2px 2px;background-repeat:no-repeat;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}div.vis-network div.vis-navigation div.vis-button:hover{box-shadow:0 0 3px 3px rgba(56,207,21,.3)}div.vis-network div.vis-navigation div.vis-button:active{box-shadow:0 0 1px 3px rgba(56,207,21,.95)}div.vis-network div.vis-navigation div.vis-button.vis-up{background-image:url(img/network/upArrow.png);bottom:50px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-down{background-image:url(img/network/downArrow.png);bottom:10px;left:55px}div.vis-network div.vis-navigation div.vis-button.vis-left{background-image:url(img/network/leftArrow.png);bottom:10px;left:15px}div.vis-network div.vis-navigation div.vis-button.vis-right{background-image:url(img/network/rightArrow.png);bottom:10px;left:95px}div.vis-network div.vis-navigation div.vis-button.vis-zoomIn{background-image:url(img/network/plus.png);bottom:10px;right:15px}div.vis-network div.vis-navigation div.vis-button.vis-zoomOut{background-image:url(img/network/minus.png);bottom:10px;right:55px}div.vis-network div.vis-navigation div.vis-button.vis-zoomExtends{background-image:url(img/network/zoomExtends.png);bottom:50px;right:15px}.vis-current-time{background-color:#ff7f6e;width:2px;z-index:1;pointer-events:none}.vis-rolling-mode-btn{height:40px;width:40px;position:absolute;top:7px;right:20px;border-radius:50%;font-size:28px;cursor:pointer;opacity:.8;color:#fff;font-weight:700;text-align:center;background:#3876c2}.vis-rolling-mode-btn:before{content:"\26F6"}.vis-rolling-mode-btn:hover{opacity:1}.vis-custom-time{background-color:#6e94ff;width:2px;cursor:move;z-index:1}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-horizontal{position:absolute;width:100%;height:0;border-bottom:1px solid}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-minor{border-color:#e5e5e5}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-major{border-color:#bfbfbf}.vis-data-axis .vis-y-axis.vis-major{width:100%;position:absolute;color:#4d4d4d;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-major.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-minor{position:absolute;width:100%;color:#bebebe;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-minor.vis-measure{padding:0;margin:0;border:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title{position:absolute;color:#4d4d4d;white-space:nowrap;bottom:20px;text-align:center}.vis-data-axis .vis-y-axis.vis-title.vis-measure{padding:0;margin:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title.vis-left{bottom:0;-webkit-transform-origin:left top;-moz-transform-origin:left top;-ms-transform-origin:left top;-o-transform-origin:left top;transform-origin:left bottom;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.vis-data-axis .vis-y-axis.vis-title.vis-right{bottom:0;-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-ms-transform-origin:right bottom;-o-transform-origin:right bottom;transform-origin:right bottom;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.vis-legend{background-color:rgba(247,252,255,.65);padding:5px;border:1px solid #b3b3b3;box-shadow:2px 2px 10px rgba(154,154,154,.55)}.vis-legend-text{white-space:nowrap;display:inline-block}.vis-item{position:absolute;color:#1a1a1a;border-color:#97b0f8;border-width:1px;background-color:#d5ddf6;display:inline-block;z-index:1}.vis-item.vis-selected{border-color:#ffc200;background-color:#fff785;z-index:2}.vis-editable.vis-selected{cursor:move}.vis-item.vis-point.vis-selected{background-color:#fff785}.vis-item.vis-box{text-align:center;border-style:solid;border-radius:2px}.vis-item.vis-point{background:0 0}.vis-item.vis-dot{position:absolute;padding:0;border-width:4px;border-style:solid;border-radius:4px}.vis-item.vis-range{border-style:solid;border-radius:2px;box-sizing:border-box}.vis-item.vis-background{border:none;background-color:rgba(213,221,246,.4);box-sizing:border-box;padding:0;margin:0}.vis-item .vis-item-overflow{position:relative;width:100%;height:100%;padding:0;margin:0;overflow:hidden}.vis-item-visible-frame{white-space:nowrap}.vis-item.vis-range .vis-item-content{position:relative;display:inline-block}.vis-item.vis-background .vis-item-content{position:absolute;display:inline-block}.vis-item.vis-line{padding:0;position:absolute;width:0;border-left-width:1px;border-left-style:solid}.vis-item .vis-item-content{white-space:nowrap;box-sizing:border-box;padding:5px}.vis-item .vis-onUpdateTime-tooltip{position:absolute;background:#4f81bd;color:#fff;width:200px;text-align:center;white-space:nowrap;padding:5px;border-radius:1px;transition:.4s;-o-transition:.4s;-moz-transition:.4s;-webkit-transition:.4s}.vis-item .vis-delete,.vis-item .vis-delete-rtl{position:absolute;top:0;width:24px;height:24px;box-sizing:border-box;padding:0 5px;cursor:pointer;-webkit-transition:background .2s linear;-moz-transition:background .2s linear;-ms-transition:background .2s linear;-o-transition:background .2s linear;transition:background .2s linear}.vis-item .vis-delete{right:-24px}.vis-item .vis-delete-rtl{left:-24px}.vis-item .vis-delete-rtl:after,.vis-item .vis-delete:after{content:"\00D7";color:red;font-family:arial,sans-serif;font-size:22px;font-weight:700;-webkit-transition:color .2s linear;-moz-transition:color .2s linear;-ms-transition:color .2s linear;-o-transition:color .2s linear;transition:color .2s linear}.vis-item .vis-delete-rtl:hover,.vis-item .vis-delete:hover{background:red}.vis-item .vis-delete-rtl:hover:after,.vis-item .vis-delete:hover:after{color:#fff}.vis-item .vis-drag-center{position:absolute;width:100%;height:100%;top:0;left:0;cursor:move}.vis-item.vis-range .vis-drag-left{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;left:-4px;cursor:w-resize}.vis-item.vis-range .vis-drag-right{position:absolute;width:24px;max-width:20%;min-width:2px;height:100%;top:0;right:-4px;cursor:e-resize}.vis-range.vis-item.vis-readonly .vis-drag-left,.vis-range.vis-item.vis-readonly .vis-drag-right{cursor:auto}.vis-itemset{position:relative;padding:0;margin:0;box-sizing:border-box}.vis-itemset .vis-background,.vis-itemset .vis-foreground{position:absolute;width:100%;height:100%;overflow:visible}.vis-axis{position:absolute;width:100%;height:0;left:0;z-index:1}.vis-foreground .vis-group{position:relative;box-sizing:border-box;border-bottom:1px solid #bfbfbf}.vis-foreground .vis-group:last-child{border-bottom:none}.vis-nesting-group{cursor:pointer}.vis-nested-group{background:#f5f5f5}.vis-label.vis-nesting-group.expanded:before{content:"\25BC"}.vis-label.vis-nesting-group.collapsed-rtl:before{content:"\25C0"}.vis-label.vis-nesting-group.collapsed:before{content:"\25B6"}.vis-overlay{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.vis-labelset{position:relative;overflow:hidden;box-sizing:border-box}.vis-labelset .vis-label{position:relative;left:0;top:0;width:100%;color:#4d4d4d;box-sizing:border-box}.vis-labelset .vis-label{border-bottom:1px solid #bfbfbf}.vis-labelset .vis-label.draggable{cursor:pointer}.vis-labelset .vis-label:last-child{border-bottom:none}.vis-labelset .vis-label .vis-inner{display:inline-block;padding:5px}.vis-labelset .vis-label .vis-inner.vis-hidden{padding:0}.vis-panel{position:absolute;padding:0;margin:0;box-sizing:border-box}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right,.vis-panel.vis-top{border:1px #bfbfbf}.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right{border-top-style:solid;border-bottom-style:solid;overflow:hidden}.vis-left.vis-panel.vis-vertical-scroll,.vis-right.vis-panel.vis-vertical-scroll{height:100%;overflow-x:hidden;overflow-y:scroll}.vis-left.vis-panel.vis-vertical-scroll{direction:rtl}.vis-left.vis-panel.vis-vertical-scroll .vis-content{direction:ltr}.vis-right.vis-panel.vis-vertical-scroll{direction:ltr}.vis-right.vis-panel.vis-vertical-scroll .vis-content{direction:rtl}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-top{border-left-style:solid;border-right-style:solid}.vis-background{overflow:hidden}.vis-panel>.vis-content{position:relative}.vis-panel .vis-shadow{position:absolute;width:100%;height:1px;box-shadow:0 0 10px rgba(0,0,0,.8)}.vis-panel .vis-shadow.vis-top{top:-1px;left:0}.vis-panel .vis-shadow.vis-bottom{bottom:-1px;left:0}.vis-graph-group0{fill:#4f81bd;fill-opacity:0;stroke-width:2px;stroke:#4f81bd}.vis-graph-group1{fill:#f79646;fill-opacity:0;stroke-width:2px;stroke:#f79646}.vis-graph-group2{fill:#8c51cf;fill-opacity:0;stroke-width:2px;stroke:#8c51cf}.vis-graph-group3{fill:#75c841;fill-opacity:0;stroke-width:2px;stroke:#75c841}.vis-graph-group4{fill:#ff0100;fill-opacity:0;stroke-width:2px;stroke:#ff0100}.vis-graph-group5{fill:#37d8e6;fill-opacity:0;stroke-width:2px;stroke:#37d8e6}.vis-graph-group6{fill:#042662;fill-opacity:0;stroke-width:2px;stroke:#042662}.vis-graph-group7{fill:#00ff26;fill-opacity:0;stroke-width:2px;stroke:#00ff26}.vis-graph-group8{fill:#f0f;fill-opacity:0;stroke-width:2px;stroke:#f0f}.vis-graph-group9{fill:#8f3938;fill-opacity:0;stroke-width:2px;stroke:#8f3938}.vis-timeline .vis-fill{fill-opacity:.1;stroke:none}.vis-timeline .vis-bar{fill-opacity:.5;stroke-width:1px}.vis-timeline .vis-point{stroke-width:2px;fill-opacity:1}.vis-timeline .vis-legend-background{stroke-width:1px;fill-opacity:.9;fill:#fff;stroke:#c2c2c2}.vis-timeline .vis-outline{stroke-width:1px;fill-opacity:1;fill:#fff;stroke:#e5e5e5}.vis-timeline .vis-icon-fill{fill-opacity:.3;stroke:none}.vis-time-axis{position:relative;overflow:hidden}.vis-time-axis.vis-foreground{top:0;left:0;width:100%}.vis-time-axis.vis-background{position:absolute;top:0;left:0;width:100%;height:100%}.vis-time-axis .vis-text{position:absolute;color:#4d4d4d;padding:3px;overflow:hidden;box-sizing:border-box;white-space:nowrap}.vis-time-axis .vis-text.vis-measure{position:absolute;padding-left:0;padding-right:0;margin-left:0;margin-right:0;visibility:hidden}.vis-time-axis .vis-grid.vis-vertical{position:absolute;border-left:1px solid}.vis-time-axis .vis-grid.vis-vertical-rtl{position:absolute;border-right:1px solid}.vis-time-axis .vis-grid.vis-minor{border-color:#e5e5e5}.vis-time-axis .vis-grid.vis-major{border-color:#bfbfbf}.vis-timeline{position:relative;border:1px solid #bfbfbf;overflow:hidden;padding:0;margin:0;box-sizing:border-box} \ No newline at end of file diff --git a/static/yarn.lock b/static/yarn.lock new file mode 100644 index 000000000..e828e1596 --- /dev/null +++ b/static/yarn.lock @@ -0,0 +1,3115 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.40.tgz#37e2b0cf7c56026b4b21d3927cadf81adec32ac6" + dependencies: + "@babel/highlight" "7.0.0-beta.40" + +"@babel/core@^7.0.0-beta", "@babel/core@^7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.0.0-beta.40.tgz#455464dd81d499fd97d32b473f0331f74379a33f" + dependencies: + "@babel/code-frame" "7.0.0-beta.40" + "@babel/generator" "7.0.0-beta.40" + "@babel/helpers" "7.0.0-beta.40" + "@babel/template" "7.0.0-beta.40" + "@babel/traverse" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + babylon "7.0.0-beta.40" + convert-source-map "^1.1.0" + debug "^3.0.1" + json5 "^0.5.0" + lodash "^4.2.0" + micromatch "^2.3.11" + resolve "^1.3.2" + source-map "^0.5.0" + +"@babel/generator@7.0.0-beta.40", "@babel/generator@^7.0.0-beta", "@babel/generator@^7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.40.tgz#ab61f9556f4f71dbd1138949c795bb9a21e302ea" + dependencies: + "@babel/types" "7.0.0-beta.40" + jsesc "^2.5.1" + lodash "^4.2.0" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-annotate-as-pure@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.40.tgz#095dd4c70b231eba17ebf61c3434e6f9d71bd574" + dependencies: + "@babel/types" "7.0.0-beta.40" + +"@babel/helper-builder-react-jsx@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.0.0-beta.40.tgz#2a171b6c4939c6cd0bdc38cca261d1f3b32cedb1" + dependencies: + "@babel/types" "7.0.0-beta.40" + esutils "^2.0.0" + +"@babel/helper-call-delegate@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-beta.40.tgz#5d5000d0bf76c68ee6866961e0b7eb6e9ed52438" + dependencies: + "@babel/helper-hoist-variables" "7.0.0-beta.40" + "@babel/traverse" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + +"@babel/helper-define-map@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.0.0-beta.40.tgz#ad64c548dd98e7746305852f113ed04dc74329c0" + dependencies: + "@babel/helper-function-name" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + lodash "^4.2.0" + +"@babel/helper-function-name@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.40.tgz#9d033341ab16517f40d43a73f2d81fc431ccd7b6" + dependencies: + "@babel/helper-get-function-arity" "7.0.0-beta.40" + "@babel/template" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + +"@babel/helper-get-function-arity@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.40.tgz#ac0419cf067b0ec16453e1274f03878195791c6e" + dependencies: + "@babel/types" "7.0.0-beta.40" + +"@babel/helper-hoist-variables@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-beta.40.tgz#59d47fd133782d60db89af0d18083ad3c9f4801c" + dependencies: + "@babel/types" "7.0.0-beta.40" + +"@babel/helper-module-imports@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.40.tgz#251cbb6404599282e8f7356a5b32c9381bef5d2d" + dependencies: + "@babel/types" "7.0.0-beta.40" + lodash "^4.2.0" + +"@babel/helper-module-transforms@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.40.tgz#e5240afd47bd98f6ae65874b9ae508533abfee76" + dependencies: + "@babel/helper-module-imports" "7.0.0-beta.40" + "@babel/helper-simple-access" "7.0.0-beta.40" + "@babel/template" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + lodash "^4.2.0" + +"@babel/helper-optimise-call-expression@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-beta.40.tgz#f0e7f70d455bff8ab6a248a84f0221098fa468ac" + dependencies: + "@babel/types" "7.0.0-beta.40" + +"@babel/helper-remap-async-to-generator@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-beta.40.tgz#33414d1cc160ebf0991ebc60afebe36b08feae05" + dependencies: + "@babel/helper-annotate-as-pure" "7.0.0-beta.40" + "@babel/helper-wrap-function" "7.0.0-beta.40" + "@babel/template" "7.0.0-beta.40" + "@babel/traverse" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + +"@babel/helper-replace-supers@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-beta.40.tgz#2ab0c9e7fa17d313745f1634ce6b7bccaa5dd5fe" + dependencies: + "@babel/helper-optimise-call-expression" "7.0.0-beta.40" + "@babel/template" "7.0.0-beta.40" + "@babel/traverse" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + +"@babel/helper-simple-access@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.40.tgz#018f765090a3d25153778958969f235dc6ce5b57" + dependencies: + "@babel/template" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + lodash "^4.2.0" + +"@babel/helper-wrap-function@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-beta.40.tgz#4db4630cdaf4fd47fa2c45b5b7a9ecc33ff3f2be" + dependencies: + "@babel/helper-function-name" "7.0.0-beta.40" + "@babel/template" "7.0.0-beta.40" + "@babel/traverse" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + +"@babel/helpers@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.0.0-beta.40.tgz#82f8e144f56b2896b1d624ca88ac4603023ececd" + dependencies: + "@babel/template" "7.0.0-beta.40" + "@babel/traverse" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + +"@babel/highlight@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.40.tgz#b43d67d76bf46e1d10d227f68cddcd263786b255" + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +"@babel/plugin-check-constants@^7.0.0-beta": + version "7.0.0-beta.38" + resolved "https://registry.yarnpkg.com/@babel/plugin-check-constants/-/plugin-check-constants-7.0.0-beta.38.tgz#bbda6306d45a4f097ccb416c0b52d6503f6502cf" + +"@babel/plugin-external-helpers@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-external-helpers/-/plugin-external-helpers-7.0.0-beta.40.tgz#9f08717d1016918a60d497ad9e35c44b3489a45c" + +"@babel/plugin-proposal-class-properties@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.0.0-beta.40.tgz#ee0549729e9f44603efa17523b459ea3021458dc" + dependencies: + "@babel/helper-function-name" "7.0.0-beta.40" + "@babel/plugin-syntax-class-properties" "7.0.0-beta.40" + +"@babel/plugin-proposal-object-rest-spread@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-beta.40.tgz#ce35d2240908e52706a612eb26d67db667cd700f" + dependencies: + "@babel/plugin-syntax-object-rest-spread" "7.0.0-beta.40" + +"@babel/plugin-syntax-class-properties@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0-beta.40.tgz#ff82c04c6d97cdb947dc64e3f3d4bc791e85a16f" + +"@babel/plugin-syntax-dynamic-import@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0-beta.40.tgz#5d9b58d4fbe1dfabbd44dee2eb267c466d7e9b87" + +"@babel/plugin-syntax-flow@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.0.0-beta.40.tgz#2326da177cd83ad3d12e8324ad003edb702c384c" + +"@babel/plugin-syntax-jsx@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.0.0-beta.40.tgz#db44d52ff06f784be22f2659e694cc2cf97f99f9" + +"@babel/plugin-syntax-object-rest-spread@7.0.0-beta.40": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-beta.40.tgz#d5e04536062e4df685c203ae48bb19bfe2cf235c" + +"@babel/plugin-transform-arrow-functions@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-beta.40.tgz#0842045b16835d6da0c334d0b09d575852f27962" + +"@babel/plugin-transform-block-scoping@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-beta.40.tgz#23197ee6f696b7e5ace884f0dc5434df20d7dd97" + dependencies: + lodash "^4.2.0" + +"@babel/plugin-transform-classes@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.0.0-beta.40.tgz#c7a752009df4bb0f77179027daa0783f9a036b0b" + dependencies: + "@babel/helper-annotate-as-pure" "7.0.0-beta.40" + "@babel/helper-define-map" "7.0.0-beta.40" + "@babel/helper-function-name" "7.0.0-beta.40" + "@babel/helper-optimise-call-expression" "7.0.0-beta.40" + "@babel/helper-replace-supers" "7.0.0-beta.40" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-beta.40.tgz#e4bd53455d9f96882cc8e9923895d71690f6969e" + +"@babel/plugin-transform-destructuring@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-beta.40.tgz#503a4719eb9ed8c933b50d4ec3f106ed371852ee" + +"@babel/plugin-transform-flow-strip-types@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.0.0-beta.40.tgz#fe3afe922de6dfbd21d9f53f01cbe1bac89e0423" + dependencies: + "@babel/plugin-syntax-flow" "7.0.0-beta.40" + +"@babel/plugin-transform-for-of@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-beta.40.tgz#67920d749bac4840ceeae9907d918dad33908244" + +"@babel/plugin-transform-function-name@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-beta.40.tgz#37b5ca4f90fba207d359c0be3af5bfecdc737a3d" + dependencies: + "@babel/helper-function-name" "7.0.0-beta.40" + +"@babel/plugin-transform-literals@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-beta.40.tgz#a6bf8808f97accf42a171b27a133802aa0650d3e" + +"@babel/plugin-transform-modules-commonjs@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.0.0-beta.40.tgz#a85f8c311f498a94a45531cc4ed5ff98b338a70a" + dependencies: + "@babel/helper-module-transforms" "7.0.0-beta.40" + "@babel/helper-simple-access" "7.0.0-beta.40" + +"@babel/plugin-transform-object-assign@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.0.0-beta.40.tgz#c201c0e46befd15cf5439db07df7d7470ac943be" + +"@babel/plugin-transform-parameters@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-beta.40.tgz#efa366fab0dcbd0221b46aa2662c324b4b414d1d" + dependencies: + "@babel/helper-call-delegate" "7.0.0-beta.40" + "@babel/helper-get-function-arity" "7.0.0-beta.40" + +"@babel/plugin-transform-react-display-name@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.0.0-beta.40.tgz#2e9aba5d74da8ecee00d6d4bf68c833955355e4c" + +"@babel/plugin-transform-react-jsx-source@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.0.0-beta.40.tgz#7e62fe33f3e46c7f0d81d187d9c9aa348daa6488" + dependencies: + "@babel/plugin-syntax-jsx" "7.0.0-beta.40" + +"@babel/plugin-transform-react-jsx@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.0.0-beta.40.tgz#93af0b0ef691cda86ab52d912b50f72eb538349d" + dependencies: + "@babel/helper-builder-react-jsx" "7.0.0-beta.40" + "@babel/plugin-syntax-jsx" "7.0.0-beta.40" + +"@babel/plugin-transform-regenerator@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-beta.40.tgz#f8a89ce89a0fae8e9cdfc2f2768104811517374a" + dependencies: + regenerator-transform "^0.12.3" + +"@babel/plugin-transform-shorthand-properties@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-beta.40.tgz#421835237b0fcab0e67c941726d95dfc543514f4" + +"@babel/plugin-transform-spread@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-beta.40.tgz#881578938e5750137301750bef7fdd0e01be76be" + +"@babel/plugin-transform-template-literals@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.40.tgz#5ef3377d1294aee39b913768a1f884806a45393b" + dependencies: + "@babel/helper-annotate-as-pure" "7.0.0-beta.40" + +"@babel/template@7.0.0-beta.40", "@babel/template@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.40.tgz#034988c6424eb5c3268fe6a608626de1f4410fc8" + dependencies: + "@babel/code-frame" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + babylon "7.0.0-beta.40" + lodash "^4.2.0" + +"@babel/traverse@7.0.0-beta.40", "@babel/traverse@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.40.tgz#d140e449b2e093ef9fe1a2eecc28421ffb4e521e" + dependencies: + "@babel/code-frame" "7.0.0-beta.40" + "@babel/generator" "7.0.0-beta.40" + "@babel/helper-function-name" "7.0.0-beta.40" + "@babel/types" "7.0.0-beta.40" + babylon "7.0.0-beta.40" + debug "^3.0.1" + globals "^11.1.0" + invariant "^2.2.0" + lodash "^4.2.0" + +"@babel/types@7.0.0-beta.40", "@babel/types@^7.0.0-beta": + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.40.tgz#25c3d7aae14126abe05fcb098c65a66b6d6b8c14" + dependencies: + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^2.0.0" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +absolute-path@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/absolute-path/-/absolute-path-0.0.0.tgz#a78762fbdadfb5297be99b15d35a785b2f095bf7" + +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +ajv@^5.1.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + dependencies: + color-convert "^1.9.0" + +anymatch@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" + dependencies: + micromatch "^2.1.5" + normalize-path "^2.0.0" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-flatten@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +async@^2.4.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" + dependencies: + lodash "^4.14.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.2.1, aws4@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-code-frame@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-7.0.0-beta.3.tgz#1614a91b2ba0e3848559f410bbacd030726899c9" + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-core@^6.24.1, babel-core@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.0" + debug "^2.6.8" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.7" + slash "^1.0.0" + source-map "^0.5.6" + +babel-generator@^6.26.0: + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.7" + trim-right "^1.0.1" + +babel-helper-builder-react-jsx@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-7.0.0-beta.3.tgz#a3ff5d2427c4aec5af6b4376e7a5103c67508f8e" + dependencies: + babel-types "7.0.0-beta.3" + esutils "^2.0.0" + +babel-helper-builder-react-jsx@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + esutils "^2.0.2" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-define-map@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-function-name@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-7.0.0-beta.3.tgz#e86dd2eb2c09e06e392e79e203fc02427b24c871" + dependencies: + babel-helper-get-function-arity "7.0.0-beta.3" + babel-template "7.0.0-beta.3" + babel-traverse "7.0.0-beta.3" + babel-types "7.0.0-beta.3" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-7.0.0-beta.3.tgz#61a47709318a31bc2db872f4be9b4c8447198be8" + dependencies: + babel-types "7.0.0-beta.3" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-module-imports@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-helper-module-imports/-/babel-helper-module-imports-7.0.0-beta.3.tgz#e15764e3af9c8e11810c09f78f498a2bdc71585a" + dependencies: + babel-types "7.0.0-beta.3" + lodash "^4.2.0" + +babel-helper-module-transforms@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-helper-module-transforms/-/babel-helper-module-transforms-7.0.0-beta.3.tgz#42ccfa323e2d3aaaf0f743e66c2e7a292dc064f7" + dependencies: + babel-helper-module-imports "7.0.0-beta.3" + babel-helper-simple-access "7.0.0-beta.3" + babel-template "7.0.0-beta.3" + babel-types "7.0.0-beta.3" + lodash "^4.2.0" + +babel-helper-optimise-call-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-replace-supers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + dependencies: + babel-helper-optimise-call-expression "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-simple-access@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-helper-simple-access/-/babel-helper-simple-access-7.0.0-beta.3.tgz#dee94c31289fca79076f7ced2d751a06e430756c" + dependencies: + babel-template "7.0.0-beta.3" + babel-types "7.0.0-beta.3" + lodash "^4.2.0" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.5.0, babel-plugin-check-es2015-constants@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-external-helpers@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-external-helpers/-/babel-plugin-external-helpers-6.22.0.tgz#2285f48b02bd5dede85175caf8c62e86adccefa1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-react-transform@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-react-transform/-/babel-plugin-react-transform-3.0.0.tgz#402f25137b7bb66e9b54ead75557dfbc7ecaaa74" + dependencies: + lodash "^4.6.1" + +babel-plugin-syntax-async-functions@^6.5.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-class-properties@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-7.0.0-beta.3.tgz#84480d42dc9ec49f5f8e1e62fb435151cbbe11a3" + +babel-plugin-syntax-class-properties@^6.5.0, babel-plugin-syntax-class-properties@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" + +babel-plugin-syntax-dynamic-import@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" + +babel-plugin-syntax-flow@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-7.0.0-beta.3.tgz#b96eceea4ffa380b95ccb21e8c75a556bffce879" + +babel-plugin-syntax-flow@^6.18.0, babel-plugin-syntax-flow@^6.5.0, babel-plugin-syntax-flow@^6.8.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + +babel-plugin-syntax-jsx@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-7.0.0-beta.3.tgz#e45983c652a50d3647dfa4acd9db5567c0c2b701" + +babel-plugin-syntax-jsx@^6.5.0, babel-plugin-syntax-jsx@^6.8.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + +babel-plugin-syntax-object-rest-spread@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-7.0.0-beta.3.tgz#7f781c180899dafd88f132f69472397549be48e5" + +babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + +babel-plugin-syntax-trailing-function-commas@^6.5.0, babel-plugin-syntax-trailing-function-commas@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-class-properties@^6.5.0, babel-plugin-transform-class-properties@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" + dependencies: + babel-helper-function-name "^6.24.1" + babel-plugin-syntax-class-properties "^6.8.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-class-properties@^7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-7.0.0-beta.3.tgz#d7cf0e431512262499421d53582969503f24581a" + dependencies: + babel-helper-function-name "7.0.0-beta.3" + babel-plugin-syntax-class-properties "7.0.0-beta.3" + babel-template "7.0.0-beta.3" + +babel-plugin-transform-es2015-arrow-functions@^6.5.0, babel-plugin-transform-es2015-arrow-functions@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoped-functions@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoping@^6.5.0, babel-plugin-transform-es2015-block-scoping@^6.8.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" + dependencies: + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-plugin-transform-es2015-classes@^6.5.0, babel-plugin-transform-es2015-classes@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + dependencies: + babel-helper-define-map "^6.24.1" + babel-helper-function-name "^6.24.1" + babel-helper-optimise-call-expression "^6.24.1" + babel-helper-replace-supers "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-computed-properties@^6.5.0, babel-plugin-transform-es2015-computed-properties@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-destructuring@6.x, babel-plugin-transform-es2015-destructuring@^6.5.0, babel-plugin-transform-es2015-destructuring@^6.8.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-for-of@^6.5.0, babel-plugin-transform-es2015-for-of@^6.8.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@6.x, babel-plugin-transform-es2015-function-name@^6.5.0, babel-plugin-transform-es2015-function-name@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-literals@^6.5.0, babel-plugin-transform-es2015-literals@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-modules-commonjs@6.x, babel-plugin-transform-es2015-modules-commonjs@^6.5.0, babel-plugin-transform-es2015-modules-commonjs@^6.8.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-types "^6.26.0" + +babel-plugin-transform-es2015-modules-commonjs@^7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-7.0.0-beta.3.tgz#d53ae18f16e0b6a50ab65cdafbe3fa51a22f39b5" + dependencies: + babel-helper-module-transforms "7.0.0-beta.3" + babel-helper-simple-access "7.0.0-beta.3" + babel-types "7.0.0-beta.3" + +babel-plugin-transform-es2015-object-super@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + dependencies: + babel-helper-replace-supers "^6.24.1" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-parameters@6.x, babel-plugin-transform-es2015-parameters@^6.5.0, babel-plugin-transform-es2015-parameters@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-shorthand-properties@6.x, babel-plugin-transform-es2015-shorthand-properties@^6.5.0, babel-plugin-transform-es2015-shorthand-properties@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@6.x, babel-plugin-transform-es2015-spread@^6.5.0, babel-plugin-transform-es2015-spread@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@6.x: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-template-literals@^6.5.0, babel-plugin-transform-es2015-template-literals@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@6.x: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-es3-member-expression-literals@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es3-member-expression-literals/-/babel-plugin-transform-es3-member-expression-literals-6.22.0.tgz#733d3444f3ecc41bef8ed1a6a4e09657b8969ebb" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es3-property-literals@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es3-property-literals/-/babel-plugin-transform-es3-property-literals-6.22.0.tgz#b2078d5842e22abf40f73e8cde9cd3711abd5758" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-flow-strip-types@^6.5.0, babel-plugin-transform-flow-strip-types@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + dependencies: + babel-plugin-syntax-flow "^6.18.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-flow-strip-types@^7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-7.0.0-beta.3.tgz#40d7c030895dc9811e7dd09997ee96df6054eed6" + dependencies: + babel-plugin-syntax-flow "7.0.0-beta.3" + +babel-plugin-transform-object-assign@^6.5.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-assign/-/babel-plugin-transform-object-assign-6.22.0.tgz#f99d2f66f1a0b0d498e346c5359684740caa20ba" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-object-rest-spread@^6.5.0, babel-plugin-transform-object-rest-spread@^6.8.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + +babel-plugin-transform-object-rest-spread@^7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-7.0.0-beta.3.tgz#5c409f3cd70819dbb3382d2056971c5ebe01393a" + dependencies: + babel-plugin-syntax-object-rest-spread "7.0.0-beta.3" + +babel-plugin-transform-react-display-name@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-7.0.0-beta.3.tgz#9bec4984d868bf37e5184ed22bb66c44db61d612" + +babel-plugin-transform-react-display-name@^6.5.0, babel-plugin-transform-react-display-name@^6.8.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-self@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-7.0.0-beta.3.tgz#33bc0b66b090661409d308e6020d6835f8cfe611" + dependencies: + babel-plugin-syntax-jsx "7.0.0-beta.3" + +babel-plugin-transform-react-jsx-source@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-7.0.0-beta.3.tgz#e5b1015a8eb107e51d64aace7677b2621c87f2ed" + dependencies: + babel-plugin-syntax-jsx "7.0.0-beta.3" + +babel-plugin-transform-react-jsx-source@^6.5.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-7.0.0-beta.3.tgz#61af93962a3e6938ae8aeca10bb544f8b231ee7b" + dependencies: + babel-helper-builder-react-jsx "7.0.0-beta.3" + babel-plugin-syntax-jsx "7.0.0-beta.3" + +babel-plugin-transform-react-jsx@^6.5.0, babel-plugin-transform-react-jsx@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" + dependencies: + babel-helper-builder-react-jsx "^6.24.1" + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.5.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" + dependencies: + regenerator-transform "^0.10.0" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-preset-es2015-node@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-preset-es2015-node/-/babel-preset-es2015-node-6.1.1.tgz#60b23157024b0cfebf3a63554cb05ee035b4e55f" + dependencies: + babel-plugin-transform-es2015-destructuring "6.x" + babel-plugin-transform-es2015-function-name "6.x" + babel-plugin-transform-es2015-modules-commonjs "6.x" + babel-plugin-transform-es2015-parameters "6.x" + babel-plugin-transform-es2015-shorthand-properties "6.x" + babel-plugin-transform-es2015-spread "6.x" + babel-plugin-transform-es2015-sticky-regex "6.x" + babel-plugin-transform-es2015-unicode-regex "6.x" + semver "5.x" + +babel-preset-fbjs@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/babel-preset-fbjs/-/babel-preset-fbjs-2.1.4.tgz#22f358e6654073acf61e47a052a777d7bccf03af" + dependencies: + babel-plugin-check-es2015-constants "^6.8.0" + babel-plugin-syntax-class-properties "^6.8.0" + babel-plugin-syntax-flow "^6.8.0" + babel-plugin-syntax-jsx "^6.8.0" + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-plugin-syntax-trailing-function-commas "^6.8.0" + babel-plugin-transform-class-properties "^6.8.0" + babel-plugin-transform-es2015-arrow-functions "^6.8.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.8.0" + babel-plugin-transform-es2015-block-scoping "^6.8.0" + babel-plugin-transform-es2015-classes "^6.8.0" + babel-plugin-transform-es2015-computed-properties "^6.8.0" + babel-plugin-transform-es2015-destructuring "^6.8.0" + babel-plugin-transform-es2015-for-of "^6.8.0" + babel-plugin-transform-es2015-function-name "^6.8.0" + babel-plugin-transform-es2015-literals "^6.8.0" + babel-plugin-transform-es2015-modules-commonjs "^6.8.0" + babel-plugin-transform-es2015-object-super "^6.8.0" + babel-plugin-transform-es2015-parameters "^6.8.0" + babel-plugin-transform-es2015-shorthand-properties "^6.8.0" + babel-plugin-transform-es2015-spread "^6.8.0" + babel-plugin-transform-es2015-template-literals "^6.8.0" + babel-plugin-transform-es3-member-expression-literals "^6.8.0" + babel-plugin-transform-es3-property-literals "^6.8.0" + babel-plugin-transform-flow-strip-types "^6.8.0" + babel-plugin-transform-object-rest-spread "^6.8.0" + babel-plugin-transform-react-display-name "^6.8.0" + babel-plugin-transform-react-jsx "^6.8.0" + +babel-preset-react-native@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/babel-preset-react-native/-/babel-preset-react-native-4.0.0.tgz#3df80dd33a453888cdd33bdb87224d17a5d73959" + dependencies: + babel-plugin-check-es2015-constants "^6.5.0" + babel-plugin-react-transform "^3.0.0" + babel-plugin-syntax-async-functions "^6.5.0" + babel-plugin-syntax-class-properties "^6.5.0" + babel-plugin-syntax-dynamic-import "^6.18.0" + babel-plugin-syntax-flow "^6.5.0" + babel-plugin-syntax-jsx "^6.5.0" + babel-plugin-syntax-trailing-function-commas "^6.5.0" + babel-plugin-transform-class-properties "^6.5.0" + babel-plugin-transform-es2015-arrow-functions "^6.5.0" + babel-plugin-transform-es2015-block-scoping "^6.5.0" + babel-plugin-transform-es2015-classes "^6.5.0" + babel-plugin-transform-es2015-computed-properties "^6.5.0" + babel-plugin-transform-es2015-destructuring "^6.5.0" + babel-plugin-transform-es2015-for-of "^6.5.0" + babel-plugin-transform-es2015-function-name "^6.5.0" + babel-plugin-transform-es2015-literals "^6.5.0" + babel-plugin-transform-es2015-modules-commonjs "^6.5.0" + babel-plugin-transform-es2015-parameters "^6.5.0" + babel-plugin-transform-es2015-shorthand-properties "^6.5.0" + babel-plugin-transform-es2015-spread "^6.5.0" + babel-plugin-transform-es2015-template-literals "^6.5.0" + babel-plugin-transform-flow-strip-types "^6.5.0" + babel-plugin-transform-object-assign "^6.5.0" + babel-plugin-transform-object-rest-spread "^6.5.0" + babel-plugin-transform-react-display-name "^6.5.0" + babel-plugin-transform-react-jsx "^6.5.0" + babel-plugin-transform-react-jsx-source "^6.5.0" + babel-plugin-transform-regenerator "^6.5.0" + babel-template "^6.24.1" + react-transform-hmr "^1.0.4" + +babel-preset-react@^7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-7.0.0-beta.3.tgz#3a6453e0a5e6156d9528590b629a70ecd9d50226" + dependencies: + babel-plugin-syntax-jsx "7.0.0-beta.3" + babel-plugin-transform-react-display-name "7.0.0-beta.3" + babel-plugin-transform-react-jsx "7.0.0-beta.3" + babel-plugin-transform-react-jsx-self "7.0.0-beta.3" + babel-plugin-transform-react-jsx-source "7.0.0-beta.3" + +babel-register@^6.24.1, babel-register@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + dependencies: + babel-core "^6.26.0" + babel-runtime "^6.26.0" + core-js "^2.5.0" + home-or-tmp "^2.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + source-map-support "^0.4.15" + +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-7.0.0-beta.3.tgz#ebb877b6070ce9912b0d0c22fcad3372165913a8" + dependencies: + babel-code-frame "7.0.0-beta.3" + babel-traverse "7.0.0-beta.3" + babel-types "7.0.0-beta.3" + babylon "7.0.0-beta.27" + lodash "^4.2.0" + +babel-template@^6.24.1, babel-template@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-7.0.0-beta.3.tgz#3cf0a45d53d934d85275d8770775d7944fc7c199" + dependencies: + babel-code-frame "7.0.0-beta.3" + babel-helper-function-name "7.0.0-beta.3" + babel-types "7.0.0-beta.3" + babylon "7.0.0-beta.27" + debug "^3.0.1" + globals "^10.0.0" + invariant "^2.2.0" + lodash "^4.2.0" + +babel-traverse@^6.24.1, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@7.0.0-beta.3: + version "7.0.0-beta.3" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-7.0.0-beta.3.tgz#cd927ca70e0ae8ab05f4aab83778cfb3e6eb20b4" + dependencies: + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^2.0.0" + +babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@7.0.0-beta.27: + version "7.0.0-beta.27" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.27.tgz#b6edd30ef30619e2f630eb52585fdda84e6542cd" + +babylon@7.0.0-beta.40, babylon@^7.0.0-beta, babylon@^7.0.0-beta.40: + version "7.0.0-beta.40" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.40.tgz#91fc8cd56d5eb98b28e6fde41045f2957779940a" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +boom@4.x.x: + version "4.3.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" + dependencies: + hoek "4.x.x" + +boom@5.x.x: + version "5.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" + dependencies: + hoek "4.x.x" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +bser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + dependencies: + node-int64 "^0.4.0" + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65" + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +color-convert@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + dependencies: + delayed-stream "~1.0.0" + +commander@~2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.6.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.1.tgz#261b8f518301f1d834e36342b9fea095d2620a26" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +connect@^3.6.5: + version "3.6.6" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.6.tgz#09eff6c55af7236e137135a72574858b6786f524" + dependencies: + debug "2.6.9" + finalhandler "1.1.0" + parseurl "~1.3.2" + utils-merge "1.0.1" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +convert-source-map@^1.1.0, convert-source-map@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + +core-js@^2.2.2, core-js@^2.4.0, core-js@^2.5.0: + version "2.5.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +cryptiles@3.x.x: + version "3.1.2" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" + dependencies: + boom "5.x.x" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +debug@2.6.9, debug@^2.2.0, debug@^2.6.8: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +debug@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +decamelize@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +denodeify@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + +detect-newline@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + +dom-walk@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +encodeurl@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +esutils@^2.0.0, esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +eventemitter3@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.0.1.tgz#4ce66c3fc5b5a6b9f2245e359e1938f1ab10f960" + +exec-sh@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + dependencies: + merge "^1.1.3" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +extend@~3.0.0, extend@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + +fb-watchman@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + dependencies: + bser "^2.0.0" + +fbjs@^0.8.14: + version "0.8.16" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +finalhandler@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" + dependencies: + debug "2.6.9" + encodeurl "~1.0.1" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.3.1" + unpipe "~1.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +form-data@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + +fs-extra@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.39" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-caller-file@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob@^7.0.5: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" + dependencies: + min-document "^2.19.0" + process "~0.5.1" + +globals@^10.0.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-10.4.0.tgz#5c477388b128a9e4c5c5d01c7a2aca68c68b2da7" + +globals@^11.1.0: + version "11.3.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.3.0.tgz#e04fdb7b9796d8adac9c8f64c14837b2313378b0" + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +hawk@3.1.3, hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +hawk@~6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" + dependencies: + boom "4.x.x" + cryptiles "3.x.x" + hoek "4.x.x" + sntp "2.x.x" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +hoek@4.x.x: + version "4.2.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +hosted-git-info@^2.1.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@~0.4.13: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +image-size@^0.6.0: + version "0.6.2" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.6.2.tgz#8ee316d4298b028b965091b673d5f1537adee5b4" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + +invariant@^2.2.0, invariant@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.3.tgz#1a827dfde7dcbd7c323f0ca826be8fa7c5e9d688" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-stream@^1.0.1, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +jest-docblock@22.4.0, jest-docblock@^22.4.0: + version "22.4.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.4.0.tgz#dbf1877e2550070cfc4d9b07a55775a0483159b8" + dependencies: + detect-newline "^2.1.0" + +jest-haste-map@22.4.2: + version "22.4.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-22.4.2.tgz#a90178e66146d4378bb076345a949071f3b015b4" + dependencies: + fb-watchman "^2.0.0" + graceful-fs "^4.1.11" + jest-docblock "^22.4.0" + jest-serializer "^22.4.0" + jest-worker "^22.2.2" + micromatch "^2.3.11" + sane "^2.0.0" + +jest-serializer@^22.4.0: + version "22.4.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-22.4.0.tgz#b5d145b98c4b0d2c20ab686609adbb81fe23b566" + +jest-worker@22.2.2, jest-worker@^22.2.2: + version "22.2.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-22.2.2.tgz#c1f5dc39976884b81f68ec50cb8532b2cbab3390" + dependencies: + merge-stream "^1.0.1" + +js-tokens@^3.0.0, js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json5@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.4.0.tgz#054352e4c4c80c86c0923877d449de176a732c8d" + +json5@^0.5.0, json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + optionalDependencies: + graceful-fs "^4.1.9" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +left-pad@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.2.0.tgz#d30a73c6b8201d8f7d8e7956ba9616087a68e0ee" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + +lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.6.1: + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + dependencies: + tmpl "1.0.x" + +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + +merge-stream@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" + dependencies: + readable-stream "^2.0.1" + +merge@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + +metro-babylon7@0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/metro-babylon7/-/metro-babylon7-0.28.0.tgz#cf9701ffdc1992d1562b4cb667d9692164950df4" + dependencies: + babylon "^7.0.0-beta" + +metro-cache@0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.28.0.tgz#c5164a361985fc0294059fccdf4ea824e3173c1d" + dependencies: + jest-serializer "^22.4.0" + mkdirp "^0.5.1" + +metro-core@0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.28.0.tgz#e1ced4cf07ca8fb5196a6e5ca853b5d893f06038" + dependencies: + lodash.throttle "^4.1.1" + +metro-minify-uglify@0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.28.0.tgz#c9aecb8e893430d2fd58e00cf799c00b99dc0f79" + dependencies: + uglify-es "^3.1.9" + +metro-resolver@0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.28.0.tgz#813802d60fc762772927c81d02e01c7eec84bad8" + dependencies: + absolute-path "^0.0.0" + +metro-source-map@0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.28.0.tgz#ec8c3161d8516ad3c4e7149f2c3d4802f4fd6fa2" + dependencies: + source-map "^0.5.6" + +metro@^0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/metro/-/metro-0.28.0.tgz#22999c96c3129682a76acd4e1f2adc17f7d77cac" + dependencies: + "@babel/core" "^7.0.0-beta" + "@babel/generator" "^7.0.0-beta" + "@babel/helper-remap-async-to-generator" "^7.0.0-beta" + "@babel/plugin-check-constants" "^7.0.0-beta" + "@babel/plugin-external-helpers" "^7.0.0-beta" + "@babel/plugin-proposal-class-properties" "^7.0.0-beta" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0-beta" + "@babel/plugin-syntax-dynamic-import" "^7.0.0-beta" + "@babel/plugin-transform-arrow-functions" "^7.0.0-beta" + "@babel/plugin-transform-block-scoping" "^7.0.0-beta" + "@babel/plugin-transform-classes" "^7.0.0-beta" + "@babel/plugin-transform-computed-properties" "^7.0.0-beta" + "@babel/plugin-transform-destructuring" "^7.0.0-beta" + "@babel/plugin-transform-flow-strip-types" "^7.0.0-beta" + "@babel/plugin-transform-for-of" "^7.0.0-beta" + "@babel/plugin-transform-function-name" "^7.0.0-beta" + "@babel/plugin-transform-literals" "^7.0.0-beta" + "@babel/plugin-transform-modules-commonjs" "^7.0.0-beta" + "@babel/plugin-transform-object-assign" "^7.0.0-beta" + "@babel/plugin-transform-parameters" "^7.0.0-beta" + "@babel/plugin-transform-react-display-name" "^7.0.0-beta" + "@babel/plugin-transform-react-jsx" "^7.0.0-beta" + "@babel/plugin-transform-react-jsx-source" "^7.0.0-beta" + "@babel/plugin-transform-regenerator" "^7.0.0-beta" + "@babel/plugin-transform-shorthand-properties" "^7.0.0-beta" + "@babel/plugin-transform-spread" "^7.0.0-beta" + "@babel/plugin-transform-template-literals" "^7.0.0-beta" + "@babel/template" "^7.0.0-beta" + "@babel/traverse" "^7.0.0-beta" + "@babel/types" "^7.0.0-beta" + absolute-path "^0.0.0" + async "^2.4.0" + babel-core "^6.24.1" + babel-generator "^6.26.0" + babel-plugin-external-helpers "^6.22.0" + babel-preset-es2015-node "^6.1.1" + babel-preset-fbjs "^2.1.4" + babel-preset-react-native "^4.0.0" + babel-register "^6.24.1" + babylon "^6.18.0" + chalk "^1.1.1" + concat-stream "^1.6.0" + connect "^3.6.5" + core-js "^2.2.2" + debug "^2.2.0" + denodeify "^1.2.1" + eventemitter3 "^3.0.0" + fbjs "^0.8.14" + fs-extra "^1.0.0" + graceful-fs "^4.1.3" + image-size "^0.6.0" + jest-docblock "22.4.0" + jest-haste-map "22.4.2" + jest-worker "22.2.2" + json-stable-stringify "^1.0.1" + json5 "^0.4.0" + left-pad "^1.1.3" + lodash.throttle "^4.1.1" + merge-stream "^1.0.1" + metro-babylon7 "0.28.0" + metro-cache "0.28.0" + metro-core "0.28.0" + metro-minify-uglify "0.28.0" + metro-resolver "0.28.0" + metro-source-map "0.28.0" + mime-types "2.1.11" + mkdirp "^0.5.1" + request "^2.79.0" + rimraf "^2.5.4" + serialize-error "^2.1.0" + source-map "^0.5.6" + temp "0.8.3" + throat "^4.1.0" + wordwrap "^1.0.0" + write-file-atomic "^1.2.0" + ws "^1.1.0" + xpipe "^1.0.5" + yargs "^9.0.0" + +micromatch@^2.1.5, micromatch@^2.3.11: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +mime-db@~1.23.0: + version "1.23.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.23.0.tgz#a31b4070adaea27d732ea333740a64d0ec9a6659" + +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + +mime-types@2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.11.tgz#c259c471bda808a85d6cd193b430a5fae4473b3c" + dependencies: + mime-db "~1.23.0" + +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + dependencies: + mime-db "~1.33.0" + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + dependencies: + dom-walk "^0.1.0" + +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.1, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +"mkdirp@>=0.5 0", mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +nan@^2.3.0: + version "2.9.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866" + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + +node-pre-gyp@^0.6.39: + version "0.6.39" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" + dependencies: + detect-libc "^1.0.2" + hawk "3.1.3" + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.0.0, normalize-path@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +oauth-sign@~0.8.1, oauth-sign@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +options@>=0.0.5: + version "0.0.6" + resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + dependencies: + p-try "^1.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +private@^0.1.6, private@^0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + +process@~0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +qs@~6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +rc@^1.1.7: + version "1.2.5" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-deep-force-update@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.1.tgz#bcd31478027b64b3339f108921ab520b4313dc2c" + +react-proxy@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-1.1.8.tgz#9dbfd9d927528c3aa9f444e4558c37830ab8c26a" + dependencies: + lodash "^4.6.1" + react-deep-force-update "^1.0.0" + +react-transform-hmr@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/react-transform-hmr/-/react-transform-hmr-1.0.4.tgz#e1a40bd0aaefc72e8dfd7a7cda09af85066397bb" + dependencies: + global "^4.3.0" + react-proxy "^1.1.7" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +readable-stream@^2.0.1, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2: + version "2.3.5" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +regenerate@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + +regenerator-transform@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" + dependencies: + babel-runtime "^6.18.0" + babel-types "^6.19.0" + private "^0.1.6" + +regenerator-transform@^0.12.3: + version "0.12.3" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.12.3.tgz#459adfb64f6a27164ab991b7873f45ab969eca8b" + dependencies: + private "^0.1.6" + +regex-cache@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + dependencies: + is-equal-shallow "^0.1.3" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +request@^2.79.0: + version "2.83.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + +resolve@^1.3.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + dependencies: + path-parse "^1.0.5" + +rimraf@2, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + dependencies: + glob "^7.0.5" + +rimraf@~2.2.6: + version "2.2.8" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" + +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +sane@^2.0.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/sane/-/sane-2.4.1.tgz#29f991208cf28636720efdc584293e7fd66663a5" + dependencies: + anymatch "^1.3.0" + exec-sh "^0.2.0" + fb-watchman "^2.0.0" + minimatch "^3.0.2" + minimist "^1.1.1" + walker "~1.0.5" + watch "~0.18.0" + optionalDependencies: + fsevents "^1.1.1" + +"semver@2 || 3 || 4 || 5", semver@5.x, semver@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + +serialize-error@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +slide@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +sntp@2.x.x: + version "2.1.0" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" + dependencies: + hoek "4.x.x" + +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + dependencies: + source-map "^0.5.6" + +source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + +spdx-correct@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +statuses@~1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +stringstream@~0.0.4, stringstream@~0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0" + dependencies: + has-flag "^3.0.0" + +tar-pack@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +temp@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" + dependencies: + os-tmpdir "^1.0.0" + rimraf "~2.2.6" + +throat@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + +tough-cookie@~2.3.0, tough-cookie@~2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + dependencies: + punycode "^1.4.1" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +ua-parser-js@^0.7.9: + version "0.7.17" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" + +uglify-es@^3.1.9: + version "3.3.9" + resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" + dependencies: + commander "~2.13.0" + source-map "~0.6.1" + +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + +ultron@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + +uuid@^3.0.0, uuid@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" + +validate-npm-package-license@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + dependencies: + makeerror "1.0.x" + +watch@~0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" + dependencies: + exec-sh "^0.2.0" + minimist "^1.2.0" + +whatwg-fetch@>=0.10.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + +which@^1.2.9: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-atomic@^1.2.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + slide "^1.1.5" + +ws@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51" + dependencies: + options ">=0.0.5" + ultron "1.0.x" + +xpipe@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/xpipe/-/xpipe-1.0.5.tgz#8dd8bf45fc3f7f55f0e054b878f43a62614dafdf" + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + dependencies: + camelcase "^4.1.0" + +yargs@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 000000000..aeedda9bf --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,11 @@ +node_modules +.DS_Store +lib/core/metadata.js +lib/core/MetadataBlog.js +website/translated_docs +website/build/ +website/yarn.lock +website/node_modules + +website/i18n/* +!website/i18n/en.json diff --git a/website/blog/2016-03-11-blog-post.md b/website/blog/2016-03-11-blog-post.md new file mode 100755 index 000000000..cf2ba2960 --- /dev/null +++ b/website/blog/2016-03-11-blog-post.md @@ -0,0 +1,18 @@ +--- +title: Blog Title +author: Blog Author +authorURL: http://twitter.com/ +authorFBID: 100002976521003 +--- + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien. + + + +Mauris vestibulum ullamcorper nibh, ut semper purus pulvinar ut. Donec volutpat orci sit amet mauris malesuada, non pulvinar augue aliquam. Vestibulum ultricies at urna ut suscipit. Morbi iaculis, erat at imperdiet semper, ipsum nulla sodales erat, eget tincidunt justo dui quis justo. Pellentesque dictum bibendum diam at aliquet. Sed pulvinar, dolor quis finibus ornare, eros odio facilisis erat, eu rhoncus nunc dui sed ex. Nunc gravida dui massa, sed ornare arcu tincidunt sit amet. Maecenas efficitur sapien neque, a laoreet libero feugiat ut. + +Nulla facilisi. Maecenas sodales nec purus eget posuere. Sed sapien quam, pretium a risus in, porttitor dapibus erat. Sed sit amet fringilla ipsum, eget iaculis augue. Integer sollicitudin tortor quis ultricies aliquam. Suspendisse fringilla nunc in tellus cursus, at placerat tellus scelerisque. Sed tempus elit a sollicitudin rhoncus. Nulla facilisi. Morbi nec dolor dolor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras et aliquet lectus. Pellentesque sit amet eros nisi. Quisque ac sapien in sapien congue accumsan. Nullam in posuere ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin lacinia leo a nibh fringilla pharetra. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin venenatis lectus dui, vel ultrices ante bibendum hendrerit. Aenean egestas feugiat dui id hendrerit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur in tellus laoreet, eleifend nunc id, viverra leo. Proin vulputate non dolor vel vulputate. Curabitur pretium lobortis felis, sit amet finibus lorem suscipit ut. Sed non mollis risus. Duis sagittis, mi in euismod tincidunt, nunc mauris vestibulum urna, at euismod est elit quis erat. Phasellus accumsan vitae neque eu placerat. In elementum arcu nec tellus imperdiet, eget maximus nulla sodales. Curabitur eu sapien eget nisl sodales fermentum. + +Phasellus pulvinar ex id commodo imperdiet. Praesent odio nibh, sollicitudin sit amet faucibus id, placerat at metus. Donec vitae eros vitae tortor hendrerit finibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque vitae purus dolor. Duis suscipit ac nulla et finibus. Phasellus ac sem sed dui dictum gravida. Phasellus eleifend vestibulum facilisis. Integer pharetra nec enim vitae mattis. Duis auctor, lectus quis condimentum bibendum, nunc dolor aliquam massa, id bibendum orci velit quis magna. Ut volutpat nulla nunc, sed interdum magna condimentum non. Sed urna metus, scelerisque vitae consectetur a, feugiat quis magna. Donec dignissim ornare nisl, eget tempor risus malesuada quis. diff --git a/website/blog/2017-04-10-blog-post-two.md b/website/blog/2017-04-10-blog-post-two.md new file mode 100644 index 000000000..3ab4637bd --- /dev/null +++ b/website/blog/2017-04-10-blog-post-two.md @@ -0,0 +1,18 @@ +--- +title: New Blog Post +author: Blog Author +authorURL: http://twitter.com/ +authorFBID: 100002976521003 +--- + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien. + + + +Mauris vestibulum ullamcorper nibh, ut semper purus pulvinar ut. Donec volutpat orci sit amet mauris malesuada, non pulvinar augue aliquam. Vestibulum ultricies at urna ut suscipit. Morbi iaculis, erat at imperdiet semper, ipsum nulla sodales erat, eget tincidunt justo dui quis justo. Pellentesque dictum bibendum diam at aliquet. Sed pulvinar, dolor quis finibus ornare, eros odio facilisis erat, eu rhoncus nunc dui sed ex. Nunc gravida dui massa, sed ornare arcu tincidunt sit amet. Maecenas efficitur sapien neque, a laoreet libero feugiat ut. + +Nulla facilisi. Maecenas sodales nec purus eget posuere. Sed sapien quam, pretium a risus in, porttitor dapibus erat. Sed sit amet fringilla ipsum, eget iaculis augue. Integer sollicitudin tortor quis ultricies aliquam. Suspendisse fringilla nunc in tellus cursus, at placerat tellus scelerisque. Sed tempus elit a sollicitudin rhoncus. Nulla facilisi. Morbi nec dolor dolor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras et aliquet lectus. Pellentesque sit amet eros nisi. Quisque ac sapien in sapien congue accumsan. Nullam in posuere ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin lacinia leo a nibh fringilla pharetra. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin venenatis lectus dui, vel ultrices ante bibendum hendrerit. Aenean egestas feugiat dui id hendrerit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur in tellus laoreet, eleifend nunc id, viverra leo. Proin vulputate non dolor vel vulputate. Curabitur pretium lobortis felis, sit amet finibus lorem suscipit ut. Sed non mollis risus. Duis sagittis, mi in euismod tincidunt, nunc mauris vestibulum urna, at euismod est elit quis erat. Phasellus accumsan vitae neque eu placerat. In elementum arcu nec tellus imperdiet, eget maximus nulla sodales. Curabitur eu sapien eget nisl sodales fermentum. + +Phasellus pulvinar ex id commodo imperdiet. Praesent odio nibh, sollicitudin sit amet faucibus id, placerat at metus. Donec vitae eros vitae tortor hendrerit finibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque vitae purus dolor. Duis suscipit ac nulla et finibus. Phasellus ac sem sed dui dictum gravida. Phasellus eleifend vestibulum facilisis. Integer pharetra nec enim vitae mattis. Duis auctor, lectus quis condimentum bibendum, nunc dolor aliquam massa, id bibendum orci velit quis magna. Ut volutpat nulla nunc, sed interdum magna condimentum non. Sed urna metus, scelerisque vitae consectetur a, feugiat quis magna. Donec dignissim ornare nisl, eget tempor risus malesuada quis. diff --git a/website/blog/2017-09-25-testing-rss.md b/website/blog/2017-09-25-testing-rss.md new file mode 100644 index 000000000..b7ff8129c --- /dev/null +++ b/website/blog/2017-09-25-testing-rss.md @@ -0,0 +1,11 @@ +--- +title: Adding RSS Support - RSS Truncation Test +author: Eric Nakagawa +authorURL: http://twitter.com/ericnakagawa +authorFBID: 661277173 +--- +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + +This should be truncated. + +This line should never render in XML. diff --git a/website/blog/2017-09-26-adding-rss.md b/website/blog/2017-09-26-adding-rss.md new file mode 100644 index 000000000..eeb4f0477 --- /dev/null +++ b/website/blog/2017-09-26-adding-rss.md @@ -0,0 +1,10 @@ +--- +title: Adding RSS Support +author: Eric Nakagawa +authorURL: http://twitter.com/ericnakagawa +authorFBID: 661277173 +--- + +This is a test post. + +A whole bunch of other information. diff --git a/website/blog/2017-10-24-new-version-1.0.0.md b/website/blog/2017-10-24-new-version-1.0.0.md new file mode 100644 index 000000000..60761c02d --- /dev/null +++ b/website/blog/2017-10-24-new-version-1.0.0.md @@ -0,0 +1,8 @@ +--- +title: New Version 1.0.0 +author: Eric Nakagawa +authorURL: http://twitter.com/ericnakagawa +authorFBID: 661277173 +--- + +This blog post will test file name parsing issues when periods are present. diff --git a/website/core/Footer.js b/website/core/Footer.js new file mode 100644 index 000000000..1b187d243 --- /dev/null +++ b/website/core/Footer.js @@ -0,0 +1,82 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +const React = require('react'); + +class Footer extends React.Component { + docUrl(doc, language) { + const baseUrl = this.props.config.baseUrl; + return baseUrl + 'docs/' + (language ? language + '/' : '') + doc; + } + + pageUrl(doc, language) { + const baseUrl = this.props.config.baseUrl; + return baseUrl + (language ? language + '/' : '') + doc; + } + + render() { + return ( + + ); + } +} + +module.exports = Footer; diff --git a/website/i18n/en.json b/website/i18n/en.json new file mode 100644 index 000000000..b4c0cff11 --- /dev/null +++ b/website/i18n/en.json @@ -0,0 +1,46 @@ +{ + "_comment": "This file is auto-generated by write-translations.js", + "localized-strings": { + "next": "Next", + "previous": "Previous", + "tagline": "Extensible mobile app debugging", + "communicating": "Device Communication", + "Device Communication": "Device Communication", + "create-plugin": "Mobile Setup", + "Mobile Setup": "Mobile Setup", + "create-table-plugin": "Create Table Plugin", + "Create Table Plugin": "Create Table Plugin", + "error-handling": "Error Handling", + "Error Handling": "Error Handling", + "getting-started": "Getting Started", + "Getting Started": "Getting Started", + "js-setup": "JavaScript Setup", + "JavaScript Setup": "JavaScript Setup", + "layout-plugin": "Layout Inspector", + "logs-plugin": "Logs", + "network-plugin": "Network", + "send-data": "Sending Data to Plugins", + "Send Data": "Send Data", + "stetho": "Stetho Guidance", + "Stetho Guidance": "Stetho Guidance", + "styling-components": "Styling Components", + "Styling Components": "Styling Components", + "testing": "Testing", + "Testing": "Testing", + "ui-components": "UI Components", + "UI Components": "UI Components", + "understand": "Understanding Sonar", + "Understanding Sonar": "Understanding Sonar", + "Docs": "Docs", + "GitHub": "GitHub", + "Using Sonar": "Using Sonar", + "Built-in Plugins": "Built-in Plugins", + "Plugins: Desktop part": "Plugins: Desktop part", + "Plugins: Mobile part": "Plugins: Mobile part" + }, + "pages-strings": { + "Help Translate|recruit community translators for your project": "Help Translate", + "Edit this Doc|recruitment message asking to edit the doc source": "Edit", + "Translate this Doc|recruitment message asking to translate the docs": "Translate" + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 000000000..f5822c33b --- /dev/null +++ b/website/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "examples": "docusaurus-examples", + "start": "docusaurus-start", + "build": "docusaurus-build", + "publish-gh-pages": "docusaurus-publish", + "write-translations": "docusaurus-write-translations", + "version": "docusaurus-version", + "rename-version": "docusaurus-rename-version" + }, + "devDependencies": { + "docusaurus": "^1.0.9" + } +} diff --git a/website/pages/docs/en/index.js b/website/pages/docs/en/index.js new file mode 100644 index 000000000..f8ce11168 --- /dev/null +++ b/website/pages/docs/en/index.js @@ -0,0 +1,22 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +const React = require('react'); + +class Docs extends React.Component { + render() { + return ( +