Repository: steelbrain/flow-ide Branch: master Commit: 3ec590624211 Files: 36 Total size: 176.4 KB Directory structure: gitextract_6xu3tfy1/ ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .flowconfig ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── decls/ │ ├── atom.js │ ├── hyperclick.js │ ├── jasmine-atom.js │ ├── jasmine.js │ ├── linter.js │ └── outline.js ├── lib/ │ ├── coverage-view.js │ ├── helpers.js │ ├── index.js │ ├── language-client.js │ ├── linter/ │ │ ├── v1/ │ │ │ ├── index.js │ │ │ ├── pretty.js │ │ │ └── types.js │ │ └── v2/ │ │ ├── index.js │ │ └── types.js │ ├── main.js │ ├── outline/ │ │ ├── index.js │ │ ├── parse.js │ │ ├── text.js │ │ └── types.js │ └── types.js ├── package.json ├── spec/ │ ├── linter/ │ │ └── v2/ │ │ └── index-spec.js │ └── outline/ │ ├── parse-sample-ast.js │ └── parse-spec.js ├── styles/ │ └── flow-ide.less └── vendor/ └── .flowconfig ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ ["env", { "targets": { "node": "current" } }] ], "sourceMap": "inline", "plugins": [ "transform-class-properties", "transform-object-rest-spread", "transform-flow-strip-types" ] } ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .eslintignore ================================================ decls ================================================ FILE: .eslintrc.json ================================================ { "extends": "steelbrain", "plugins": [ "flowtype" ], "rules": { "import/prefer-default-export": "off", "no-duplicate-imports": "off", "flowtype/define-flow-type": "error", "flowtype/use-flow-type": "error" }, "globals": { "waitsForPromise": true } } ================================================ FILE: .flowconfig ================================================ [ignore] [include] [libs] decls [options] module.system=node include_warnings=true suppress_comment= \\(.\\|\n\\)*\\$FlowIgnore [lints] all=warn # turned off because there are lot of types missing atm... unclear-type=off untyped-import=off ================================================ FILE: .gitignore ================================================ .idea node_modules *.log .DS_Store ================================================ FILE: CHANGELOG.md ================================================ #### 1.13.0 - Use the official [language server protocol](https://microsoft.github.io/language-server-protocol/) to communicate with `flow` #### 1.12.1 - Support for Tree-Sitter Flow grammar #### 1.12.0 - Add config to stop flow server when flow-ide is stopped - Fix autocomplete selector #### 1.11.1 - Support for Tree-Sitter Flow grammar #### 1.11.0 - Support new errors messages since [flow 0.66.0](https://github.com/facebook/flow/releases/tag/v0.66.0) - Fix goto definition (via hyperclick) if the cursor is at the end of the word - Fix outline for rest/spread operators #### 1.10.0 - [Outline](https://github.com/facebook-atom/atom-ide-ui/blob/2767934/docs/outline-view.md) of classes, functions, types and variables (using [atom-ide-ui](https://github.com/facebook-atom/atom-ide-ui/)) - Minor bug fixes regarding markdown rendering of flow errors #### 1.9.0 - Support [datatip](https://github.com/facebook-atom/atom-ide-ui/blob/508ecfd6aa8121ae2e423b2becbe22e34cf191fb/docs/datatips.md) from [atom-ide-ui](https://github.com/facebook-atom/atom-ide-ui/) - Fix wrong coverage (#112) - Fix minor markdown rendering issue #### 1.8.1 - Hide coverage view instead of emptying to fix extra padding #### 1.8.0 - Add linting in flow support - Show a restart notification after `hyperclickPriority` is updated #### 1.7.0 - Change search order of `flow` executable (_Executable Path_ setting > `node_modules/.bin/flow` > global `flow`) #### 1.6.0 - Add "jump to definition" via `hyperclick` - Show the complete flow error message as error description #### 1.5.0 - Add option to show uncovered code - Fix process exited with non-zero status code for autocomplete - Fix invalid behavior of `onlyIfAppropriate` config #### 1.4.2 - Limit concurrent spawned processes #### 1.4.1 - Increase exec timeout to 60 seconds #### 1.4.0 - Upgrade for linter v2 support #### 1.3.0 - Terminate flow servers on deactivate - Fix path.dirname deprecation by ignoring autocomplete requests on files not yet saved #### 1.2.4 - Had to bump version because of some issues in deployment (network) #### 1.2.2 - Handle coverage count zero (#52) #### 1.2.1 - Fix flow type checking (#49) #### 1.2 - Add Flow coverage view #### 1.1.10 - Provide a default .flowconfig file if onlyIfAppropriate is enabled and a .flowconfig file is not found already #### 1.1.9 - Fix autocompletion for properties (#33) - Remove types from function params (#8) #### 1.1.8 - APM was having hiccups back then so didn't publish properly #### 1.1.7 - Fix a bug in last release #### 1.1.6 - Workaround a Atom's builtin babel bug #### 1.1.5 - Just another patch to catch more flow errors gracefully #### 1.1.4 - Handle flow crashes/respawns gracefully #### 1.1.3 - Show entire linter error message #### 1.1.1-1.1.2 - Fix a bug introduced in 1.1.0 where autocomplete wouldn't work - Fix a reference to undefined variable in case of error ( Fixes #12 ) #### 1.1.0 - Add support for locally installed flow bins - Bump `atom-linter` to v5 #### 1.0.6 - Show a correct type for Objects in autocomplete - Only run autocomplete at appropriate times ( Fixes #3 ) #### 1.0.5 - Fix a bug in retrying for server - Fix a bug where deep nested objects would mess up autocomplete #### 1.0.4 - Bump `atom-package-deps` version #### 1.0.3 - Bump `atom-linter` dependency to include fix for projects that don't have a `.flowcofig` #### 1.0.2 - Improve handling of fatal errors #### 1.0.1 - Implement smart sorting and filtering of autocomplete suggestions - Make linter messages more user friendly #### 1.0.0 - Linting support added - Autocomplete support added ================================================ FILE: LICENSE.md ================================================ Copyright (c) 2016 steelbrain 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. ================================================ FILE: README.md ================================================ Flow-IDE ======= Flow IDE is a lightweight package that provides IDE features for [FlowType][FlowType] for [Atom Editor][Atom]. It's pretty lightweight and robust. #### Installation ``` apm install flow-ide ``` #### Setup 1) You will need to install flow-bin into your project! ``` npm install --save-dev flow-bin ``` or ``` yarn add --dev flow-bin ``` 2) You will need ` // @flow ` at the top of all files you wish to lint 3) You will need a `.flowconfig` which can be initialized with `flow init` if you have flow installed, if not you can use [this flowconfig](https://github.com/steelbrain/flow-ide/blob/master/.flowconfig) 4) Window: Reload (Ctrl+Shift+F5) to apply changes #### Features - Linting - Autocomplete - Jump to declaration (using [facebook-atom/hyperclick][hyperclick] or [facebook-atom/atom-ide-ui][atom-ide-ui]) - Datatip on hover (using [facebook-atom/atom-ide-ui][atom-ide-ui]) - Outline of classes, functions, types and variables (using [facebook-atom/atom-ide-ui][atom-ide-ui]) #### Differences to other packages Differences to [facebook/nuclide][nuclide] - Nuclide is nice and all but it's mostly bloatware for lightweight flow programming Differences to [AtomLinter/linter-flow][linter-flow] - It tries to manage flow servers by itself, I find it annoying Differences to [nmn/autocomplete-flow][autocomplete-flow] - Never worked for me Differences to [LukeHoban/ide-flow][ide-flow] - Outdated and buggy - No longer maintained #### Screenshots ![Autocomplete](https://cloud.githubusercontent.com/assets/4278113/12857027/bb8e2c80-cc69-11e5-918d-4451d0679e66.png) #### License This project is licensed under the terms of MIT License. Check the LICENSE file for more info. [FlowType]:http://flowtype.org/ [Atom]:https://atom.io/ [nuclide]:https://github.com/facebook/nuclide [hyperclick]:https://github.com/facebook-atom/hyperclick [atom-ide-ui]:https://github.com/facebook-atom/atom-ide-ui [ide-flow]:https://github.com/lukehoban/atom-ide-flow [linter-flow]:https://github.com/AtomLinter/linter-flow [autocomplete-flow]:https://github.com/nmn/autocomplete-flow ================================================ FILE: decls/atom.js ================================================ /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. * * @flow */ /** * Private Classes */ type IDisposable = { dispose(): void } // Octicons v4.4.0. List extracted from the atom-styleguide package. type atom$Octicon = 'alert' | 'alignment-align' | 'alignment-aligned-to' | 'alignment-unalign' | 'arrow-down' | 'arrow-left' | 'arrow-right' | 'arrow-small-down' | 'arrow-small-left' | 'arrow-small-right' | 'arrow-small-up' | 'arrow-up' | 'beaker' | 'beer' | 'bell' | 'bold' | 'book' | 'bookmark' | 'briefcase' | 'broadcast' | 'browser' | 'bug' | 'calendar' | 'check' | 'checklist' | 'chevron-down' | 'chevron-left' | 'chevron-right' | 'chevron-up' | 'circle-slash' | 'circuit-board' | 'clippy' | 'clock' | 'cloud-download' | 'cloud-upload' | 'code' | 'color-mode' | 'comment' | 'comment-add' | 'comment-discussion' | 'credit-card' | 'dash' | 'dashboard' | 'database' | 'desktop-download' | 'device-camera' | 'device-camera-video' | 'device-desktop' | 'device-mobile' | 'diff' | 'diff-added' | 'diff-ignored' | 'diff-modified' | 'diff-removed' | 'diff-renamed' | 'ellipses' | 'ellipsis' | 'eye' | 'eye-unwatch' | 'eye-watch' | 'file' | 'file-add' | 'file-binary' | 'file-code' | 'file-directory' | 'file-directory-create' | 'file-media' | 'file-pdf' | 'file-submodule' | 'file-symlink-directory' | 'file-symlink-file' | 'file-text' | 'file-zip' | 'flame' | 'fold' | 'gear' | 'gift' | 'gist' | 'gist-fork' | 'gist-new' | 'gist-private' | 'gist-secret' | 'git-branch' | 'git-branch-create' | 'git-branch-delete' | 'git-commit' | 'git-compare' | 'git-fork-private' | 'git-merge' | 'git-pull-request' | 'git-pull-request-abandoned' | 'globe' | 'grabber' | 'graph' | 'heart' | 'history' | 'home' | 'horizontal-rule' | 'hourglass' | 'hubot' | 'inbox' | 'info' | 'issue-closed' | 'issue-opened' | 'issue-reopened' | 'italic' | 'jersey' | 'jump-down' | 'jump-left' | 'jump-right' | 'jump-up' | 'key' | 'keyboard' | 'law' | 'light-bulb' | 'link' | 'link-external' | 'list-ordered' | 'list-unordered' | 'location' | 'lock' | 'log-in' | 'log-out' | 'logo-gist' | 'logo-github' | 'mail' | 'mail-read' | 'mail-reply' | 'mark-github' | 'markdown' | 'megaphone' | 'mention' | 'microscope' | 'milestone' | 'mirror' | 'mirror-private' | 'mirror-public' | 'mortar-board' | 'move-down' | 'move-left' | 'move-right' | 'move-up' | 'mute' | 'no-newline' | 'octoface' | 'organization' | 'package' | 'paintcan' | 'pencil' | 'person' | 'person-add' | 'person-follow' | 'pin' | 'playback-fast-forward' | 'playback-pause' | 'playback-play' | 'playback-rewind' | 'plug' | 'plus-small' | 'plus' | 'podium' | 'primitive-dot' | 'primitive-square' | 'pulse' | 'puzzle' | 'question' | 'quote' | 'radio-tower' | 'remove-close' | 'reply' | 'repo' | 'repo-clone' | 'repo-create' | 'repo-delete' | 'repo-force-push' | 'repo-forked' | 'repo-pull' | 'repo-push' | 'repo-sync' | 'rocket' | 'rss' | 'ruby' | 'screen-full' | 'screen-normal' | 'search' | 'search-save' | 'server' | 'settings' | 'shield' | 'sign-in' | 'sign-out' | 'smiley' | 'split' | 'squirrel' | 'star' | 'star-add' | 'star-delete' | 'steps' | 'stop' | 'sync' | 'tag' | 'tag-add' | 'tag-remove' | 'tasklist' | 'telescope' | 'terminal' | 'text-size' | 'three-bars' | 'thumbsdown' | 'thumbsup' | 'tools' | 'trashcan' | 'triangle-down' | 'triangle-left' | 'triangle-right' | 'triangle-up' | 'type-array'| 'type-boolean'| 'type-class'| 'type-constant'| 'type-constructor'| 'type-enum'| 'type-field'| 'type-file'| 'type-function'| 'type-interface'| 'type-method'| 'type-module'| 'type-namespace'| 'type-number'| 'type-package'| 'type-property'| 'type-string'| 'type-variable' | 'unfold' | 'unmute' | 'unverified' | 'verified' | 'versions' | 'watch' | 'x' | 'zap'; type atom$PaneLocation = 'left' | 'right' | 'bottom' | 'center'; declare type atom$Color = { // Returns a String in the form '#abcdef'. toHexString(): string; // Returns a String in the form 'rgba(25, 50, 75, .9)'. toRGBAString(): string; } declare class atom$Model { destroy(): void, isDestroyed(): boolean, } declare class atom$Package { path: string, activateTime: number, mainModule: any, mainModulePath: string, metadata: Object, name: string, loadTime: number, getType(): 'atom' | 'textmate' | 'theme', hasActivationCommands(): boolean, hasActivationHooks(): boolean, initializeTime: number, getActivationHooks(): Array, onDidDeactivate(cb: () => mixed): IDisposable, activateNow(): void, // Undocumented bundledPackage: boolean, getCanDeferMainModuleRequireStorageKey(): string, initializeIfNeeded(): void, } /** * Essential Classes */ declare type atom$CustomEvent = CustomEvent & { originalEvent?: Event; } type atom$CommandCallback = (event: atom$CustomEvent) => mixed; type atom$CommandDescriptor = { name: string, displayName: string, description?: string, hiddenInCommandPalette?: boolean, tags?: Array, }; type atom$CommandListener = atom$CommandCallback | { displayName?: string, description?: string, didDispatch: atom$CommandCallback, }; declare class atom$CommandRegistry { // Methods add( target: string | HTMLElement, commandNameOrCommands: string | {[commandName: string]: atom$CommandListener}, listener?: atom$CommandListener, throwOnInvalidSelector?: boolean, ): IDisposable, dispatch(target: HTMLElement, commandName: string, detail?: Object): void, onDidDispatch(callback: (event: atom$CustomEvent) => mixed): IDisposable, onWillDispatch(callback: (event: atom$CustomEvent) => mixed): IDisposable, findCommands(opts: {target: Node}): Array, } declare class atom$CompositeDisposable { constructor(...disposables: Array): void, dispose(): void, add(...disposables: Array): void, remove(disposable: IDisposable): void, clear(): void, } type atom$ConfigParams = { saveCallback?: Object => void, mainSource?: string, projectHomeSchema?: atom$ConfigSchema, }; type atom$ConfigType = 'boolean' | 'string' | 'integer' | 'number' | 'array' | 'object' | 'color' | 'any'; type atom$ConfigSchema = { default?: mixed, description?: string, enum?: Array, maximum?: number, minimum?: number, properties?: Object, title?: string, type: Array | atom$ConfigType, }; declare class atom$Config { defaultSettings: Object, settings: Object, // Config Subscription observe( keyPath: string, optionsOrCallback?: | {scope?: atom$ScopeDescriptorLike} | (value: mixed) => void, callback?: (value: mixed) => mixed, ): IDisposable, onDidChange( keyPathOrCallback: | string | (event: {oldValue: mixed, newValue: mixed}) => mixed, optionsOrCallback?: | {scope?: atom$ScopeDescriptorLike} | (event: {oldValue: mixed, newValue: mixed}) => mixed, callback?: (event: {oldValue: mixed, newValue: mixed}) => mixed ): IDisposable, // Managing Settings get( keyPath?: string, options?: { excludeSources?: Array, sources?: Array, scope?: atom$ScopeDescriptorLike, } ): mixed, set( keyPath: string, value: ?mixed, options?: { scopeSelector?: string, source?: string, }, ): boolean, unset( keyPath: string, options?: { scopeSelector?: string, source?: string, } ): void, getUserConfigPath(): string, // Undocumented Methods constructor(params?: atom$ConfigParams): atom$Config, getRawValue(keyPath: ?string, options: {excludeSources?: string, sources?: string}): mixed, getSchema(keyPath: string): atom$ConfigSchema, save(): void, setRawValue(keyPath: string, value: mixed): void, setSchema( keyPath: string, schema: atom$ConfigSchema, ): void, removeAtKeyPath(keyPath: ?string, value: ?mixed): mixed, // Used by nuclide-config to set the initial settings from disk resetUserSettings(newSettings: Object, options?: {source?: string}): void, } declare class atom$Cursor { // Cursor Marker marker: atom$Marker; editor: atom$TextEditor; // Event Subscription onDidChangePosition( callback: (event: { oldBufferPosition: atom$Point, oldScreenPosition: atom$Point, newBufferPosition: atom$Point, newScreenPosition: atom$Point, textChanged: boolean, Cursor: atom$Cursor, }) => mixed, ): IDisposable, // Managing Cursor Position getBufferRow(): number, getBufferColumn(): number, getBufferPosition(): atom$Point, // Cursor Position Details // Moving the Cursor moveUp(rowCount: number, {moveToEndOfSelection?: boolean}): void, moveDown(rowCount: number, {moveToEndOfSelection?: boolean}): void, // Local Positions and Ranges getCurrentWordBufferRange(options?: {wordRegex: RegExp}): atom$Range, getCurrentWordPrefix(): string, // Visibility // Comparing to another cursor // Utilities wordRegExp(options?: {includeNonWordCharacters: boolean}): RegExp, } declare class atom$Decoration { destroy(): void, onDidChangeProperties( callback: (event: {oldProperties: Object, newProperties: Object}) => mixed ): IDisposable, onDidDestroy(callback: () => mixed): IDisposable, getMarker(): atom$Marker, getProperties(): Object, setProperties(properties: mixed): void, } declare class atom$DisplayMarkerLayer { destroy(): void, clear(): void, isDestroyed(): boolean, markBufferPosition(position: atom$PointLike, options?: MarkerOptions): atom$Marker, markBufferRange(range: atom$Range | atom$RangeLike, options?: MarkerOptions): atom$Marker, findMarkers(options: MarkerOptions): Array, getMarkers(): Array, onDidUpdate(callback: () => mixed): IDisposable } declare class atom$LayerDecoration { destroy(): void, isDestroyed(): boolean, getProperties(): Object, setProperties(properties: mixed): void, setPropertiesForMarker(marker: atom$Marker, properties: mixed): void, } declare class atom$Disposable { constructor(disposalAction?: (...args: Array) => any): void, disposed: boolean, dispose(): void, } declare class atom$Emitter { static onEventHandlerException(error: any): IDisposable, dispose(): void, on(name: string, callback: (v: any) => mixed): IDisposable, once(name: string, callback: (v: any) => mixed): IDisposable, preempt(name: string, callback: (v: any) => void): IDisposable, // This is a flow hack to prevent emitting more than one value. // `EventEmitter` allows emitting any number of values - making this a land // mine, since we tend to think of `emit` as interchangeable. // This hack only works if the extra value is not `undefined`, so this isn't // full-proof, but it works for most cases. emit(name: string, value: any, ...no_extra_args_allowed: Array): void, } declare class atom$Gutter { name: string, destroy(): void, decorateMarker( marker: atom$Marker, options?: {type?: string, 'class'?: string, item?: Object | HTMLElement}, ): atom$Decoration, show(): void, hide(): void, onDidDestroy(callback: () => void): IDisposable, } declare type atom$MarkerChangeEvent = { oldHeadScreenPosition: atom$Point, newHeadScreenPosition: atom$Point, oldTailScreenPosition: atom$Point, newTailScreenPosition: atom$Point, oldHeadBufferPosition: atom$Point, newHeadBufferPosition: atom$Point, oldTailBufferPosition: atom$Point, newTailBufferPosition: atom$Point, isValid: boolean, textChanged: boolean, } declare class atom$Marker { destroy(): void, getBufferRange(): atom$Range, getStartBufferPosition(): atom$Point, onDidChange(callback: (event: atom$MarkerChangeEvent) => mixed): IDisposable, isValid(): boolean, isDestroyed(): boolean, onDidDestroy(callback: () => mixed): IDisposable, getScreenRange(): atom$Range, setBufferRange( range: atom$RangeLike, properties?: {reversed: boolean}, ): void, id: number, } declare class atom$ServiceHub { provide(keyPath: string, version: string, service: T): IDisposable, consume( keyPath: string, versionRange: string, callback: (provider: T) => mixed ): IDisposable, } type atom$PackageMetadata = { name: string, version: string, }; declare class atom$PackageManager { +initialPackagesActivated: boolean, // Event Subscription onDidLoadInitialPackages(callback: () => void): IDisposable, onDidActivateInitialPackages(callback: () => void): IDisposable, onDidActivatePackage(callback: (pkg: atom$Package) => mixed): IDisposable, onDidDeactivatePackage(callback: (pkg: atom$Package) => mixed): IDisposable, onDidLoadPackage(callback: (pkg: atom$Package) => mixed): IDisposable, onDidUnloadPackage(callback: (pkg: atom$Package) => mixed): IDisposable, onDidTriggerActivationHook(activationHook: string, callback: () => mixed): IDisposable, // Package system data getApmPath(): string, getPackageDirPaths(): Array, // General package data resolvePackagePath(name: string): ?string, isBundledPackage(name: string): boolean, // Enabling and disabling packages enablePackage(name: string): ?atom$Package, disablePackage(name: string): ?atom$Package, isPackageDisabled(name: string): boolean, // Accessing active packages getActivePackage(name: string): ?atom$Package, getActivePackages(): Array, isPackageActive(name: string): boolean, hasActivatedInitialPackages(): boolean, // Activating and deactivating packages activatePackage(name: string): Promise, // Accessing loaded packages getLoadedPackage(name: string): ?atom$Package, getLoadedPackages(): Array, isPackageLoaded(name: string): boolean, hasLoadedInitialPackages(): boolean, // Accessing available packages getAvailablePackageNames(): Array, getAvailablePackageMetadata(): Array, // (Undocumented.) activate(): Promise, deactivatePackages(): Promise, deactivatePackage(name: string, suppressSerialization?: boolean): Promise, emitter: atom$Emitter, loadedPackages: {[packageName: string]: atom$Package}, loadPackage(name: string): void, loadPackages(): void, serializePackage(pkg: atom$Package): void, serviceHub: atom$ServiceHub, packageDirPaths: Array, triggerActivationHook(hook: string): void, triggerDeferredActivationHooks(): void, unloadPackage(name: string): void, unloadPackages(): void, } declare class atom$StyleManager { // Event Subscription // Reading Style Elements getStyleElements(): Array, // Paths getUserStyleSheetPath(): string, // (Undocumented.) addStyleSheet( source: string, params: { sourcePath?: string, context?: boolean, priority?: number, skipDeprecatedSelectorsTransformation?: boolean } ): IDisposable, } type atom$PaneSplitParams = { copyActiveItem?: boolean, items?: Array, }; type atom$PaneSplitOrientation = 'horizontal' | 'vertical'; type atom$PaneSplitSide = 'before' | 'after'; // Undocumented class declare class atom$applicationDelegate { focusWindow(): Promise, open(params: { pathsToOpen: Array, newWindow?: boolean, devMode?: boolean, safeMode?: boolean, }): void, // Used by nuclide-config to replicate atom.config saveCallback setUserSettings(config: atom$Config, configFilePath: string): Promise; } type atom$PaneParams = { activeItem?: Object, applicationDelegate: atom$applicationDelegate, focused?: boolean, container: Object, config: atom$Config, notificationManager: atom$NotificationManager, deserializerManager: atom$DeserializerManager, items?: Array, itemStackIndices?: Array, flexScale?: number, }; declare class atom$Pane { // Items addItem(item: Object, options?: {index?: number, pending?: boolean}): Object, getItems(): Array, getActiveItem(): ?Object, itemAtIndex(index: number): ?Object, getActiveItemIndex(): number, activateItem(item: Object): ?Object, activateItemAtIndex(index: number): void, moveItemToPane(item: Object, pane: atom$Pane, index: number): void, destroyItem(item: Object, force?: boolean): Promise, itemForURI(uri: string): Object, // Event subscriptions. onDidAddItem(cb: (event: {item: Object, index: number}) => void): IDisposable, onDidRemoveItem(cb: (event: {item: Object, index: number}) => void): IDisposable, onWillRemoveItem(cb: (event: {item: Object, index: number}) => void): IDisposable, onDidDestroy(cb: () => mixed): IDisposable, onDidChangeFlexScale(cb: (newFlexScale: number) => void): IDisposable, onWillDestroy(cb: () => void): IDisposable, observeActiveItem(cb: (item: ?Object) => void): IDisposable, // Lifecycle isActive(): boolean, activate(): void, destroy(): void, isDestroyed(): void, // Splitting splitLeft(params?: atom$PaneSplitParams): atom$Pane, splitRight(params?: atom$PaneSplitParams): atom$Pane, splitUp(params?: atom$PaneSplitParams): atom$Pane, splitDown(params?: atom$PaneSplitParams): atom$Pane, split( orientation: atom$PaneSplitOrientation, side: atom$PaneSplitSide, params?: atom$PaneSplitParams, ): atom$Pane, // Undocumented Methods constructor(params: atom$PaneParams): atom$Pane, getPendingItem(): atom$PaneItem, setPendingItem(item: atom$PaneItem): void, clearPendingItem(): void, getFlexScale(): number, getElement(): HTMLElement, getParent(): Object, removeItem(item: Object, moved: ?boolean): void, setActiveItem(item: Object): Object, setFlexScale(flexScale: number): number, getContainer(): atom$PaneContainer, element: HTMLElement, } declare interface atom$PaneItem { // These are all covariant, meaning that these props are read-only. Therefore we can assign an // object with more strict requirements to an variable of this type. +getTitle: () => string, +getLongTitle?: () => string, +getIconName?: () => string, +getURI?: () => ?string, +onDidChangeIcon?: (cb: (icon: string) => void) => IDisposable, +onDidChangeTitle?: (cb: (title: string) => void) => IDisposable, +onDidTerminatePendingState?: (() => mixed) => IDisposable; +serialize?: () => Object, +terminatePendingState?: () => void, } // Undocumented class declare class atom$PaneAxis { getFlexScale(): number, setFlexScale(flexScale: number): number, getItems(): Array, } // Undocumented class // Note that this is not the same object returned by `atom.workspace.getPaneContainers()`. (Those // are typed here as AbstractPaneContainers and, in the current implementation, wrap these.) declare class atom$PaneContainer { constructor({ config: atom$Config, applicationDelegate: atom$applicationDelegate, notificationManager: atom$NotificationManager, deserializerManager: atom$DeserializerManager, }): atom$PaneContainer, destroy(): void, getActivePane(): atom$Pane, getActivePaneItem(): ?Object, getLocation(): atom$PaneLocation, getPanes(): Array, getPaneItems(): Array, observePanes(cb: (pane: atom$Pane) => void): IDisposable, onDidAddPane(cb: (event: {pane: atom$Pane}) => void): IDisposable, onDidDestroyPane(cb: (event: {pane: atom$Pane}) => void): IDisposable, onWillDestroyPane(cb: (event: {pane: atom$Pane}) => void): IDisposable, onDidAddPaneItem(cb: (item: atom$PaneItem) => void): IDisposable, onDidDestroyPaneItem(cb: (item: atom$Pane) => void): IDisposable, paneForItem(item: Object): ?atom$Pane, serialize(): Object, } declare class atom$Panel { // Construction and Destruction destroy(): void, // Event Subscription onDidChangeVisible(callback: (visible: boolean) => any): IDisposable, onDidDestroy(callback: (panel: atom$Panel) => any): IDisposable, // Panel Details getElement(): HTMLElement, getItem(): any, getPriority(): number, isVisible(): boolean, hide(): void, show(): void, } type atom$PointObject = {row: number, column: number}; type atom$PointLike = atom$Point | [number, number] | atom$PointObject; declare class atom$Point { static fromObject(object: atom$PointLike, copy: ? boolean): atom$Point, constructor(row: number, column: number): void, row: number, column: number, copy(): atom$Point, negate(): atom$Point, // Comparison min(point1: atom$PointLike, point2: atom$PointLike): atom$Point, compare(other: atom$PointLike): -1 | 0 | 1, isEqual(otherRange: atom$PointLike): boolean, isLessThan(other: atom$PointLike): boolean, isLessThanOrEqual(other: atom$PointLike): boolean, isGreaterThan(other: atom$PointLike): boolean, isGreaterThanOrEqual(other: atom$PointLike): boolean, // Operations translate(other: atom$PointLike): atom$Point, // Conversion serialize(): [number, number], toArray(): [number, number], } type atom$RangeObject = { start: atom$PointObject, end: atom$PointObject, }; type atom$RangeLike = atom$Range | atom$RangeObject // TODO: Flow doesn't really handle the real signature below... | [atom$PointLike, atom$PointLike] | { start: atom$PointLike, end: atom$PointLike, }; declare class atom$Range { static fromObject( object: atom$RangeLike, copy?: boolean, ): atom$Range, constructor(pointA: atom$PointLike, pointB: atom$PointLike): void, compare(other: atom$Range): number, start: atom$Point, end: atom$Point, isEmpty(): boolean, isEqual(otherRange: atom$RangeLike): boolean, intersectsWith(otherRange: atom$RangeLike, exclusive?: boolean): boolean, containsPoint(point: atom$PointLike, exclusive?: boolean): boolean, containsRange(other: atom$Range, exclusive?: boolean): boolean, union(other: atom$Range): atom$Range, serialize(): Array>, translate(startDelta: atom$PointLike, endDelta?: atom$PointLike): atom$Range, getRowCount(): number, getRows(): Array, } type RawStatusBarTile = { item: HTMLElement, priority: number, }; type atom$StatusBarTile = { getPriority(): number, getItem(): HTMLElement, destroy(): void, }; declare class atom$ScopeDescriptor { constructor(object: {scopes: Array}): void, getScopesArray(): Array, } type atom$ScopeDescriptorLike = atom$ScopeDescriptor | Array; /** * This API is defined at https://github.com/atom/status-bar. */ declare class atom$StatusBar { addLeftTile(tile: RawStatusBarTile): atom$StatusBarTile, addRightTile(tile: RawStatusBarTile): atom$StatusBarTile, getLeftTiles(): Array, getRightTiles(): Array, } // https://github.com/atom/atom/blob/v1.9.0/src/text-editor-registry.coffee declare class atom$TextEditorRegistry { add(editor: atom$TextEditor): IDisposable, remove(editor: atom$TextEditor): boolean, observe(callback: (editor: atom$TextEditor) => void): IDisposable, build: (params: atom$TextEditorParams) => atom$TextEditor, // Private editors: Set, } declare class atom$ThemeManager { // Event Subscription /** * As recent as Atom 1.0.10, the implementation of this method was: * * ``` * onDidChangeActiveThemes: (callback) -> * @emitter.on 'did-change-active-themes', callback * @emitter.on 'did-reload-all', callback # TODO: Remove once deprecated pre-1.0 APIs are gone * ``` * * Due to the nature of CoffeeScript, onDidChangeActiveThemes returns a Disposable even though it * is not documented as doing so. However, the Disposable that it does return removes the * subscription on the 'did-reload-all' event (which is supposed to be deprecated) rather than the * 'did-change-active-themes' one. */ onDidChangeActiveThemes(callback: () => mixed): IDisposable, // Accessing Loaded Themes getLoadedThemeNames(): Array, getLoadedThemes(): Array, // TODO: Define undocumented ThemePackage class. // Accessing Active Themes getActiveThemeNames(): Array, getActiveThemes(): Array, // TODO: Define undocumented ThemePackage class. // Managing Enabled Themes getEnabledThemeNames(): Array, // Private activateThemes(): Promise, requireStylesheet(stylesheetPath: string): IDisposable, } type atom$TooltipsPlacementOption = 'top' | 'bottom' | 'left' | 'right' | 'auto'; type atom$TooltipsAddOptions = { title?: string, item?: HTMLElement, keyBindingCommand?: string, keyBindingTarget?: HTMLElement, animation?: boolean, container?: string | false, delay?: number | {show: number, hide: number}, placement?: atom$TooltipsPlacementOption | () => atom$TooltipsPlacementOption, trigger?: string, }; type atom$Tooltip = { show(): void; hide(): void; getTooltipElement(): HTMLElement, }; declare class atom$TooltipManager { tooltips: Map>; add( target: HTMLElement, options: atom$TooltipsAddOptions, ): IDisposable, findTooltips(HTMLElement): Array, } type InsertTextOptions = { select: boolean, autoIndent: boolean, autoIndentNewline: boolean, autoDecreaseIndent: boolean, normalizeLineEndings: ?boolean, undo: string, }; type DecorateMarkerParams = { type: 'line', class: string, onlyHead?: boolean, onlyEmpty?: boolean, onlyNonEmpty?: boolean, } | { type: 'gutter', item?: HTMLElement, class?: string, onlyHead?: boolean, onlyEmpty?: boolean, onlyNonEmpty?: boolean, gutterName?: string, } | { type: 'highlight', class?: string, gutterName?: string, } | { type: 'overlay', item: Object, position?: 'head' | 'tail', // Defaults to 'head' when unspecified. } | { type: 'block', item: HTMLElement, position?: 'before' | 'after', // Defaults to 'before' when unspecified. } | { type: 'line-number', class?: string, }; type ChangeCursorPositionEvent = { oldBufferPosition: atom$Point, oldScreenPosition: atom$Point, newBufferPosition: atom$Point, newScreenPosition: atom$Point, textChanged: boolean, cursor: atom$Cursor, }; type MarkerOptions = { reversed?: boolean, tailed?: boolean, invalidate?: 'never' | 'surround' | 'overlap' | 'inside' | 'touch', exclusive?: boolean, }; type atom$ChangeSelectionRangeEvent = {| oldBufferRange: atom$Range, oldScreenRange: atom$Range, newBufferRange: atom$Range, newScreenRange: atom$Range, selection: atom$Selection, |}; declare class atom$TextEditor extends atom$Model { id: number, verticalScrollMargin: number, // Event Subscription onDidChange(callback: () => void): IDisposable, onDidChangePath(callback: (newPath: string) => mixed): IDisposable, onDidStopChanging(callback: () => mixed): IDisposable, onDidChangeCursorPosition(callback: (event: ChangeCursorPositionEvent) => mixed): IDisposable, onDidAddCursor(callback: (cursor: atom$Cursor) => mixed): IDisposable; onDidRemoveCursor(callback: (cursor: atom$Cursor) => mixed): IDisposable; onDidDestroy(callback: () => mixed): IDisposable, onDidSave(callback: (event: {path: string}) => mixed): IDisposable, getBuffer(): atom$TextBuffer, observeGrammar(callback: (grammar: atom$Grammar) => mixed): IDisposable, onWillInsertText(callback: (event: {cancel: () => void, text: string}) => void): IDisposable, // Note that the range property of the event is undocumented. onDidInsertText(callback: (event: {text: string, range: atom$Range}) => mixed): IDisposable, onDidChangeSoftWrapped(callback: (softWrapped: boolean) => mixed): IDisposable, onDidChangeSelectionRange(callback: (event: atom$ChangeSelectionRangeEvent) => mixed): IDisposable, observeSelections(callback: (selection: atom$Selection) => mixed): IDisposable, // File Details getTitle: () => string, getLongTitle(): string, /** * If you open Atom via Spotlight such that it opens with a tab named * "untitled" that does not correspond to a file on disk, this will return * null. */ getPath(): ?string, getURI: () => ?string, insertNewline(): void, isModified: () => boolean, isEmpty(): boolean, getEncoding(): buffer$Encoding, setEncoding(encoding: string): void, getTabLength() : number, getSoftTabs(): boolean, getIconName(): string, onDidChangeIcon(cb: (icon: string) => void): IDisposable, onDidChangeTitle(cb: (title: string) => void): IDisposable, // File Operations save(): Promise, // DO NOT USE: Doesn't work with remote text buffers! // saveAs(filePath: string): void, // Reading Text getText(): string, getTextInBufferRange(range: atom$RangeLike): string, getLineCount(): number, getScreenLineCount(): number, getLastScreenRow(): number, // Mutating Text setText(text: string, options?: InsertTextOptions): void, setTextInBufferRange( range: atom$RangeLike, text: string, options?: { normalizeLineEndings?: boolean, undo?: string, }, ): atom$Range, insertText(text: string): Array | false, mutateSelectedText(fn: (selection: atom$Selection, index: number) => void): void, delete: () => void, backspace: () => void, duplicateLines: () => void, // History createCheckpoint(): atom$TextBufferCheckpoint, revertToCheckpoint(checkpoint: atom$TextBufferCheckpoint): boolean, terminatePendingState(): void, transact(fn: () => mixed, _: void): void, transact(groupingInterval: number, fn: () => mixed): void, onDidTerminatePendingState(() => mixed): IDisposable; // TextEditor Coordinates screenPositionForBufferPosition( bufferPosition: atom$PointLike, options?: { wrapBeyondNewlines?: boolean, wrapAtSoftNewlines?: boolean, screenLine?: boolean, }, ): atom$Point, bufferPositionForScreenPosition( screenPosition: atom$PointLike, options?: { wrapBeyondNewlines?: boolean, wrapAtSoftNewlines?: boolean, screenLine?: boolean, }, ): atom$Point, getEofBufferPosition(): atom$Point, getVisibleRowRange(): [number, number], bufferRowForScreenRow(screenRow: number): number, screenRangeForBufferRange(bufferRange: atom$RangeLike): atom$Range, // Decorations decorateMarker(marker: atom$Marker, decorationParams: DecorateMarkerParams): atom$Decoration, decorateMarkerLayer( markerLayer: atom$DisplayMarkerLayer, decorationParams: DecorateMarkerParams, ): atom$LayerDecoration, decorationsForScreenRowRange( startScreenRow: number, endScreenRow: number, ): {[markerId: string]: Array}, getDecorations(options?: {class?: string, type?: string}): Array, // Markers addMarkerLayer(): atom$DisplayMarkerLayer, getDefaultMarkerLayer(): atom$DisplayMarkerLayer, markBufferPosition(position: atom$PointLike, options?: MarkerOptions): atom$Marker, markBufferRange(range: atom$RangeLike, options?: MarkerOptions): atom$Marker, markScreenRange(range: atom$RangeLike, options?: MarkerOptions): atom$Marker, markScreenPosition(position: atom$PointLike, options?: MarkerOptions): atom$Marker, findMarkers(options: MarkerOptions): Array, getMarkerCount(): number, // Cursors getCursors(): Array, setCursorBufferPosition( position: atom$PointLike, options?: { autoscroll?: boolean, wrapBeyondNewlines?: boolean, wrapAtSoftNewlines?: boolean, screenLine?: boolean, }): void, getCursorBufferPosition(): atom$Point, getCursorBufferPositions(): Array, getCursorScreenPosition(): atom$Point, getCursorScreenPositions(): Array, getLastCursor(): atom$Cursor, addCursorAtBufferPosition(point: atom$PointLike): atom$Cursor, moveToBeginningOfLine(): void, moveToEndOfLine(): void, moveToBottom(): void, // Folds foldCurrentRow(): void, unfoldCurrentRow(): void, foldBufferRow(bufferRow: number): void, unfoldBufferRow(bufferRow: number): void, // Selections getSelectedText(): string, selectAll(): void, getSelectedBufferRange(): atom$Range, getSelectedBufferRanges(): Array, getSelections(): Array, selectToBufferPosition(point: atom$Point): void, setSelectedBufferRange( bufferRange: atom$RangeLike, options?: { reversed?: boolean, preserveFolds?: boolean, }, ): void, setSelectedBufferRanges( bufferRanges: Array, options?: { reversed?: boolean, preserveFolds?: boolean, }, ): void, selectWordsContainingCursors(): void, // Folds unfoldAll(): void, // Searching and Replacing scanInBufferRange( regex: RegExp, range: atom$Range, iterator: (foundMatch: { match: mixed, matchText: string, range: atom$Range, stop: () => mixed, replace: (replaceWith: string) => mixed, }) => mixed ): void, scan( regex: RegExp, iterator: (foundMatch: { match: mixed, matchText: string, range: atom$Range, stop: () => mixed, replace: (replaceWith: string) => mixed, }) => mixed ): void, // Tab Behavior // Soft Wrap Behavior // Indentation indentationForBufferRow(bufferRow: number): number, setTabLength(tabLength: number): void, setSoftTabs(softTabs: boolean): void, lineTextForBufferRow(bufferRow: number): string, lineTextForScreenRow(screenRow: number): string, // Grammars getGrammar(): atom$Grammar, setGrammar(grammar: ?atom$Grammar): void, // Clipboard Operations pasteText: (options?: Object) => void, copySelectedText: () => void, // Managing Syntax Scopes getRootScopeDescriptor(): atom$ScopeDescriptor, scopeDescriptorForBufferPosition( bufferPosition: atom$PointLike, ): atom$ScopeDescriptor, // Gutter addGutter(options: { name: string, priority?: number, visible?: boolean, }): atom$Gutter, observeGutters(callback: (gutter: atom$Gutter) => void): IDisposable, getGutters(): Array, gutterWithName(name: string): ?atom$Gutter, // Scrolling the TextEditor scrollToBufferPosition( position: atom$Point | [?number, ?number], options?: {center?: boolean} ): void, scrollToScreenPosition( position: atom$Point | [?number, ?number], options?: {center?: boolean} ): void, scrollToScreenRange(screenRange: atom$Range, options?: {clip?: boolean}): void, scrollToCursorPosition( options?: {center?: boolean} ): void, scrollToBottom(): void, scrollToTop(): void, // TextEditor Rendering getPlaceholderText(): string, setPlaceholderText(placeholderText: string): void, // This is undocumented, but Nuclide uses it in the AtomTextEditor wrapper. setLineNumberGutterVisible(lineNumberGutterVisible: boolean): void, // Editor Options setSoftWrapped(softWrapped: boolean): void, isFoldedAtBufferRow(row: number): boolean, getLastBufferRow(): number, // Undocumented Methods getElement(): atom$TextEditorElement, getDefaultCharWidth(): number, getLineHeightInPixels(): number, moveToTop(): void, tokenForBufferPosition(position: atom$Point | [?number, ?number]): atom$Token, onDidConflict(callback: () => void): IDisposable, serialize(): Object, foldBufferRowRange(startRow: number, endRow: number): void, getNonWordCharacters(position?: atom$PointLike): string, scheduleComponentUpdate(): void, } /** * This is not part of the official Atom 1.0 API. Nevertheless, we need to reach into this object * via `atom$TextEditorElement` to do some things that we have no other way to do. */ declare class atom$TextEditorComponent { domNode: HTMLElement, scrollViewNode: HTMLElement, presenter: atom$TextEditorPresenter, refs: atom$TextEditorComponentRefs, linesComponent: atom$LinesComponent, // NOTE: This is typed as a property to allow overwriting. startCursorBlinking: () => void, stopCursorBlinking(): void, pixelPositionForScreenPosition( screenPosition: atom$Point, clip?: boolean, ): {top: number, left: number}, screenPositionForMouseEvent(event: MouseEvent): atom$Point, pixelPositionForMouseEvent( event: MouseEvent, linesClientRect?: {top: number, left: number, bottom: number, right: number}, ): {top: number, left: number, bottom: number, right: number}, invalidateBlockDecorationDimensions(decoration: atom$Decoration): void, element: atom$TextEditorElement, didFocus(): void, setScrollTop(scrollTop: number): void, getScrollTop(): number, setScrollLeft(scrollLeft: number): void, getScrollLeft(): number, updateSync(useScheduler?: boolean): void, } /** * This is not part of the official Atom 1.0 API. Nevertheless, we need to reach into this object * via `atom$TextEditorComponent` to do some things that we have no other way to do. */ declare class atom$TextEditorPresenter { startBlinkingCursors: () => void, stopBlinkingCursors(visible: boolean): void, updateLineNumberGutterState(): void, } /** * This is not part of the official Atom 1.0 API. Nevertheless, we need it to access * the deepest dom element receiving DOM events. */ declare class atom$LinesComponent { domNode: HTMLElement, getDomNode(): HTMLElement, } /** * This is not part of the official Atom 1.0 API. Nevertheless, we need it to access * the deepest dom element receiving DOM events. */ declare class atom$TextEditorComponentRefs { lineTiles: HTMLElement, } /** * This is not part of the official Atom 1.0 API, but it really should be. This is the element that * is returned when you run `atom.views.getView()`. */ declare class atom$TextEditorElement extends HTMLElement { component: ?atom$TextEditorComponent, getModel(): atom$TextEditor, setModel(model: atom$TextEditor): void, pixelPositionForBufferPosition( bufferPosition: atom$PointLike, ): {top: number, left: number}, pixelPositionForScreenPosition(screenPosition: atom$Point): { left: number, top: number, }, setScrollTop(scrollTop: number): void, getScrollTop(): number, setScrollLeft(scrollLeft: number): void, getScrollLeft(): number, getScrollHeight(): number, getHeight(): number, onDidChangeScrollTop(callback: (scrollTop: number) => mixed): IDisposable, onDidChangeScrollLeft(callback: (scrollLeft: number) => mixed): IDisposable, // Called when the editor is attached to the DOM. onDidAttach(callback: () => mixed): IDisposable, // Called when the editor is detached from the DOM. onDidDetach(callback: () => mixed): IDisposable, measureDimensions(): void, // Undocumented Methods // Returns a promise that resolves after the next update. getNextUpdatePromise(): Promise, // `undefined` means no explicit width. `null` sets a zero width (which is almost certainly a // mistake) so we don't allow it. setWidth(width: number | void): void, } declare class atom$ViewProvider { modelConstructor: Function, } declare class atom$ViewRegistry { // Methods addViewProvider( modelConstructor: any, createView?: (...args: Array) => ?HTMLElement ): IDisposable, getView(textEditor: atom$TextEditor): atom$TextEditorElement, getView(notification: atom$Notification): HTMLElement, getView(gutter: atom$Gutter): HTMLElement, getView(panel: atom$Panel): HTMLElement, getView(workspace: atom$Workspace): HTMLElement, getView(object: Object): HTMLElement, providers: Array, } type atom$WorkspaceAddPanelOptions = { item: Object, visible?: boolean, priority?: number, className?: string, }; type atom$TextEditorParams = { buffer?: atom$TextBuffer, lineNumberGutterVisible?: boolean, }; type DestroyPaneItemEvent = { item: atom$PaneItem, pane: atom$Pane, index: number, }; type AddPaneItemEvent = { item: atom$PaneItem, pane: atom$Pane, index: number, }; type OnDidOpenEvent = { uri: string, item: mixed, pane: atom$Pane, index: number, }; type AddTextEditorEvent = { textEditor: atom$TextEditor, pane: atom$Pane, index: number, }; type atom$WorkspaceOpenOptions = { activatePane?: ?boolean, activateItem?: ?boolean, initialLine?: ?number, initialColumn?: ?number, pending?: ?boolean, split?: ?string, searchAllPanes?: ?boolean, location?: atom$PaneLocation, } declare class atom$Workspace { // Event Subscription observePanes(cb: (pane: atom$Pane) => void): IDisposable, observeTextEditors(callback: (editor: atom$TextEditor) => mixed): IDisposable, observeActiveTextEditor(callback: (editor: ?atom$TextEditor) => mixed): IDisposable, onDidAddTextEditor(callback: (event: AddTextEditorEvent) => mixed): IDisposable, onDidChangeActivePaneItem(callback: (item: mixed) => mixed): IDisposable, onDidDestroyPaneItem(callback: (event: DestroyPaneItemEvent) => mixed): IDisposable, onDidAddPaneItem(callback: (event: AddPaneItemEvent) => mixed): IDisposable, observeActivePaneItem(callback: (item: ?mixed) => mixed): IDisposable, onDidStopChangingActivePaneItem(callback: (item: ?mixed) => mixed): IDisposable, observePaneItems(callback: (item: mixed) => mixed): IDisposable, onWillDestroyPaneItem( callback: (event: {item: mixed, pane: mixed, index: number}) => mixed ): IDisposable, onDidOpen(callback: (event: OnDidOpenEvent) => mixed): IDisposable, getElement(): HTMLElement, // Opening open( uri?: string, options?: atom$WorkspaceOpenOptions, ): Promise, openURIInPane( uri?: string, pane: atom$Pane, options?: { initialLine?: number, initialColumn?: number, activePane?: boolean, searchAllPanes?: boolean, } ): Promise, isTextEditor(item: ?mixed): boolean, /* Optional method because this was added post-1.0. */ buildTextEditor: ((params: ?atom$TextEditorParams) => atom$TextEditor), /* Optional method because this was added in 1.9.0 */ handleGrammarUsed?: (grammar: atom$Grammar) => void, reopenItem(): Promise, addOpener(callback: (uri: string) => any): IDisposable, hide(uriOrItem: string | Object): void, toggle(uriOrItem: string | Object): void, // Pane Containers getPaneContainers(): Array, paneContainerForItem(item: ?mixed): ?atom$AbstractPaneContainer, // Pane Items getPaneItems(): Array, getActivePaneItem(): ?Object, getActivePaneContainer(): atom$PaneContainer, getTextEditors(): Array, getActiveTextEditor(): ?atom$TextEditor, createItemForURI(uri: string, options: atom$WorkspaceOpenOptions): atom$PaneItem, // Panes getPanes(): Array, getActivePane(): atom$Pane, activateNextPane(): boolean, activatePreviousPane(): boolean, paneForURI(uri: string): atom$Pane, paneForItem(item: mixed): ?atom$Pane, paneContainers: {[location: atom$PaneLocation]: atom$AbstractPaneContainer}, // Panels panelContainers: {[location: string]: atom$PanelContainer}, getBottomPanels(): Array, addBottomPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel, getLeftPanels(): Array, addLeftPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel, getRightPanels(): Array, addRightPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel, getTopPanels(): Array, addTopPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel, getModalPanels(): Array, addModalPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel, getHeaderPanels(): Array, addHeaderPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel, getLeftDock(): atom$Dock, getRightDock(): atom$Dock, getBottomDock(): atom$Dock, getCenter(): atom$WorkspaceCenter, // Searching and Replacing scan( regex: RegExp, options: { paths?: Array, onPathsSearched?: (numSearched: number) => mixed, leadingContextLineCount?: number, trailingContextLineCount?: number, }, iterator: ( ?{ filePath: string, matches: Array<{ leadingContextLines: Array, lineText: string, lineTextOffset: number, range: atom$RangeLike, matchText: string, trailingContextLines: Array, }>, }, Error, ) => mixed, ): Promise, destroyActivePaneItemOrEmptyPane(): void, destroyActivePaneItem(): void, } declare class atom$AbstractPaneContainer { activate(): void, getLocation(): atom$PaneLocation, getElement(): HTMLElement, isVisible(): boolean, show(): void, hide(): void, getActivePane(): atom$Pane, getPanes(): Array, onDidAddPaneItem((item: {item: Object}) => void): IDisposable, observePanes(cb: (pane: atom$Pane) => void): IDisposable, state: { size: number, } } declare class atom$Dock extends atom$AbstractPaneContainer { // This is a woefully incomplete list, items can be added as needed from // https://github.com/atom/atom/blob/master/src/dock.js toggle(): void, } declare class atom$WorkspaceCenter extends atom$AbstractPaneContainer { activate(): void; // Pane Items getPaneItems(): Array, getActivePaneItem(): ?atom$PaneItem, getTextEditors(): Array, getActiveTextEditor(): ?atom$TextEditor, observeActivePaneItem(callback: atom$PaneItem => mixed): IDisposable; onDidChangeActivePaneItem(callback: (item: mixed) => mixed): IDisposable; onDidAddTextEditor( callback: (item: { textEditor: atom$TextEditor, pane: atom$Pane, index: number, }) => mixed, ): IDisposable; // This should be removed soon anyway, it's currently deprecated. paneContainer: Object; } /** * Extended Classes */ declare class atom$BufferedNodeProcess { } declare class atom$BufferedProcess { // Event Subscription onWillThrowError( callback: (errorObject: {error: Object, handle: mixed}) => mixed ): IDisposable, // Helper Methods kill(): void, } declare class atom$Clipboard { // Methods write(text: string, metadata?: mixed): void, read(): string, readWithMetadata(): { metadata: ?mixed, text: string, }, } declare class atom$ContextMenuManager { add(itemsBySelector: {[cssSelector: string]: Array}): IDisposable, itemSets: Array, // Undocumented methods showForEvent(event: Event): void, templateForEvent(event: Event): Array, } declare class atom$ContextMenuItemSet { items: Array, selector: string, } type atom$ContextMenuItem = { command?: string, created?: (event: MouseEvent) => void, enabled?: boolean, label?: string, shouldDisplay?: (event: MouseEvent) => boolean, submenu?: Array, type?: string, visible?: boolean, }; type atom$Deserializer = { name: string, deserialize: (state: Object) => mixed, }; declare class atom$DeserializerManager { add(...deserializers: Array): IDisposable, deserialize(state: Object, params?: Object): mixed, } // Apparently it can sometimes include a `code` property. declare class atom$GetEntriesError extends Error { code?: string, } declare class atom$Directory { constructor(dirname?: string): atom$Directory, symlink: boolean, // Construction create(mode?: number): Promise, // Event Subscription onDidChange(callback: () => mixed): IDisposable, // Directory Metadata isFile(): boolean, isDirectory(): boolean, exists():Promise, // Managing Paths getPath(): string, getBaseName(): string, relativize(fullPath: string): string, // Event Subscription onDidRename(callback: () => void): IDisposable, onDidDelete(callback: () => void): IDisposable, // Traversing getParent(): atom$Directory, getFile(filename: string): atom$File, getSubdirectory(dirname: string): atom$Directory, getEntries( callback: ( error: ?atom$GetEntriesError, entries: ?Array, ) => mixed): void, contains(path: string): boolean, } // These are the methods called on a file by atom-text-buffer interface atom$Fileish { existsSync(): boolean, setEncoding(encoding: string): void, getEncoding(): ?string, onDidRename(callback: () => void): IDisposable, onDidDelete(callback: () => void): IDisposable, onDidChange(callback: () => void): IDisposable, onWillThrowWatchError(callback: () => mixed): IDisposable, getPath(): string, getBaseName(): string, createReadStream(): stream$Readable, createWriteStream(): stream$Writable, } declare class atom$File /* implements atom$Fileish */ { constructor(filePath?: string, symlink?: boolean): atom$File, symlink: boolean, // Construction create(): Promise, // File Metadata isFile(): boolean, isDirectory(): boolean, exists(): Promise, existsSync(): boolean, setEncoding(encoding: string): void, getEncoding(): string, // Event Subscription onDidRename(callback: () => void): IDisposable, onDidDelete(callback: () => void): IDisposable, onDidChange(callback: () => void): IDisposable, onWillThrowWatchError(callback: () => mixed): IDisposable, // Managing Paths getPath(): string, getBaseName(): string, // Traversing getParent(): atom$Directory, // Reading and Writing read(flushCache?: boolean): Promise, write(text: string): Promise, writeSync(text: string): void, createReadStream(): stream$Readable, createWriteStream(): stream$Writable, } declare class atom$GitRepository extends atom$Repository { // Unofficial API. statuses: {[filePath: string]: number}, // Return the `git-utils` async repo. getRepo(): atom$GitRepositoryInternal, } declare class atom$Grammar { name: string, scopeName: string, tokenizeLines(text: string): Array>, tokenizeLine(line: string, ruleStack: mixed, firstLine: boolean): { line: string, tags: Array, // Dynamic property: invoking it will incur additional overhead tokens: Array, ruleStack: mixed }, } type atom$GrammarToken = { value: string, scopes: Array, }; declare class atom$GrammarRegistry { // Event Subscription onDidAddGrammar(callback: (grammar: atom$Grammar) => void): IDisposable, // Managing Grammars grammarForScopeName(scopeName: string): ?atom$Grammar, removeGrammarForScopeName(scopeName: string): ?atom$Grammar, loadGrammarSync(grammarPath: string): atom$Grammar, selectGrammar(filePath: string, fileContents: string): atom$Grammar, autoAssignLanguageMode(buffer: atom$TextBuffer): void, assignLanguageMode(buffer: atom$TextBuffer, languageId: string): void, // Extended getGrammarScore(grammar: atom$Grammar, filePath: string, contents?: string): number, forEachGrammar(callback: (grammar: atom$Grammar) => mixed): void, // Private API clear(): IDisposable, } declare class atom$HistoryManager { removeProject(paths: Array): void, getProjects(): Array, } declare class atom$HistoryProject { paths: Array; lastOpened: Date; } // https://github.com/atom/atom-keymap/blob/18f00ac307de5770bb8f98958bd9a13ecffa9e68/src/key-binding.coffee declare class atom$KeyBinding { cachedKeyups: ?Array, command: string, index: number, keystrokeArray : Array, keystrokeCount: number, keystrokes: string, priority: number, selector: string, source: string, specificity: number, matches(keystroke: string): boolean, compare(keybinding: atom$KeyBinding): number, getKeyups(): ?Array, matchesKeystrokes(userKeystrokes: Array): boolean | 'exact' | 'partial' | 'pendingKeyup', } declare class atom$KeymapManager { // Event Subscription onDidMatchBinding(callback: (event: { keystrokes: string, binding: atom$KeyBinding, keyboardEventTarget: HTMLElement, }) => mixed): IDisposable, onDidPartiallyMatchBinding(callback: (event: { keystrokes: string, partiallyMatchedBindings: atom$KeyBinding, keyboardEventTarget: HTMLElement, }) => mixed): IDisposable, onDidFailToMatchBinding(callback: (event: { keystrokes: string, partiallyMatchedBindings: atom$KeyBinding, keyboardEventTarget: HTMLElement, }) => mixed): IDisposable, onDidFailToReadFile(callback: (error: { message: string, stack: string, }) => mixed): IDisposable, // Adding and Removing Bindings add(source: string, bindings: Object): IDisposable, removeBindingsFromSource(source: string): void, // Accessing Bindings getKeyBindings(): Array, findKeyBindings(params: { keystrokes?: string, command?: string, target?: HTMLElement, }): Array, // Managing Keymap Files loadKeymap(path: string, options?: {watch: boolean}): void, watchKeymap(path: string): void, // Managing Keyboard Events handleKeyboardEvent(event: Event): void, keystrokeForKeyboardEvent(event: Event): string, getPartialMatchTimeout(): number, static buildKeydownEvent( key: string, options: { target: HTMLElement, alt?: boolean, cmd?: boolean, ctrl?: boolean, shift?: boolean, }, ): Event, } declare class atom$MenuManager { add(items: Array): IDisposable, update(): void, // Private API template: Array, } type atom$ProjectSpecification = { originPath: string, paths?: Array, config?: {[string]: mixed} }; declare class atom$Project { // Event Subscription onDidChangePaths(callback: (projectPaths: Array) => mixed): IDisposable, onDidAddBuffer(callback: (buffer: atom$TextBuffer) => mixed): IDisposable, onDidReplace((settings: atom$ProjectSpecification) => mixed): IDisposable, observeBuffers(callback: (buffer: atom$TextBuffer) => mixed): IDisposable, replace?: (newSettings: atom$ProjectSpecification) => void, // Accessing the git repository getRepositories(): Array, repositoryForDirectory(directory: atom$Directory): Promise, // Managing Paths getPaths(): Array, addPath(projectPath: string, options?: { emitEvent?: boolean, exact?: boolean, mustExist?: boolean, }): void, setPaths(paths: Array): void, removePath(projectPath: string): void, getDirectories(): Array, relativizePath(relativizePath?: string): Array, // [projectPath: ?string, relativePath: string] relativize(filePath: string): string, contains(path: string): boolean, // Private API findBufferForPath(path: string): ?atom$TextBuffer, addBuffer(buffer: atom$TextBuffer): void, removeBuffer(buffer: atom$TextBuffer): void, getBuffers(): Array, } type TextBufferScanIterator = (arg: { match: Array, matchText: string, range: atom$Range, stop(): void, replace(replacement: string): void, }) => void; // This happens to be a number but it would be better if the type could be entirely opaque. All you // need to know is that if something needs a checkpoint you should only pass it values received from // TextBuffer::createCheckpoint type atom$TextBufferCheckpoint = number; // TextBuffer did-change/will-change type atom$TextEditEvent = { oldRange: atom$Range, newRange: atom$Range, oldText: string, newText: string, }; type atom$AggregatedTextEditEvent = { changes: Array, }; declare class atom$LanguageMode { getLanguageId(): string, } declare class atom$TextBuffer { constructor(text?: string): atom$TextBuffer, constructor(params?: { filePath?: string, text?: string, }): atom$TextBuffer, file: ?atom$File, // Mixin static deserialize: (state: Object, params: Object) => mixed, static load: (file: string | atom$Fileish, params: Object) => Promise, setFile(file: atom$Fileish): void, // Events onWillChange(callback: () => mixed): IDisposable, onDidChangeText(callback: (event: atom$AggregatedTextEditEvent) => mixed): IDisposable, onDidStopChanging(callback: () => mixed): IDisposable, onDidConflict(callback: () => mixed): IDisposable, onDidChangeModified(callback: () => mixed): IDisposable, onDidUpdateMarkers(callback: () => mixed): IDisposable, onDidCreateMarker(callback: () => mixed): IDisposable, onDidChangePath(callback: () => mixed): IDisposable, onDidChangeEncoding(callback: () => mixed): IDisposable, onWillSave(callback: () => mixed): IDisposable, onDidSave(callback: (event: {path: string}) => mixed): IDisposable, onDidDelete(callback: () => mixed): IDisposable, onWillReload(callback: () => mixed): IDisposable, onDidReload(callback: () => mixed): IDisposable, onDidDestroy(callback: () => mixed): IDisposable, onWillThrowWatchError(callback: () => mixed): IDisposable, // File Details // DO NOT USE (T21363106): Doesn't work with remote text buffers! // setPath(filePath: string): void, getPath(): ?string, setEncoding(encoding: string): void, getEncoding(): string, getUri(): string, getId(): string, getLanguageMode(): atom$LanguageMode, // Reading Text isEmpty(): boolean, getText(): string, getTextInRange(range: atom$RangeLike): string, getLineCount(): number, getLines(): Array, getLastLine(): string, lineForRow(row: number): string, lineEndingForRow(row: number): string, lineLengthForRow(row: number): number, isRowBlank(row: number): boolean, previousNonBlankRow(startRow: number): ?number, nextNonBlankRow(startRow: number): ?number, // Mutating Text setText: (text: string) => atom$Range, setTextInRange(range: atom$RangeLike, text: string, options?: Object): atom$Range, setTextViaDiff(text: string): void, insert( position: atom$Point, text: string, options?: { normalizeLineEndings?: boolean, undo?: string, }, ): atom$Range, append(text: string, options: ?{ normalizeLineEndings?: boolean, undo?: string, }): atom$Range, delete(range: atom$Range): atom$Range, deleteRows(startRow: number, endRow: number): atom$Range, // History undo(): void, redo(): void, transact(fn: () => mixed, _: void): void, transact(groupingInterval: number, fn: () => mixed): void, clearUndoStack(): void, createCheckpoint(): atom$TextBufferCheckpoint, revertToCheckpoint(checkpoint: atom$TextBufferCheckpoint): boolean, groupChangesSinceCheckpoint(checkpoint: atom$TextBufferCheckpoint): boolean, // TODO describe the return type more precisely. getChangesSinceCheckpoint(checkpoint: atom$TextBufferCheckpoint): Array, // Search And Replace scan(regex: RegExp, iterator: TextBufferScanIterator): void, scanInRange(regex: RegExp, range: atom$Range, iterator: TextBufferScanIterator): void, backwardsScanInRange(regex: RegExp, range: atom$Range, iterator: TextBufferScanIterator): void, // Buffer Range Details getLastRow(): number, getRange(): atom$Range, rangeForRow(row: number, includeNewLine?: boolean): atom$Range, getLength(): number, // Position/Index mapping characterIndexForPosition(position: atom$PointLike): number, positionForCharacterIndex(index: number): atom$Point, // Buffer Operations reload(): void, load(): Promise, save(): Promise, isInConflict(): boolean, isModified(): boolean, // Private APIs cachedDiskContents: ?string, emitter: atom$Emitter, refcount: number, loaded: boolean, wasModifiedBeforeRemove: boolean, finishLoading(): atom$TextBuffer, updateCachedDiskContents(flushCache?: boolean, callback?: () => mixed): Promise, emitModifiedStatusChanged(changed: boolean): void, destroy(): void, isDestroyed(): boolean, applyChange: () => void, shouldDestroyOnFileDelete?: () => boolean, } declare class atom$Notification { // Event Subscription onDidDismiss(callback: () => mixed): IDisposable, onDidDisplay(callback: () => mixed): IDisposable, // Methods getType(): string, getMessage(): string, getOptions(): Object, dismiss(): void, isDismissed(): boolean, } type atom$NotificationButton = { text: string, className?: string, onDidClick?: () => mixed, }; type atom$NotificationOptions = { detail?: string, dismissable?: boolean, description?: string, icon?: string, stack?: string, buttons?: Array, }; declare class atom$NotificationManager { // Events onDidAddNotification(callback: (notification: atom$Notification) => void): IDisposable, // Adding Notifications add(type: string, message: string, options?: atom$NotificationOptions): atom$Notification, addSuccess(message: string, options?: atom$NotificationOptions): atom$Notification, addInfo(message: string, options?: atom$NotificationOptions): atom$Notification, addWarning(message: string, options?: atom$NotificationOptions): atom$Notification, addError(message: string, options?: atom$NotificationOptions): atom$Notification, addFatalError(message: string, options?: atom$NotificationOptions): atom$Notification, addNotification(notification: atom$Notification): atom$Notification, // Getting Notifications getNotifications(): Array, } // The items in this declaration are available off of `require('atom')`. // This list is not complete. declare module 'atom' { declare var BufferedNodeProcess: typeof atom$BufferedNodeProcess; declare var BufferedProcess: typeof atom$BufferedProcess; declare var CompositeDisposable: typeof atom$CompositeDisposable; declare var Directory: typeof atom$Directory; declare var Disposable: typeof atom$Disposable; declare var Emitter: typeof atom$Emitter; declare var File: typeof atom$File; declare var GitRepository: typeof atom$GitRepository; declare var Notification: typeof atom$Notification; declare var Point: typeof atom$Point; declare var Range: typeof atom$Range; declare var TextBuffer: typeof atom$TextBuffer; declare var TextEditor: typeof atom$TextEditor; } // Make sure that common types can be referenced without the `atom$` prefix // in type declarations. declare var Cursor: typeof atom$Cursor; declare var Panel: typeof atom$Panel; declare var TextEditor: typeof atom$TextEditor; type atom$UnhandledErrorEvent = { originalError: Object, message: string, url: string, line: number, column: number, }; // The properties of this type match the properties of the `atom` global. // This list is not complete. type AtomGlobal = { // Properties appVersion: string, atomScriptMode: ?boolean, // Added by nuclide-atom-script. clipboard: atom$Clipboard, commands: atom$CommandRegistry, config: atom$Config, contextMenu: atom$ContextMenuManager, applicationDelegate: atom$applicationDelegate, deserializers: atom$DeserializerManager, grammars: atom$GrammarRegistry, history: atom$HistoryManager, keymaps: atom$KeymapManager, menu: atom$MenuManager, notifications: atom$NotificationManager, packages: atom$PackageManager, styles: atom$StyleManager, themes: atom$ThemeManager, textEditors: atom$TextEditorRegistry, tooltips: atom$TooltipManager, views: atom$ViewRegistry, workspace: atom$Workspace, project: atom$Project, devMode: boolean, // Event Subscription onWillThrowError(callback: (event: atom$UnhandledErrorEvent) => mixed): IDisposable, onDidThrowError(callback: (event: atom$UnhandledErrorEvent) => mixed): IDisposable, whenShellEnvironmentLoaded(callback: () => mixed): IDisposable, // Atom Details inDevMode(): boolean, inSafeMode(): boolean, inSpecMode(): boolean, getVersion(): string, isReleasedVersion(): boolean, getWindowLoadTime(): number, // This is an undocumented way to reach the Electron BrowserWindow. // Use `electron.remote.getCurrentWindow` instead. getCurrentWindow: void, // Messaging the User +confirm: & (( { message: string, detail?: string, buttons?: Array, }, (number) => void, ) => void) // The synchronous form. You really shouldn't use this. & (({ message: string, detailedMessage?: string, buttons?: Array | {[buttonName: string]: () => mixed}, }) => ?number), open(params: { pathsToOpen?: Array, newWindow?: boolean, devMode?: boolean, safeMode?: boolean, }): void, reload(): void, restartApplication(): void, // Undocumented Methods getConfigDirPath(): string, showSaveDialogSync(options: Object): string, loadState(): Promise, getLoadSettings(): Object, }; declare var atom: AtomGlobal; type RepositoryDidChangeStatusCallback = (event: {path: string, pathStatus: number}) => mixed; type RepositoryLineDiff = { oldStart: number, newStart: number, oldLines: number, newLines: number, }; // Taken from the interface of [`GitRepository`][1], which is also implemented by // `HgRepositoryClient`. // // [1]: https://github.com/atom/atom/blob/v1.7.3/src/git-repository.coffee declare class atom$Repository { constructor(path: string, options?: {refreshOnWindowFocus?: boolean}): void, // Event Subscription onDidChangeStatus: (callback: RepositoryDidChangeStatusCallback) => IDisposable, onDidChangeStatuses: (callback: () => mixed) => IDisposable, // Repository Details getType: () => string, getPath: () => string, getWorkingDirectory: () => string, isProjectAtRoot: () => boolean, relativize: (aPath: string) => string, getOriginURL: (aPath: ?string) => ?string, // Reading Status isPathModified: (aPath: string) => boolean, isPathNew: (aPath: string) => boolean, isPathIgnored: (aPath: string) => boolean, getDirectoryStatus: (aPath: string) => number, getPathStatus: (aPath: string) => number, getCachedPathStatus: (aPath: string) => ?number, isStatusModified: (status: number) => boolean, isStatusNew: (status: number) => boolean, refreshStatus: () => Promise, // Retrieving Diffs getDiffStats: (filePath: string) => {added: number, deleted: number}, getLineDiffs: (aPath: string, text: string) => Array, // Checking Out checkoutHead: (aPath: string) => boolean, checkoutReference: (reference: string, create: boolean) => Promise, // Event Subscription onDidDestroy(callback: () => mixed): IDisposable, isDestroyed(): boolean, } declare class atom$GitRepositoryInternal { // Reading Status isStatusModified: (status: number) => boolean, isStatusNew: (status: number) => boolean, isStatusIgnored: (status: number) => boolean, isStatusStaged: (status: number) => boolean, isStatusDeleted: (status: number) => boolean, } // One of text or snippet is required. // TODO(hansonw): use a union + intersection type type atom$AutocompleteSuggestion = { text?: string, snippet?: string, displayText?: string, replacementPrefix?: string, type?: ?string, leftLabel?: ?string, leftLabelHTML?: ?string, rightLabel?: ?string, rightLabelHTML?: ?string, className?: ?string, iconHTML?: ?string, description?: ?string, descriptionMarkdown?: ?string, descriptionMoreURL?: ?string, }; type atom$AutocompleteRequest = { editor: TextEditor, bufferPosition: atom$Point, scopeDescriptor: atom$ScopeDescriptor, prefix: string, activatedManually?: boolean, }; type atom$AutocompleteProvider = { +selector: string, +getSuggestions: ( request: atom$AutocompleteRequest, ) => Promise> | ?Array, +getSuggestionDetailsOnSelect?: ( suggestion: atom$AutocompleteSuggestion ) => Promise, +disableForSelector?: string, +inclusionPriority?: number, +excludeLowerPriority?: boolean, +suggestionPriority?: number, +filterSuggestions?: boolean, +disposable?: () => void, +onDidInsertSuggestion?: ( insertedSuggestion: atom$SuggestionInsertedRequest, ) => void, }; type atom$SuggestionInsertedRequest = { +editor: atom$TextEditor, +triggerPosition: atom$Point, +suggestion: atom$AutocompleteSuggestion, }; // https://github.com/atom/autocomplete-plus/blob/master/README.md#the-watcheditor-api type atom$AutocompleteWatchEditor = ( editor: atom$TextEditor, labels?: Array, ) => IDisposable; // Undocumented API. declare class atom$Token { value: string, matchesScopeSelector(selector: string): boolean, } declare class atom$Selection { clear(): void, getText(): string, getBufferRange(): atom$Range, insertText( text: string, options?: { select?: boolean, autoIndent?: boolean, autoIndentNewLine?: boolean, autoDecreaseIdent?: boolean, normalizeLineEndings?: boolean, undo?: boolean, }, ): string, } declare class atom$PanelContainer { dock: atom$Dock, element: HTMLElement, emitter: atom$Emitter, location: atom$PaneLocation, panels: Array, subscriptions: atom$CompositeDisposable, viewRegistry: atom$ViewRegistry, getPanels(): Array, }; ================================================ FILE: decls/hyperclick.js ================================================ /* @flow */ declare module 'atom-ide-ui/hyperclick' { import type { Point, Range, TextEditor } from 'atom' declare type HyperclickProvider = { // Use this to provide a suggestion for single-word matches. // Optionally set `wordRegExp` to adjust word-matching. getSuggestionForWord?: ( textEditor: TextEditor, text: string, range: Range, ) => Promise, wordRegExp?: RegExp, // Use this to provide a suggestion if it can have non-contiguous ranges. // A primary use-case for this is Objective-C methods. getSuggestion?: ( textEditor: TextEditor, position: Point, ) => Promise, // The higher this is, the more precedence the provider gets. Defaults to 0. priority?: number, // Must be unique. Used for analytics. providerName?: string, }; declare type HyperclickSuggestion = { // The range(s) to underline to provide as a visual cue for clicking. range: ?Range | ?Array, // The function to call when the underlined text is clicked. callback: (() => mixed) | Array<{rightLabel?: string, title: string, callback: () => mixed}>, }; } ================================================ FILE: decls/jasmine-atom.js ================================================ /* @flow */ declare function waitsForPromise( optionsOrFunc: {timeout?: number, shouldReject?: boolean, label?: string} | () => Promise, func?: () => Promise ): void; ================================================ FILE: decls/jasmine.js ================================================ /* @flow */ declare var jasmine: Object; declare function it(name: string, callback: (() => void)): void; declare function fit(name: string, callback: (() => void)): void; declare function spyOn(object: Object, property: string): Object; declare function expect(value: any): Object; declare function describe(name: string, callback: (() => void)): void; declare function fdescribe(name: string, callback: (() => void)): void; declare function beforeEach(callback: (() => void)): void; declare function afterEach(callback: (() => void)): void; ================================================ FILE: decls/linter.js ================================================ /* @flow */ declare module 'linter' { import type { Point, Range } from 'atom' declare type LinterProvider = { } declare type Message = { location: { file: string, position: Range, }, reference: ?{ file: string, position?: Point, }, url?: string, icon?: string, excerpt: string, severity: 'error' | 'warning' | 'info', solutions?: Array<{ title?: string, position: Range, priority?: number, currentText?: string, replaceWith: string, } | { title?: string, position: Range, priority?: number, apply: (() => any), }>, description?: string | (() => Promise | string), linterName?: string, } } ================================================ FILE: decls/outline.js ================================================ /* @flow */ declare module 'atom-ide-ui/outline' { import type { Point, TextEditor } from 'atom' declare type TokenKind = | 'keyword' | 'class-name' | 'constructor' | 'method' | 'param' | 'string' | 'whitespace' | 'plain' | 'type'; declare type TextToken = { kind: TokenKind, value: string, }; declare type TokenizedText = Array; declare type OutlineTreeKind = | 'file' | 'module' | 'namespace' | 'package' | 'class' | 'method' | 'property' | 'field' | 'constructor' | 'enum' | 'interface' | 'function' | 'variable' | 'constant' | 'string' | 'number' | 'boolean' | 'array'; declare type OutlineTree = { icon?: string, // from atom$Octicon (that type's not allowed over rpc so we use string) kind?: OutlineTreeKind, // kind you can pass to the UI for theming // Must be one or the other. If both are present, tokenizedText is preferred. plainText?: string, tokenizedText?: TokenizedText, // If user has atom-ide-outline-view.nameOnly then representativeName is used instead. representativeName?: string, startPosition: Point, endPosition?: Point, landingPosition?: Point, children: Array, }; declare type Outline = { outlineTrees: Array, }; declare type OutlineProvider = { name: string, // If there are multiple providers for a given grammar, the one with the highest priority will be // used. priority: number, grammarScopes: Array, updateOnEdit?: boolean, getOutline(editor: TextEditor): Promise, }; } ================================================ FILE: lib/coverage-view.js ================================================ /* @flow */ import type { CoverageObject } from './types' class CoverageView extends HTMLElement { tooltipDisposable: ?IDisposable = null; initialize(): void { this.classList.add('inline-block') this.addEventListener('click', () => { atom.config.set('flow-ide.showUncovered', !atom.config.get('flow-ide.showUncovered')) }) } update(json: CoverageObject): void { const covered: number = json.expressions.covered_count const uncovered: number = json.expressions.uncovered_count const total: number = covered + uncovered const percent: number = total === 0 ? 100 : Math.round((covered / total) * 100) this.textContent = `Flow Coverage: ${percent}%` if (this.tooltipDisposable) { this.tooltipDisposable.dispose() } this.classList.remove('flow-ide-hide') this.tooltipDisposable = atom.tooltips.add(this, { title: `Covered ${percent}% (${covered} of ${total} expressions)
Click to toggle uncovered code`, }) } reset() { this.classList.add('flow-ide-hide') this.textContent = '' if (this.tooltipDisposable) { this.tooltipDisposable.dispose() } } destroy(): void { if (this.tooltipDisposable) { this.tooltipDisposable.dispose() } } } export default document.registerElement('flow-ide-coverage', { prototype: CoverageView.prototype, extends: 'a', }) ================================================ FILE: lib/helpers.js ================================================ /* @flow */ import { findCached, findCachedAsync } from 'atom-linter' import * as Path from 'path' const executable = process.platform === 'win32' ? 'flow.cmd' : 'flow' export const defaultFlowFile = Path.resolve(__dirname, '..', 'vendor', '.flowconfig') export const defaultFlowBinLocation = Path.join('node_modules', '.bin', executable) export const grammarScopes = [ 'source.js', 'source.jsx', 'source.js.jsx', 'source.flow', 'flow-javascript', ] export async function getExecutablePath(fileDirectory: string): Promise { return ( (atom.config.get('flow-ide.executablePath'): any) || await findCachedAsync(fileDirectory, defaultFlowBinLocation) || 'flow' ) } export function getExecutablePathSync(fileDirectory: string): string { return ( (atom.config.get('flow-ide.executablePath'): any) || findCached(fileDirectory, defaultFlowBinLocation) || 'flow' ) } export async function getConfigFile(fileDirectory: string): Promise { return findCachedAsync(fileDirectory, '.flowconfig') } ================================================ FILE: lib/index.js ================================================ /* @flow */ import { CompositeDisposable } from 'atom' import type { TextEditor, Point, Range } from 'atom' import { shouldTriggerAutocomplete } from 'atom-autocomplete' import { exec } from 'atom-linter' import type { HyperclickProvider, HyperclickSuggestion } from 'atom-ide-ui/hyperclick' import type { OutlineProvider } from 'atom-ide-ui/outline' import * as Path from 'path' import type { Message } from 'linter' import prettyPrintTypes from 'flow-language-server/lib/pkg/nuclide-flow-rpc/lib/prettyPrintTypes' import score from 'sb-string_score' import * as semver from 'semver' import CoverageView from './coverage-view' import * as LinterV1 from './linter/v1' import * as LinterV2 from './linter/v2' import * as Helpers from './helpers' import { toOutline } from './outline' import type { OutlineOptions } from './outline/types' import type { CoverageObject, TypeAtPosObject } from './types' export const INIT_MESSAGE = 'flow server' export const RECHECKING_MESSAGE = 'flow is' type Server = { version: ?Promise, } const servers: Map = new Map() export class Flow { subscriptions: CompositeDisposable executablePath: string onlyIfAppropriate: boolean hyperclickPriority: number showUncovered: boolean coverageView: any coverages: WeakMap statusBar: ?atom$StatusBarTile activate() { // eslint-disable-next-line global-require require('atom-package-deps').install('flow-ide', true) this.subscriptions = new CompositeDisposable() this.subscriptions.add(atom.config.observe('flow-ide.executablePath', (executablePath) => { this.executablePath = executablePath })) this.subscriptions.add(atom.config.observe('flow-ide.onlyIfAppropriate', (onlyIfAppropriate) => { this.onlyIfAppropriate = onlyIfAppropriate })) let restartNotification this.subscriptions.add(atom.config.observe('flow-ide.hyperclickPriority', (hyperclickPriority) => { if (this.hyperclickPriority != null) { if (hyperclickPriority !== this.hyperclickPriority && restartNotification === undefined) { restartNotification = atom.notifications.addSuccess('Restart atom to update flow-ide priority?', { dismissable: true, buttons: [{ text: 'Restart', onDidClick: () => atom.restartApplication(), }], }) restartNotification.onDidDismiss(() => { restartNotification = undefined }) } } this.hyperclickPriority = hyperclickPriority })) this.subscriptions.add(atom.config.observe('flow-ide.showUncovered', (showUncovered) => { this.showUncovered = showUncovered // lint again so that the coverage actually updates const editor = atom.workspace.getActiveTextEditor() if (!editor) { return } const view = atom.views.getView(editor) if (view) { atom.commands.dispatch(view, 'linter:lint') } })) this.subscriptions.add(atom.workspace.onDidChangeActivePaneItem((item: any): void => { if (this.coverageView) { const coverage = this.coverages.get(item) if (coverage) { this.coverageView.update(coverage) } else { this.coverageView.reset() } } })) this.coverages = new WeakMap() } async getConfigFile(fileDirectory: string): Promise { const configFile = await Helpers.getConfigFile(fileDirectory) if (configFile != null) { const dir = Path.dirname(configFile) if (!servers.has(dir)) { servers.set(Path.dirname(configFile), { version: null }) } } return configFile } deactivate() { this.subscriptions.dispose() if (atom.config.get('flow-ide.stopServer')) { this.stopServer() } } stopServer() { servers.forEach((server, rootDirectory) => { const executable = Helpers.getExecutablePathSync(rootDirectory) exec(executable, ['stop'], { cwd: rootDirectory, timeout: 60 * 1000, detached: true, ignoreExitCode: true, }).catch(() => null) // <-- ignore all errors }) } async getVersion(configFile: string, executable: string): Promise { const dir = Path.dirname(configFile) const server = servers.get(dir) if (server && server.version != null) { return server.version } const p = exec(executable, ['version', '--json'], { timeout: 60 * 1000, uniqueKey: 'flow-ide-version', ignoreExitCode: true, }).then((result) => { if (result === null) { // reset the version because something went wrong... servers.set(dir, { ...server, version: null }) return null } return JSON.parse(result).semver }) .catch((error) => { this.handleError(error, configFile) return this.getVersion(configFile, executable) }) servers.set(dir, { ...server, version: p }) return p } provideLinter(): Object[] { return [this.provideStatusLinter(), this.provideCoverageLinter()] } provideStatusLinter(): Object { const linter = { name: 'Flow IDE', scope: 'project', grammarScopes: Helpers.grammarScopes, lintsOnChange: false, // eslint-disable-next-line arrow-parens lint: async (textEditor: TextEditor) => { const filePath = textEditor.getPath() if (filePath == null) { return [] } const fileDirectory = Path.dirname(filePath) let configFile = await this.getConfigFile(fileDirectory) if (configFile == null) { if (this.onlyIfAppropriate) { return [] } configFile = ((configFile: any): string) } const executable = await Helpers.getExecutablePath(fileDirectory) const version = await this.getVersion(configFile, executable) if (version == null) { return null } return semver.gte(version, '0.66.0') ? this.lintV2(textEditor, filePath, fileDirectory, configFile, executable) : this.lintV1(textEditor, filePath, fileDirectory, configFile, executable) }, } return linter } async lintV1(textEditor: TextEditor, filePath: string, fileDirectory: string, configFile: string, executable: string) { let result try { result = await exec(executable, ['status', '--json'], { cwd: fileDirectory, timeout: 60 * 1000, uniqueKey: 'flow-ide-linter', ignoreExitCode: true, }) } catch (error) { this.handleError(error, configFile) return this.lintV1(textEditor, filePath, fileDirectory, configFile, executable) } if (result === null) { return null } return LinterV1.toLinterMessages(result) } async lintV2(textEditor: TextEditor, filePath: string, fileDirectory: string, configFile: string, executable: string) { let result try { result = await exec(executable, ['status', '--json', '--json-version=2'], { cwd: fileDirectory, timeout: 60 * 1000, uniqueKey: 'flow-ide-linter', ignoreExitCode: true, }) } catch (error) { this.handleError(error, configFile) return this.lintV2(textEditor, filePath, fileDirectory, configFile, executable) } if (result === null) { return null } return LinterV2.toLinterMessages(result, filePath) } provideCoverageLinter(): Object { const linter = { name: 'Flow IDE Coverage', scope: 'file', grammarScopes: Helpers.grammarScopes, lintsOnChange: false, lint: async (textEditor: TextEditor) => { const filePath = textEditor.getPath() if (filePath == null) { return [] } const fileDirectory = Path.dirname(filePath) let configFile if (this.onlyIfAppropriate) { configFile = await this.getConfigFile(fileDirectory) if (configFile == null) { return [] } } const executable = await Helpers.getExecutablePath(fileDirectory) let result: string try { result = await exec(executable, ['coverage', filePath, '--json'], { cwd: fileDirectory, timeout: 60 * 1000, uniqueKey: 'flow-ide-coverage', ignoreExitCode: true, }) if (result === null) { return null } } catch (error) { this.handleError(error, configFile) return linter.lint(textEditor) } const coverage: CoverageObject = JSON.parse(result) this.coverages.set(textEditor, coverage) if (this.coverageView) { this.coverageView.update(coverage) } return this.showUncovered ? this.toCoverageLinterMessages(coverage) : [] }, } return linter } provideAutocomplete(): Object { const provider = { selector: Helpers.grammarScopes.map(x => (x.includes('.') ? `.${x}` : x)).join(', '), disableForSelector: '.comment', inclusionPriority: 100, // eslint-disable-next-line arrow-parens getSuggestions: async (params) => { const { editor, bufferPosition, activatedManually } = params let prefix = params.prefix const filePath = editor.getPath() if (!filePath) { return [] } const fileDirectory = Path.dirname(filePath) const fileContents = this.injectPosition(editor.getText(), editor, bufferPosition) let flowOptions = ['autocomplete', '--json', filePath] let configFile = await this.getConfigFile(fileDirectory) if (configFile == null) { if (this.onlyIfAppropriate) { return [] } configFile = ((configFile: any): string) flowOptions = ['autocomplete', '--root', Helpers.defaultFlowFile, '--json', filePath] } // NOTE: Fix for class properties autocompletion if (prefix === '.') { prefix = '' } if (!shouldTriggerAutocomplete({ activatedManually, bufferPosition, editor })) { return [] } let result try { result = await exec(await Helpers.getExecutablePath(fileDirectory), flowOptions, { cwd: fileDirectory, stdin: fileContents, timeout: 60 * 1000, uniqueKey: 'flow-ide-autocomplete', ignoreExitCode: true, }) if (result === null) { return [] } } catch (error) { this.handleError(error, configFile) return provider.getSuggestions(params) } return this.toAutocompleteSuggestions(result, prefix) }, } return provider } injectPosition(text: string, editor: Object, bufferPosition: Object) { const characterIndex = editor.getBuffer().characterIndexForPosition(bufferPosition) return text.slice(0, characterIndex) + 'AUTO332' + text.slice(characterIndex) } toAutocompleteSuggestions(contents: string, prefix: string) { if (contents.slice(0, 1) !== '{') { // Invalid server response return [] } const parsed = JSON.parse(contents) const hasPrefix = prefix.trim().length const suggestions = parsed.result.map((suggestion) => { const isFunction = suggestion.func_details !== null && suggestion.func_details !== undefined let text = null let snippet = null let displayText = null let description = null if (isFunction) { const functionParams = suggestion.func_details.params displayText = `${suggestion.name}(${functionParams.map(value => value.name).join(', ')})` const snippetArgs = functionParams.map((value, i) => `\${${i + 1}:${value.name}}`).join(', ') snippet = `${suggestion.name}(${snippetArgs})$${functionParams.length + 1}` const params = functionParams.map(param => param.name + (param.type ? `: ${param.type}` : '')) const match = suggestion.type.match(/\(.*?\) => (.*)/) const returnType = match ? `=> ${match[1]}` : '' description = `(${params.join(', ')}) ${returnType}` } else { text = suggestion.name } return { text, type: isFunction ? 'function' : 'property', score: hasPrefix ? score(suggestion.name, prefix) : 1, snippet, leftLabel: isFunction ? 'function' : this.getType(suggestion), displayText, replacementPrefix: prefix, description, } }) return suggestions.sort((a, b) => b.score - a.score).filter(item => item.score) } getType(value: { value: string, type: string }) { return value.type && value.type.substr(0, 1) === '{' ? 'Object' : value.type || 'any' } provideHyperclick(): HyperclickProvider { const provider = { priority: this.hyperclickPriority, grammarScopes: Helpers.grammarScopes, getSuggestionForWord: async (textEditor: TextEditor, text: string, range: Range): Promise => { const filePath = textEditor.getPath() if (filePath == null) { return null } const fileDirectory = Path.dirname(filePath) const configFile = await this.getConfigFile(fileDirectory) if (configFile == null) { return null } const pos = this.adjustPosition(range.start, textEditor) const flowOptions = [ 'get-def', '--json', '--path=' + filePath, pos.row + 1, pos.column + 1, ] let result try { result = await exec(await Helpers.getExecutablePath(fileDirectory), flowOptions, { cwd: fileDirectory, stdin: textEditor.getText(), ignoreExitCode: true, timeout: 60 * 1000, uniqueKey: 'flow-ide-hyperclick', }) if (result === null) { return null } } catch (error) { this.handleError(error, configFile) return provider.getSuggestionForWord(textEditor, text, range) } const jsonResult = JSON.parse(result) if (!jsonResult.path) { return null } return { range, callback() { atom.workspace.open(jsonResult.path, { searchAllPanes: true }).then((editor) => { editor.setCursorBufferPosition([jsonResult.line - 1, jsonResult.start - 1]) }) }, } }, } return provider } adjustPosition(pos: atom$Point, editor: TextEditor) { // Flow fails to determine the position if the cursor is at the end of a word // e.g. "path.dirname ()" // ↑ the cursor is here, between "dirname" and "(" // In order to avoid this problem we have to check whether the char // at the given position is considered a part of an identifier. // If not step back 1 char as it might contain a valid identifier. const char = editor.getTextInBufferRange([ pos, pos.translate([0, 1]), ]) const nonWordChars = editor.getNonWordCharacters(pos) if (nonWordChars.indexOf(char) >= 0 || /\s/.test(char)) { return pos.translate([0, -1]) } return pos } provideOutlines(): OutlineProvider { const outlineOptions: OutlineOptions = { showKeywords: { export: false, default: false, const: false, var: false, let: false, class: false, function: false, type: false, }, showFunctionArgs: false, } this.subscriptions.add(atom.config.observe('flow-ide.outline', (outline) => { outlineOptions.showKeywords.export = outline.showExport outlineOptions.showFunctionArgs = outline.showFunctionArgs })) const provider = { name: 'flow-ide', priority: 1, grammarScopes: Helpers.grammarScopes, updateOnEdit: true, getOutline: async (editor: TextEditor) => { const filePath = editor.getPath() if (filePath == null) { return null } const fileDirectory = Path.dirname(filePath) const configFile = await this.getConfigFile(fileDirectory) if (configFile == null) { return null } const flowOptions = ['ast'] let result try { result = await exec(await Helpers.getExecutablePath(fileDirectory), flowOptions, { cwd: fileDirectory, stdin: editor.getText(), timeout: 60 * 1000, uniqueKey: 'flow-ide-ast', ignoreExitCode: true, }) if (result === null) { return { outlineTrees: [] } } } catch (error) { this.handleError(error, configFile) return provider.getOutline(editor) } return toOutline(result, outlineOptions) }, } return provider } consumeDatatip(datatipService: any) { const provider = { providerName: 'flow-ide', priority: 1, grammarScopes: Helpers.grammarScopes, datatip: async (editor: TextEditor, point: Point): Promise => { const filePath = editor.getPath() if (filePath == null) { return null } const fileDirectory = Path.dirname(filePath) const configFile = await this.getConfigFile(fileDirectory) if (configFile == null) { return null } const flowOptions = [ 'type-at-pos', '--json', '--path=' + filePath, point.row + 1, point.column + 1, ] let result try { result = await exec(await Helpers.getExecutablePath(fileDirectory), flowOptions, { cwd: fileDirectory, stdin: editor.getText(), timeout: 60 * 1000, uniqueKey: 'flow-ide-type-at-pos', ignoreExitCode: true, }) if (result === null) { return null } } catch (error) { this.handleError(error, configFile) return provider.datatip(editor, point) } return this.toDatatip(editor, point, result) }, } this.subscriptions.add(datatipService.addProvider(provider)) } toDatatip(editor: TextEditor, point: Point, result: string) { const parsed: TypeAtPosObject = JSON.parse(result) const { type, loc } = parsed if (type === '(unknown)') { return null } return { range: LinterV1.locationToRange(loc), markedStrings: [{ type: 'snippet', grammar: editor.getGrammar(), value: prettyPrintTypes(type), }], } } consumeStatusBar(statusBar: atom$StatusBar): void { this.coverageView = new CoverageView() this.coverageView.initialize() this.statusBar = statusBar.addLeftTile({ item: this.coverageView, priority: 10 }) this.subscriptions.add({ dispose: () => { if (this.statusBar) { this.statusBar.destroy() } }, }) } handleError(error: { message: string, code: string }, configFile: ?string) { if (error.message.indexOf(INIT_MESSAGE) !== -1 && typeof configFile === 'string') { servers.set(Path.dirname(configFile), { version: null }) } if (error.message.indexOf(INIT_MESSAGE) !== -1 || error.message.indexOf(RECHECKING_MESSAGE) !== -1) { // continue... } else if (error.code === 'ENOENT') { throw new Error('Unable to find `flow` executable.') } else { throw error } } toCoverageLinterMessages(coverage: CoverageObject): Message[] { return coverage.expressions.uncovered_locs.map(loc => ({ severity: 'info', location: LinterV1.toLinterLocation(loc), excerpt: 'Uncovered code', reference: null, })) } } ================================================ FILE: lib/language-client.js ================================================ /* @flow */ import { TextEditor } from 'atom' import { AutoLanguageClient } from '@lloiser/atom-languageclient' import { findCachedAsync } from 'atom-linter' import { spawn } from 'child_process' import * as Path from 'path' import * as Helpers from './helpers' export class FlowLanguageClient extends AutoLanguageClient { getGrammarScopes() { return Helpers.grammarScopes } getLanguageName() { return 'Flowtype' } getServerName() { return 'flow' } async startServerProcess(projectPath: string) { return spawn( await Helpers.getExecutablePath(projectPath), ['lsp'], { cwd: projectPath }, ) } async determineProjectPath(textEditor: TextEditor) { const path = textEditor.getPath() if (path == null) { return null } const configFile: ?string = await findCachedAsync(path, '.flowconfig') if (configFile != null) { return Path.dirname(configFile) } return null } } ================================================ FILE: lib/linter/v1/index.js ================================================ /* @flow */ import { Range } from 'atom' import type { Message } from 'linter' import { mainMessageOfError, prettyPrintError } from './pretty' import type { StatusMessage, StatusObject } from './types' import type { Location } from '../../types' export function locationToRange({ start, end }: Location): Range { return new Range( [start.line - 1, start.column - 1], [end.line - 1, end.column], ) } export function toLinterLocation(loc: Location) { return { file: loc.source, position: locationToRange(loc), } } function toLinterReference(messages: StatusMessage[]) { for (let i = 1, length = messages.length; i < length; i++) { const message = messages[i] if (message.loc) { return { file: message.loc.source, position: locationToRange(message.loc).start, } } } return null } export function toLinterMessages(contents: string): Message[] { const parsed: StatusObject = JSON.parse(contents) if (!Array.isArray(parsed.errors) || !parsed.errors.length) { return [] } return parsed.errors.map((error) => { const mainMsg = mainMessageOfError(error) const { loc } = mainMsg if (!loc) { return null } let excerpt = error.message.map(msg => msg.descr).join(' ') if (error.operation && mainMsg === error.operation) { excerpt = error.operation.descr + ' ' + excerpt } return { severity: error.level === 'error' ? 'error' : 'warning', location: toLinterLocation(loc), excerpt, description: prettyPrintError(error), reference: toLinterReference(error.message), } }).filter(Boolean) } ================================================ FILE: lib/linter/v1/pretty.js ================================================ /* @flow */ // Note: the following code is based on // https://github.com/facebook/flow/blob/v0.47.0/tsrc/flowResult.js // and adjusted to output nicely formatted markdown. import { format } from 'util' import type { StatusError, StatusExtra, StatusMessage } from './types' import type { Location } from '../../types' const regexMarkdownChars = /[*#()[\]<>_]/g const escapeChars = { '<': '<', '>': '>' } function fileUrl({ source, start }: Location): string { // build a link that can be opened by the linter ui. // e.g. atom://linter?file=&row=&column= const params = ['file=' + encodeURIComponent(source)] if (start.line > 0) { params.push('row=' + (start.line - 1)) if (start.column > 0) { params.push('column=' + (start.column - 1)) } } return 'atom://linter?' + params.join('&') } function linterLink(loc: Location, text: string) { return '[' + text + '](' + fileUrl(loc) + ')' } function fileLink(loc: Location): string { return linterLink(loc, atom.project.relativize(loc.source)) } function lineLink(loc: Location): string { return linterLink(loc, loc.start.line.toString()) } export function mainMessageOfError(error: StatusError): StatusMessage { return error.operation || error.message[0] } export function mainLocOfError(error: StatusError): ?Location { return mainMessageOfError(error).loc } function getExtraMessages(extra: ?StatusExtra[]): StatusMessage[] { if (extra) { const messages = extra.reduce((acc, current) => { const childrenMessages = getExtraMessages(current.children) return acc.concat(current.message, childrenMessages) }, []) messages.forEach((message) => { const msg = message msg.indent = (msg.indent || 0) + 2 }) return messages } return [] } function getTraceReasons(trace: ?StatusMessage[]): StatusMessage[] { if (trace != null && trace.length > 0) { return ([{ descr: 'Trace:', type: 'Blame' }].concat(trace): any) } return [] } function mkComment(descr: string): StatusMessage { return ({ descr, type: 'Comment' }: any) } function getOpReason(op: ?StatusMessage): StatusMessage[] { if (op) { return [ op, mkComment('Error:'), ] } return [] } function getHeader(mainLoc: ?Location, kind: string, level: string): StatusMessage[] { let line = -1 let filename = '' if (mainLoc) { const { source, start } = mainLoc line = start.line if (source) { filename = fileLink(mainLoc) } } if (!filename) { filename = format('%s:%d', '[No file]', line) } let prefix = '' if (kind === 'internal' && level === 'error') { prefix = 'Internal error (see logs):\n' } else if (mainLoc && mainLoc.type === 'LibFile') { if (kind === 'parse' && level === 'error') { prefix = 'Library parse error:\n' } else if (kind === 'infer') { prefix = 'Library type error:\n' } } return [mkComment(format('%s%s', prefix, filename))] } function prettyPrintMessage(mainFile: string, { context, descr, loc, indent }: StatusMessage): string { const indentation = ' '.repeat(indent || 0) if (loc) { const startCol = loc.start.column - 1 let contextStr = indentation if (context !== null && typeof context === 'string') { // On Windows this might have \r let ctx = context.trimRight() // Replace tabs with spaces ctx = ctx.replace(/\t/g, ' ') // escape certain chars that serve a purpose in markdown ctx = ctx.replace(regexMarkdownChars, m => escapeChars[m] || '\\' + m) const { line } = loc.start const prefix = line < 100 ? ' '.repeat(3 - line.toString().length) : '' let padding = Array((prefix + line + ': ').length + 1).join(' ') if (ctx.length > startCol) { padding += ctx.substr(0, startCol).replace(/[^\t ]/g, ' ') } const underlineSize = line === loc.end.line ? Math.max(1, loc.end.column - startCol) : 1 const underline = '^'.repeat(underlineSize) contextStr = format( '%s%s%s: %s\n%s%s%s ', indentation, prefix, lineLink(loc), ctx, indentation, padding, underline, ) } const seeAnotherFile = loc.source === mainFile ? '' : format( '. See%s: %s', loc.type === 'LibFile' ? ' lib' : '', fileLink(loc), ) return format('%s%s%s', contextStr, descr, seeAnotherFile) } return indentation + descr } export function mergedMessagesOfError(error: StatusError): StatusMessage[] { const { level, kind, message, operation, trace, extra } = error const mainLoc = mainLocOfError(error) const messages = [].concat( getHeader(mainLoc, kind, level), getOpReason(operation), message, getExtraMessages(extra), getTraceReasons(trace), ) // Merge comments into blames return messages.reduce((acc, msg) => { const { descr, loc, type } = msg if (loc || acc.length === 0 || type === 'Blame') { acc.push(msg) } else if (descr !== 'Error:') { const prev = acc[acc.length - 1] prev.descr = prev.descr === '' ? descr : format('%s. %s', prev.descr, descr) } return acc }, []) } export function prettyPrintError(error: StatusError): string { const mainLoc = mainLocOfError(error) const mainFile = (mainLoc && mainLoc.source) || '[No file]' const messages = mergedMessagesOfError(error) return '
' + messages.map(msg => prettyPrintMessage(mainFile, msg)).join('\n') + '
' } ================================================ FILE: lib/linter/v1/types.js ================================================ /* @flow */ import type { Location } from '../../types' export type StatusMessage = { context: ?string, descr: string, type: string, loc?: Location, path: string, line: number, endline: number, start: number, end: number, indent?: number, } export type StatusExtra = { message: StatusMessage[], children?: StatusExtra[], } export type StatusError = { kind: string, level: string, message: StatusMessage[], operation?: StatusMessage, extra?: StatusExtra[], trace?: StatusMessage[], } export type StatusObject = { flowVersion: string, errors: StatusError[], passed: boolean, } ================================================ FILE: lib/linter/v2/index.js ================================================ /* @flow */ import { Range } from 'atom' import type { Message } from 'linter' import type { Location, MessageMarkup, InlineMarkup, ReferenceLocs, StatusResult } from './types' function locationToRange({ start, end }: Location): Range { return new Range( [start.line - 1, start.column - 1], [end.line - 1, end.column], ) } function locactionToLinterLocation(loc: Location) { return { file: loc.source, position: locationToRange(loc), } } function plainText(messages: InlineMarkup[], references: ReferenceLocs): string { return messages.map((message) => { switch (message.kind) { case 'Text': return message.text case 'Code': return message.text case 'Reference': { const ref = references[message.referenceId] if (!ref) { return '' } return plainText(message.message, references) } default: return '' } }).join('') } function fileUrl(loc: Location, path: string): string { // build a link that can be opened by the linter ui. // e.g. atom://linter?file=&row=&column= let { source } = loc if (source === '-') { source = path } const params = ['file=' + encodeURIComponent(source)] const { start } = loc if (start.line > 0) { params.push('row=' + (start.line - 1)) if (start.column > 0) { params.push('column=' + (start.column - 1)) } } return 'atom://linter?' + params.join('&') } function markup(messages: MessageMarkup, references: ReferenceLocs, path: string): string { if (Array.isArray(messages)) { return messages.map((message) => { switch (message.kind) { case 'Text': return message.text case 'Code': return '`' + message.text + '`' case 'Reference': { const ref = references[message.referenceId] if (!ref) { return '' } return `[${markup(message.message, references, path)}](${fileUrl(ref, path)})` } default: return '' } }).join('') } if (messages.kind === 'UnorderedList') { // `messages.message` is already part of the excerpt return messages.items.map(msg => markup(msg, references, path)) .map(msg => ` - ${msg}`).join('\n') } return '' } export function toLinterMessages(json: string, path: string): Message[] { const result: StatusResult = JSON.parse(json) if (result.passed && result.errors.length === 0) { return [] } return result.errors.map((err) => { const location = locactionToLinterLocation(err.primaryLoc) const severity = err.level const { messageMarkup } = err if (Array.isArray(messageMarkup)) { return { location, excerpt: plainText(messageMarkup, err.referenceLocs), severity, reference: null, } } return { location, excerpt: plainText(messageMarkup.message, err.referenceLocs), severity, description: markup(messageMarkup, err.referenceLocs, path), reference: null, } }) } ================================================ FILE: lib/linter/v2/types.js ================================================ /* @flow */ export type Position = { line: number, column: number, offset: number } export type Location = { source: string, type: "SourceFile", // TODO: others? start: Position, end: Position, context: string } export type ReferenceId = string export type ReferenceLocs = { [id: ReferenceId]: Location } // CodeMarkup represents text that originates from the actual source code. // Typically should be rendered in a fixed-width font (e.g. in markdown using `...`) export type CodeMarkup = { kind: "Code", text: string } // TextMarkup just represents text, most likely used in combination with other markups export type TextMarkup = { kind: "Text", text: string } // ReferenceMarkup can be used to reference to source code (e.g. via links) export type ReferenceMarkup = { kind: "Reference", referenceId: ReferenceId, message: InlineMarkup[] // eslint-disable-line no-use-before-define } // UnorderedListMarkup represents a `message` and a list of bullet points (`items`) export type UnorderedListMarkup = { kind: 'UnorderedList', message: InlineMarkup[], // eslint-disable-line no-use-before-define items: MessageMarkup[] // eslint-disable-line no-use-before-define } // InlineMarkup represents a single line of text export type InlineMarkup = CodeMarkup | TextMarkup | ReferenceMarkup // BlockMarkup represents a larger block of text. // Typically this is suitable for linter `description` export type BlockMarkup = UnorderedListMarkup export type MessageMarkup = InlineMarkup[] | BlockMarkup export type Error = { kind: "lint" | "infer", level: "warning" | "error", suppressions: [], // TODO: I probably have to turn on some suppressions to see this... classic: boolean, primaryLoc: Location, rootLoc: ?Location, messageMarkup: MessageMarkup, referenceLocs: ReferenceLocs } export type StatusResult = { flowVersion: string, // e.g 0.66.0 jsonVersion: "2", errors: Error[], passed: true }; ================================================ FILE: lib/main.js ================================================ /* @flow */ const useLSP = atom.config.get('flow-ide.useLSP') // notify to restart if the value changes atom.config.onDidChange('flow-ide.useLSP', () => { const buttons = [{ text: 'Restart', onDidClick () { return atom.restartApplication() }, }] atom.notifications.addInfo('Changing this value requires a restart of atom.', { dismissable: true, buttons }) }) // possible improvement: check if flow really supports lsp if (useLSP === true) { // eslint-disable-next-line global-require const { FlowLanguageClient } = require('./language-client') module.exports = new FlowLanguageClient() } else { // eslint-disable-next-line global-require const { Flow } = require('./index') module.exports = new Flow() } ================================================ FILE: lib/outline/index.js ================================================ /* @flow */ import type { Outline } from 'atom-ide-ui/outline' import { astToOutline } from './parse' import type { OutlineOptions } from './types' export function toOutline(result: string, options: OutlineOptions): Outline { const parsed = JSON.parse(result) return astToOutline(options, parsed) } ================================================ FILE: lib/outline/parse.js ================================================ /* @flow */ // NOTE: the following code is based on // https://github.com/flowtype/flow-language-server/blob/fbd1bc3/src/pkg/nuclide-flow-rpc/lib/astToOutline.js // and adjusted to toggle keywords and function arguments import { Point } from 'atom' import invariant from 'assert' import type { Outline, OutlineTree, TokenizedText } from 'atom-ide-ui/outline' import * as Text from './text' import type { OutlineOptions, Extent } from './types' function exportDeclaration( options: OutlineOptions, item: Object, extent: Extent, isDefault: boolean, ): ?OutlineTree { const tree = itemToTree(options, item.declaration) // eslint-disable-line if (tree == null) { return null } // Flow always has tokenizedText invariant(tree.tokenizedText != null) const tokenizedText = [] if (options.showKeywords.export) { tokenizedText.push(Text.keyword('export'), Text.whitespace(' ')) } if (options.showKeywords.default && isDefault) { tokenizedText.push(Text.keyword('default'), Text.whitespace(' ')) } tokenizedText.push(...tree.tokenizedText) return { kind: tree.kind, tokenizedText, representativeName: tree.representativeName, children: tree.children, ...extent, } } function declarationsTokenizedText(options: OutlineOptions, declarations: Array): TokenizedText { return declarations.reduce((text, decl, i, decls) => { text.push(...declarationReducer(options, decl)) // eslint-disable-line if (i < decls.length - 1) { text.push(Text.plain(',')) text.push(Text.whitespace(' ')) } return text }, []) } function declarationReducer( options: OutlineOptions, decl: Object, ): TokenizedText { if (!decl) { // special case: array patterns can contain `null` elements // which means that this value is simply ignored. return [] } switch (decl.type) { case 'Identifier': return [Text.param(decl.name)] case 'ObjectPattern': return [ Text.plain('{'), ...declarationsTokenizedText(options, decl.properties), Text.plain('}'), ] case 'ArrayPattern': return [ Text.plain('['), ...declarationsTokenizedText(options, decl.elements), Text.plain(']'), ] case 'AssignmentPattern': return declarationReducer(options, decl.left) case 'RestElement': case 'RestProperty': return [ Text.plain('...'), ...declarationReducer(options, decl.argument), ] case 'Property': return declarationReducer(options, decl.value) default: throw new Error(`encountered unexpected argument type ${decl.type}`) } } function getExtent(item: Object): Extent { return { startPosition: new Point( // It definitely makes sense that the lines we get are 1-based and the columns are // 0-based... convert to 0-based all around. item.loc.start.line - 1, item.loc.start.column, ), endPosition: new Point(item.loc.end.line - 1, item.loc.end.column), } } function functionOutline( options: OutlineOptions, name: string, params: Array, extent: Extent, ): OutlineTree { const tokenizedText = [] if (options.showKeywords.function || !name) { tokenizedText.push(Text.keyword('function')) if (name) { tokenizedText.push(Text.whitespace(' ')) } } if (name) { tokenizedText.push(Text.method(name)) } if (options.showFunctionArgs) { tokenizedText.push( Text.plain('('), ...declarationsTokenizedText(options, params), Text.plain(')'), ) } return { kind: 'function', tokenizedText, representativeName: name, children: [], ...extent, } } function typeAliasOutline(options: OutlineOptions, typeAliasExpression: Object): OutlineTree { invariant(typeAliasExpression.type === 'TypeAlias' || typeAliasExpression.type === 'DeclareTypeAlias') const name = typeAliasExpression.id.name const tokenizedText = [] if (options.showKeywords.type) { tokenizedText.push(Text.keyword('type'), Text.whitespace(' ')) } tokenizedText.push(Text.type(name)) return { kind: 'interface', tokenizedText, representativeName: name, children: [], ...getExtent(typeAliasExpression), } } function isModuleExports(left: Object): boolean { return ( left.type === 'MemberExpression' && left.object.type === 'Identifier' && left.object.name === 'module' && left.property.type === 'Identifier' && left.property.name === 'exports' ) } function moduleExportsPropertyOutline(options: OutlineOptions, property: Object): ?OutlineTree { invariant(property.type === 'Property') if (property.key.type !== 'Identifier') { return null } const propName = property.key.name if (property.shorthand) { // This happens when the shorthand `{ foo }` is used for `{ foo: foo }` return { kind: 'method', tokenizedText: [Text.string(propName)], representativeName: propName, children: [], ...getExtent(property), } } if ( property.value.type === 'FunctionExpression' || property.value.type === 'ArrowFunctionExpression' ) { const tokenizedText = [Text.method(propName)] if (options.showFunctionArgs) { tokenizedText.push( Text.plain('('), ...declarationsTokenizedText(options, property.value.params), Text.plain(')'), ) } return { kind: 'method', tokenizedText, representativeName: propName, children: [], ...getExtent(property), } } return { kind: 'field', tokenizedText: [Text.string(propName), Text.plain(':')], representativeName: propName, children: [], ...getExtent(property), } } function moduleExportsOutline(options: OutlineOptions, assignmentStatement: Object): ?OutlineTree { invariant(assignmentStatement.type === 'AssignmentExpression') const left = assignmentStatement.left if (!isModuleExports(left)) { return null } const right = assignmentStatement.right if (right.type !== 'ObjectExpression') { return null } const properties: Array = right.properties return { kind: 'module', tokenizedText: [Text.plain('module.exports')], children: properties.map(prop => moduleExportsPropertyOutline(options, prop)).filter(Boolean), ...getExtent(assignmentStatement), } } // Return the function name as written as a string. Intended to stringify patterns like `describe` // and `describe.only` even though `describe.only` is a MemberExpression rather than an Identifier. function getFunctionName(callee: Object): ?string { switch (callee.type) { case 'Identifier': return callee.name case 'MemberExpression': if ( callee.object.type !== 'Identifier' || callee.property.type !== 'Identifier' ) { return null } return `${callee.object.name}.${callee.property.name}` default: return null } } function isDescribe(functionName: string): boolean { switch (functionName) { case 'describe': case 'fdescribe': case 'ddescribe': case 'xdescribe': case 'describe.only': case 'describe.skip': case 'test.cb': case 'test.serial': case 'test.todo': case 'test.failing': case 'test': case 'test.concurrent': case 'test.only': case 'test.skip': case 'suite': case 'suite.only': case 'suite.skip': case 'xtest': case 'xtest.concurrent': case 'xtest.only': case 'xtest.skip': return true default: return false } } function isIt(functionName: string): boolean { switch (functionName) { case 'it': case 'fit': case 'iit': case 'pit': case 'xit': case 'it.only': case 'it.skip': return true default: return false } } /** If the given AST Node is a string literal, return its literal value. Otherwise return null */ function getStringLiteralValue(literal: ?Object): ?string { if (literal == null) { return null } if (literal.type !== 'Literal') { return null } const value = literal.value if (typeof value !== 'string') { return null } return value } function getFunctionBody(fn: ?Object): ?Array { if (fn == null) { return null } if ( fn.type !== 'ArrowFunctionExpression' && fn.type !== 'FunctionExpression' ) { return null } return fn.body.body } function specOutline( options: OutlineOptions, expressionStatement: Object, describeOnly: boolean = false, ): ?OutlineTree { const expression = expressionStatement.expression if (expression.type !== 'CallExpression') { return null } const functionName = getFunctionName(expression.callee) if (functionName == null) { return null } if (!isDescribe(functionName)) { if (describeOnly || !isIt(functionName)) { return null } } const description = getStringLiteralValue(expression.arguments[0]) const specBody = getFunctionBody(expression.arguments[1]) if (description == null || specBody == null) { return null } let children if (isIt(functionName)) { children = [] } else { children = specBody .filter(item => item.type === 'ExpressionStatement') .map(item => specOutline(options, item)) .filter(Boolean) } return { kind: 'function', tokenizedText: [Text.method(functionName), Text.whitespace(' '), Text.string(description)], representativeName: description, children, ...getExtent(expressionStatement), } } function topLevelExpressionOutline(options: OutlineOptions, expressionStatement: Object): ?OutlineTree { switch (expressionStatement.expression.type) { case 'CallExpression': return specOutline(options, expressionStatement, /* describeOnly */ true) case 'AssignmentExpression': return moduleExportsOutline(options, expressionStatement.expression) default: return null } } function variableDeclaratorOutline( options: OutlineOptions, declarator: Object, kind: string, extent: Extent, ): ?OutlineTree { if ( declarator.init != null && (declarator.init.type === 'FunctionExpression' || declarator.init.type === 'ArrowFunctionExpression') ) { return functionOutline(options, declarator.id.name, declarator.init.params, extent) } const { id } = declarator const tokenizedText = [] if (options.showKeywords[kind]) { tokenizedText.push( Text.keyword(kind), Text.whitespace(' '), ) } tokenizedText.push(...declarationsTokenizedText(options, [id])) const representativeName = id.type === 'Identifier' ? id.name : undefined return { kind: kind === 'const' ? 'constant' : 'variable', tokenizedText, representativeName, children: [], ...extent, } } function variableDeclarationOutline(options: OutlineOptions, declaration: Object): ?OutlineTree { // If there are multiple var declarations in one line, just take the first. return variableDeclaratorOutline( options, declaration.declarations[0], declaration.kind, getExtent(declaration), ) } function itemToTree(options: OutlineOptions, item: Object): ?OutlineTree { if (item == null) { return null } const extent = getExtent(item) switch (item.type) { case 'FunctionDeclaration': case 'ArrowFunctionExpression': return functionOutline( options, item.id != null ? item.id.name : '', item.params, extent, ) case 'ClassDeclaration': case 'ClassExpression': { let representativeName if (item.id != null) { representativeName = item.id.name } const tokenizedText = [] if (options.showKeywords.class || !representativeName) { tokenizedText.push(Text.keyword('class')) } if (representativeName) { if (options.showKeywords.class) { tokenizedText.push(Text.whitespace(' ')) } tokenizedText.push(Text.className(representativeName)) } return { kind: 'class', tokenizedText, representativeName, children: itemsToTrees(options, item.body.body), // eslint-disable-line ...extent, } } case 'ClassProperty': { if (item.value && item.value.type === 'ArrowFunctionExpression') { const tokenizedText = [Text.method(item.key.name)] if (options.showFunctionArgs) { tokenizedText.push( Text.plain('('), ...declarationsTokenizedText(options, item.value.params), Text.plain(')'), ) } return { kind: 'method', tokenizedText, representativeName: item.key.name, children: [], ...extent, } } return { kind: 'property', tokenizedText: [Text.param(item.key.name)], representativeName: item.key.name, children: [], ...extent, } } case 'MethodDefinition': { const tokenizedText = [Text.method(item.key.name)] if (options.showFunctionArgs) { tokenizedText.push( Text.plain('('), ...declarationsTokenizedText(options, item.value.params), Text.plain(')'), ) } return { kind: 'method', tokenizedText, representativeName: item.key.name, children: [], ...extent, } } case 'ExportDeclaration': case 'ExportNamedDeclaration': return exportDeclaration(options, item, extent, Boolean(item.default)) case 'ExportDefaultDeclaration': return exportDeclaration(options, item, extent, true) case 'ExpressionStatement': return topLevelExpressionOutline(options, item) case 'DeclareTypeAlias': case 'TypeAlias': return typeAliasOutline(options, item) case 'VariableDeclaration': return variableDeclarationOutline(options, item) default: return null } } function itemsToTrees(options: OutlineOptions, items: Array): Array { return items.map(item => itemToTree(options, item)).filter(Boolean) } export function astToOutline(options: OutlineOptions, ast: Object): Outline { return { outlineTrees: itemsToTrees(options, ast.body), } } ================================================ FILE: lib/outline/text.js ================================================ /* @flow */ import type { TokenKind, TextToken } from 'atom-ide-ui/outline' function buildToken(kind: TokenKind, value: string): TextToken { return { kind, value } } export function keyword(value: string): TextToken { return buildToken('keyword', value) } export function className(value: string): TextToken { return buildToken('class-name', value) } export function constructor(value: string): TextToken { return buildToken('constructor', value) } export function method(value: string): TextToken { return buildToken('method', value) } export function param(value: string): TextToken { return buildToken('param', value) } export function string(value: string): TextToken { return buildToken('string', value) } export function whitespace(value: string): TextToken { return buildToken('whitespace', value) } export function plain(value: string): TextToken { return buildToken('plain', value) } export function type(value: string): TextToken { return buildToken('type', value) } ================================================ FILE: lib/outline/types.js ================================================ /* @flow */ import type { Point } from 'atom' export type OutlineOptions = { showKeywords: { export: boolean, default: boolean, const: boolean, var: boolean, let: boolean, class: boolean, function: boolean, type: boolean, }, showFunctionArgs: boolean }; export type Extent = {| startPosition: Point, endPosition: Point, |}; ================================================ FILE: lib/types.js ================================================ /* @flow */ export type Position = { line: number, column: number, offset: number, end: number, } export type Location = { source: string, type: string, start: Position, end: Position, } export type CoverageObject = { expressions: { covered_count: number, uncovered_count: number, uncovered_locs: Location[], } } export type TypeAtPosObject = { type: string, reasons: Array<{ desc: string, loc: Location, path: string, line: number, endline: number, start: number, end: number, }>, loc: Location, path: string, line: number, endline: number, start: number, end: number, } ================================================ FILE: package.json ================================================ { "name": "flow-ide", "private": true, "version": "1.13.0", "description": "Flowtype support in Atom without any bloatware", "main": "lib/main.js", "scripts": { "test": "apm test", "lint": "eslint ." }, "package-deps": [ "linter", "hyperclick" ], "engines": { "atom": ">=1.4.0 <2.0.0" }, "providedServices": { "autocomplete.provider": { "versions": { "2.0.0": "provideAutocomplete" } }, "hyperclick.provider": { "versions": { "0.0.0": "provideHyperclick" } }, "linter": { "versions": { "2.0.0": "provideLinter" } }, "outline-view": { "versions": { "0.1.0": "provideOutlines" } }, "code-actions": { "versions": { "0.1.0": "provideCodeActions" } }, "code-format.range": { "versions": { "0.1.0": "provideCodeFormat" } }, "code-highlight": { "versions": { "0.1.0": "provideCodeHighlight" } }, "definitions": { "versions": { "0.1.0": "provideDefinitions" } }, "find-references": { "versions": { "0.1.0": "provideFindReferences" } } }, "consumedServices": { "datatip": { "versions": { "0.1.0": "consumeDatatip" } }, "status-bar": { "versions": { "^1.0.0": "consumeStatusBar" } }, "console": { "versions": { "0.1.0": "consumeConsole" } }, "atom-ide-busy-signal": { "versions": { "0.1.0": "consumeBusySignal" } }, "linter-indie": { "versions": { "2.0.0": "consumeLinterV2" } }, "signature-help": { "versions": { "0.1.0": "consumeSignatureHelp" } } }, "atomTranspilers": [ { "glob": "{lib,spec}/**/*.js", "transpiler": "atom-babel6-transpiler", "options": { "cacheKeyFiles": [ "package.json", ".babelrc" ] } } ], "repository": { "type": "git", "url": "https://github.com/steelbrain/flow-ide.git" }, "keywords": [ "flow", "flowtype", "ide", "javascript" ], "author": "steelbrain", "license": "MIT", "bugs": { "url": "https://github.com/steelbrain/flow-ide/issues" }, "homepage": "https://github.com/steelbrain/flow-ide#readme", "dependencies": { "@lloiser/atom-languageclient": "^0.10.1", "atom-autocomplete": "^1.0.0", "atom-babel6-transpiler": "^1.2.0", "atom-linter": "^10.0.0", "atom-package-deps": "^4.4.1", "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-flow-strip-types": "^6.22.0", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-env": "^1.7.0", "flow-language-server": "^0.2.3", "sb-string_score": "^0.1.20", "semver": "^5.5.0" }, "devDependencies": { "eslint-config-steelbrain": "^3.0.1", "eslint-plugin-flowtype": "^2.33.0", "flow-bin": "^0.91.0" }, "configSchema": { "useLSP": { "title": "Use the Language Server Protocol to talk to flow, otherwise call flow", "description": "Changing this value requires a restart of atom.", "type": "boolean", "default": false, "order": 1 }, "executablePath": { "description": "Path to `flow` executable", "type": "string", "default": "", "order": 2 }, "onlyIfAppropriate": { "title": "Only activate when .flowconfig exists", "type": "boolean", "default": true, "order": 3 }, "stopServer": { "title": "Stop flow server when disabling this plugin or closing atom", "type": "boolean", "default": true, "order": 4 }, "showUncovered": { "title": "Show uncovered code in the editor", "type": "boolean", "default": false, "order": 5 }, "hyperclickPriority": { "description": "Priority to use for hyperclick provider (requires restart)", "type": "integer", "default": 0, "order": 6 }, "outline": { "title": "Outline", "type": "object", "order": 7, "properties": { "showExport": { "title": "Show 'export' keyword", "description": "Shows a leading 'export' keyword if the variable, function or type is exported", "type": "boolean", "default": true }, "showFunctionArgs": { "title": "Show function arguments", "type": "boolean", "default": true } } } } } ================================================ FILE: spec/linter/v2/index-spec.js ================================================ /* @flow */ /* eslint-env jasmine */ import { findCached, exec } from 'atom-linter' import * as LinterV2 from '../../../lib/linter/v2' function fileUrl(file, row, column) { return `atom://linter?file=${encodeURIComponent(file)}&row=${row}&column=${column}` } describe('linter/v2', function() { const flowBin = findCached(__dirname, 'node_modules/.bin/flow') describe('toLinterMessages', function() { it('only excerpt', function() { const code = `// @flow type Human = { name: string } function getAge({ age }): number { return age } const p1: Human = { name: 'John Doe' } getAge(p1)` /* flow status Cannot call getAge with p1 bound to the first parameter because property age is missing in Human [1]. 7| } 8| [1] 9| const p1: Human = { name: 'John Doe' } 10| getAge(p1) 11| */ let result = null waitsForPromise(async () => { result = await exec(flowBin, ['check-contents', '--json', '--json-version=2'], { stdin: code }) const messages = LinterV2.toLinterMessages(result, __filename) expect(messages.length).toEqual(1) expect(messages[0].excerpt).toEqual('Cannot call getAge with p1 bound to the first parameter because property age is missing in Human.') }) }) it('with description', function() { const code = `// @flow type Human = { name: string } type Man = Human & { gender: 'male' } function getAge({ age }): number { return age } const p1: Man = { name: 'John Doe', gender: 'male' } getAge(p1)` /* flow status Cannot call getAge with p1 bound to the first parameter because: - Either property age is missing in Human [1]. - Or property age is missing in object type [2]. [1][2] 4| type Man = Human & { gender: 'male' } 5| 6| function getAge({ age }): number { 7| return age 8| } 9| 10| const p1: Man = { name: 'John Doe', gender: 'male' } 11| getAge(p1) 12| */ let result = null waitsForPromise(async () => { result = await exec(flowBin, ['check-contents', '--json', '--json-version=2'], { stdin: code }) const messages = LinterV2.toLinterMessages(result, __filename) expect(messages.length).toEqual(1) expect(messages[0].excerpt).toEqual('Cannot call getAge with p1 bound to the first parameter because:') expect(messages[0].description).toEqual( ` - Either property \`age\` is missing in [\`Human\`](${fileUrl(__filename, 3, 17)}).\n` + ` - Or property \`age\` is missing in [object type](${fileUrl(__filename, 3, 25)}).`, ) }) }) }) }) ================================================ FILE: spec/outline/parse-sample-ast.js ================================================ /* @flow */ const lc = (line, column) => ({ line, column }) const loc = (start, end, source = null) => ({ start, end, source }) /* declare type LocalType = { foo: string, bar: string } */ export const typeDecl = { type: 'Program', loc: loc(lc(3, 0), lc(6, 1)), range: [13, 70], body: [{ type: 'DeclareTypeAlias', loc: loc(lc(3, 0), lc(6, 1)), range: [13, 70], id: { type: 'Identifier', loc: loc(lc(3, 13), lc(3, 22)), range: [26, 35], name: 'LocalType', optional: false, }, right: { type: 'ObjectTypeAnnotation', loc: loc(lc(3, 25), lc(6, 1)), range: [38, 70], exact: false, properties: [{ type: 'ObjectTypeProperty', loc: loc(lc(4, 2), lc(4, 13)), range: [42, 53], key: { type: 'Identifier', loc: loc(lc(4, 2), lc(4, 5)), range: [42, 45], name: 'foo', optional: false, }, value: { type: 'StringTypeAnnotation', loc: loc(lc(4, 7), lc(4, 13)), range: [47, 53], }, optional: false, static: false, kind: 'init', }, { type: 'ObjectTypeProperty', loc: loc(lc(5, 2), lc(5, 13)), range: [57, 68], key: { type: 'Identifier', loc: loc(lc(5, 2), lc(5, 5)), range: [57, 60], name: 'bar', optional: false, }, value: { type: 'StringTypeAnnotation', loc: loc(lc(5, 7), lc(5, 13)), range: [62, 68], }, optional: false, static: false, kind: 'init', }], }, }], } /* export type ExportedType = { foo: string } */ export const exportedTypeDecl = { type: 'Program', loc: loc(lc(3, 0), lc(5, 1)), range: [13, 48], body: [{ type: 'ExportNamedDeclaration', loc: loc(lc(3, 0), lc(5, 1)), range: [13, 48], declaration: { type: 'TypeAlias', loc: loc(lc(3, 7), lc(5, 1)), range: [20, 48], id: { type: 'Identifier', loc: loc(lc(3, 12), lc(3, 15)), range: [25, 28], name: 'ExportedType', optional: false, }, right: { type: 'ObjectTypeAnnotation', loc: loc(lc(3, 18), lc(5, 1)), range: [31, 48], exact: false, properties: [{ type: 'ObjectTypeProperty', loc: loc(lc(4, 2), lc(4, 13)), range: [35, 46], key: { type: 'Identifier', loc: loc(lc(4, 2), lc(4, 5)), range: [35, 38], name: 'foo', optional: false, }, value: { type: 'StringTypeAnnotation', loc: loc(lc(4, 7), lc(4, 13)), range: [40, 46], }, optional: false, static: false, kind: 'init', }], indexers: [], callProperties: [], }, }, specifiers: [], exportKind: 'type', }], } /* function func(foo: string, bar: string) { } */ export const func = { type: 'Program', loc: loc(lc(3, 0), lc(4, 1)), range: [13, 59], body: [{ type: 'FunctionDeclaration', loc: loc(lc(3, 0), lc(4, 1)), range: [13, 59], id: { type: 'Identifier', loc: loc(lc(3, 9), lc(3, 16)), range: [22, 29], name: 'func', optional: false, }, params: [{ type: 'Identifier', loc: loc(lc(3, 17), lc(3, 28)), range: [30, 41], name: 'foo', typeAnnotation: { type: 'TypeAnnotation', loc: loc(lc(3, 20), lc(3, 28)), range: [33, 41], typeAnnotation: { type: 'StringTypeAnnotation', loc: loc(lc(3, 22), lc(3, 28)), range: [35, 41], }, }, optional: false, }, { type: 'Identifier', loc: loc(lc(3, 30), lc(3, 41)), range: [43, 54], name: 'bar', typeAnnotation: { type: 'TypeAnnotation', loc: loc(lc(3, 33), lc(3, 41)), range: [46, 54], typeAnnotation: { type: 'StringTypeAnnotation', loc: loc(lc(3, 35), lc(3, 41)), range: [48, 54], }, }, optional: false, }], body: { type: 'BlockStatement', loc: loc(lc(3, 43), lc(4, 1)), range: [56, 59], body: [], }, async: false, generator: false, expression: false, }], } /* export function exportedFunc(fooBar: FooBar) { } */ export const exportedFunc = { type: 'Program', loc: loc(lc(3, 0), lc(4, 1)), range: [13, 62], body: [{ type: 'ExportNamedDeclaration', loc: loc(lc(3, 0), lc(4, 1)), range: [13, 62], declaration: { type: 'FunctionDeclaration', loc: loc(lc(3, 7), lc(4, 1)), range: [20, 62], id: { type: 'Identifier', loc: loc(lc(3, 16), lc(3, 29)), range: [29, 42], name: 'exportedFunc', optional: false, }, params: [{ type: 'Identifier', loc: loc(lc(3, 30), lc(3, 44)), range: [43, 57], name: 'fooBar', typeAnnotation: { type: 'TypeAnnotation', loc: loc(lc(3, 36), lc(3, 44)), range: [49, 57], typeAnnotation: { type: 'GenericTypeAnnotation', loc: loc(lc(3, 38), lc(3, 44)), range: [51, 57], id: { type: 'Identifier', loc: loc(lc(3, 38), lc(3, 44)), range: [51, 57], name: 'FooBar', optional: false, }, }, }, optional: false, }], body: { type: 'BlockStatement', loc: loc(lc(3, 46), lc(4, 1)), range: [59, 62], body: [], }, async: false, generator: false, expression: false, }, specifiers: [], exportKind: 'value', }], } /* const constantValue = {} let letValue = 1 var varValue = '' */ export const variables = { type: 'Program', loc: loc(lc(3, 0), lc(5, 17)), range: [13, 72], body: [{ type: 'VariableDeclaration', loc: loc(lc(3, 0), lc(3, 24)), range: [13, 37], declarations: [{ type: 'VariableDeclarator', loc: loc(lc(3, 6), lc(3, 24)), range: [19, 37], id: { type: 'Identifier', loc: loc(lc(3, 6), lc(3, 19)), range: [19, 32], name: 'constantValue', optional: false, }, init: { type: 'ObjectExpression', loc: loc(lc(3, 22), lc(3, 24)), range: [35, 37], properties: [], }, }], kind: 'const', }, { type: 'VariableDeclaration', loc: loc(lc(4, 0), lc(4, 16)), range: [38, 54], declarations: [{ type: 'VariableDeclarator', loc: loc(lc(4, 4), lc(4, 16)), range: [42, 54], id: { type: 'Identifier', loc: loc(lc(4, 4), lc(4, 12)), range: [42, 50], name: 'letValue', optional: false, }, init: { type: 'Literal', loc: loc(lc(4, 15), lc(4, 16)), range: [53, 54], value: 1, raw: '1', }, }], kind: 'let', }, { type: 'VariableDeclaration', loc: loc(lc(5, 0), lc(5, 17)), range: [55, 72], declarations: [{ type: 'VariableDeclarator', loc: loc(lc(5, 4), lc(5, 17)), range: [59, 72], id: { type: 'Identifier', loc: loc(lc(5, 4), lc(5, 12)), range: [59, 67], name: 'varValue', optional: false, }, init: { type: 'Literal', loc: loc(lc(5, 15), lc(5, 17)), range: [70, 72], value: '', raw: "''", }, }], kind: 'var', }], } /* export const exportedConstantValue = {} export let exportedLetValue = 1 export var exportedVarValue = '' */ export const exportedVariables = { errors: [], tokens: [], type: 'Program', loc: loc(lc(3, 0), lc(5, 30)), range: [13, 111], body: [{ type: 'ExportNamedDeclaration', loc: loc(lc(3, 0), lc(3, 37)), range: [13, 50], declaration: { type: 'VariableDeclaration', loc: loc(lc(3, 7), lc(3, 37)), range: [20, 50], declarations: [{ type: 'VariableDeclarator', loc: loc(lc(3, 13), lc(3, 37)), range: [26, 50], id: { type: 'Identifier', loc: loc(lc(3, 13), lc(3, 32)), range: [26, 45], name: 'exportedConstantValue', optional: false, }, init: { type: 'ObjectExpression', loc: loc(lc(3, 35), lc(3, 37)), range: [48, 50], properties: [], }, }], kind: 'const', }, specifiers: [], exportKind: 'value', }, { type: 'ExportNamedDeclaration', loc: loc(lc(4, 0), lc(4, 29)), range: [51, 80], declaration: { type: 'VariableDeclaration', loc: loc(lc(4, 7), lc(4, 29)), range: [58, 80], declarations: [{ type: 'VariableDeclarator', loc: loc(lc(4, 11), lc(4, 29)), range: [62, 80], id: { type: 'Identifier', loc: loc(lc(4, 11), lc(4, 25)), range: [62, 76], name: 'exportedLetValue', optional: false, }, init: { type: 'Literal', loc: loc(lc(4, 28), lc(4, 29)), range: [79, 80], value: 1, raw: '1', }, }], kind: 'let', }, specifiers: [], exportKind: 'value', }, { type: 'ExportNamedDeclaration', loc: loc(lc(5, 0), lc(5, 30)), range: [81, 111], declaration: { type: 'VariableDeclaration', loc: loc(lc(5, 7), lc(5, 30)), range: [88, 111], declarations: [{ type: 'VariableDeclarator', loc: loc(lc(5, 11), lc(5, 30)), range: [92, 111], id: { type: 'Identifier', loc: loc(lc(5, 11), lc(5, 25)), range: [92, 106], name: 'exportedVarValue', optional: false, }, init: { type: 'Literal', loc: loc(lc(5, 28), lc(5, 30)), range: [109, 111], value: '', raw: "''", }, }], kind: 'var', }, specifiers: [], exportKind: 'value', }], } /* export class Component extends React.Component { static propTypes = { } prop = 1 constructor(props: any) { } handleChangeEvent = (ev: Event) => { } } */ export const classDecl = { errors: [], tokens: [], type: 'Program', loc: loc(lc(3, 0), lc(14, 1)), range: [13, 172], body: [{ type: 'ClassDeclaration', loc: loc(lc(3, 0), lc(14, 1)), range: [13, 172], id: { type: 'Identifier', loc: loc(lc(3, 6), lc(3, 15)), range: [19, 28], name: 'Component', optional: false, }, body: { type: 'ClassBody', loc: loc(lc(3, 40), lc(14, 1)), range: [53, 172], body: [{ type: 'ClassProperty', loc: loc(lc(4, 2), lc(5, 3)), range: [57, 81], key: { type: 'Identifier', loc: loc(lc(4, 9), lc(4, 18)), range: [64, 73], name: 'propTypes', optional: false, }, value: { type: 'ObjectExpression', loc: loc(lc(4, 21), lc(5, 3)), range: [76, 81], properties: [], }, computed: false, static: true, }, { type: 'ClassProperty', loc: loc(lc(7, 2), lc(7, 10)), range: [85, 93], key: { type: 'Identifier', loc: loc(lc(7, 2), lc(7, 6)), range: [85, 89], name: 'prop', optional: false, }, value: { type: 'Literal', loc: loc(lc(7, 9), lc(7, 10)), range: [92, 93], value: 1, raw: '1', }, computed: false, static: false, }, { type: 'MethodDefinition', loc: loc(lc(9, 2), lc(10, 3)), range: [97, 126], key: { type: 'Identifier', loc: loc(lc(9, 2), lc(9, 13)), range: [97, 108], name: 'constructor', optional: false, }, value: { type: 'FunctionExpression', loc: loc(lc(9, 13), lc(10, 3)), range: [108, 126], params: [{ type: 'Identifier', loc: loc(lc(9, 14), lc(9, 24)), range: [109, 119], name: 'props', typeAnnotation: { type: 'TypeAnnotation', loc: loc(lc(9, 19), lc(9, 24)), range: [114, 119], typeAnnotation: { type: 'AnyTypeAnnotation', loc: loc(lc(9, 21), lc(9, 24)), range: [116, 119], }, }, optional: false, }], body: { type: 'BlockStatement', loc: loc(lc(9, 26), lc(10, 3)), range: [121, 126], body: [], }, async: false, generator: false, expression: false, }, kind: 'constructor', static: false, computed: false, decorators: [], }, { type: 'ClassProperty', loc: loc(lc(12, 2), lc(13, 3)), range: [130, 170], key: { type: 'Identifier', loc: loc(lc(12, 2), lc(12, 19)), range: [130, 147], name: 'handleChangeEvent', optional: false, }, value: { type: 'ArrowFunctionExpression', loc: loc(lc(12, 22), lc(13, 3)), range: [150, 170], params: [{ type: 'Identifier', loc: loc(lc(12, 23), lc(12, 32)), range: [151, 160], name: 'ev', typeAnnotation: { type: 'TypeAnnotation', loc: loc(lc(12, 25), lc(12, 32)), range: [153, 160], typeAnnotation: { type: 'GenericTypeAnnotation', loc: loc(lc(12, 27), lc(12, 32)), range: [155, 160], id: { type: 'Identifier', loc: loc(lc(12, 27), lc(12, 32)), range: [155, 160], name: 'Event', optional: false, }, }, }, optional: false, }], body: { type: 'BlockStatement', loc: loc(lc(12, 37), lc(13, 3)), range: [165, 170], body: [], }, async: false, generator: false, expression: false, }, computed: false, static: false, }], }, superClass: { type: 'MemberExpression', loc: loc(lc(3, 24), lc(3, 39)), range: [37, 52], object: { type: 'Identifier', loc: loc(lc(3, 24), lc(3, 29)), range: [37, 42], name: 'React', optional: false, }, property: { type: 'Identifier', loc: loc(lc(3, 30), lc(3, 39)), range: [43, 52], name: 'Component', optional: false, }, computed: false, }, implements: [], decorators: [], }], } /* export class ExportedComponent extends React.Component { static propTypes = { } prop = 1 constructor(props: any) { } handleChangeEvent = (ev: Event) => { } } */ export const exportedClassDecl = { type: 'Program', loc: loc(lc(3, 0), lc(14, 1)), range: [13, 179], body: [{ type: 'ExportNamedDeclaration', loc: loc(lc(3, 0), lc(14, 1)), range: [13, 179], declaration: { type: 'ClassDeclaration', loc: loc(lc(3, 7), lc(14, 1)), range: [20, 179], id: { type: 'Identifier', loc: loc(lc(3, 13), lc(3, 22)), range: [26, 35], name: 'ExportedComponent', optional: false, }, body: { type: 'ClassBody', loc: loc(lc(3, 47), lc(14, 1)), range: [60, 179], body: [{ type: 'ClassProperty', loc: loc(lc(4, 2), lc(5, 3)), range: [64, 88], key: { type: 'Identifier', loc: loc(lc(4, 9), lc(4, 18)), range: [71, 80], name: 'propTypes', optional: false, }, value: { type: 'ObjectExpression', loc: loc(lc(4, 21), lc(5, 3)), range: [83, 88], properties: [], }, computed: false, static: true, }, { type: 'ClassProperty', loc: loc(lc(7, 2), lc(7, 10)), range: [92, 100], key: { type: 'Identifier', loc: loc(lc(7, 2), lc(7, 6)), range: [92, 96], name: 'prop', optional: false, }, value: { type: 'Literal', loc: loc(lc(7, 9), lc(7, 10)), range: [99, 100], value: 1, raw: '1', }, computed: false, static: false, }, { type: 'MethodDefinition', loc: loc(lc(9, 2), lc(10, 3)), range: [104, 133], key: { type: 'Identifier', loc: loc(lc(9, 2), lc(9, 13)), range: [104, 115], name: 'constructor', optional: false, }, value: { type: 'FunctionExpression', loc: loc(lc(9, 13), lc(10, 3)), range: [115, 133], params: [{ type: 'Identifier', loc: loc(lc(9, 14), lc(9, 24)), range: [116, 126], name: 'props', typeAnnotation: { type: 'TypeAnnotation', loc: loc(lc(9, 19), lc(9, 24)), range: [121, 126], typeAnnotation: { type: 'AnyTypeAnnotation', loc: loc(lc(9, 21), lc(9, 24)), range: [123, 126], }, }, optional: false, }], body: { type: 'BlockStatement', loc: loc(lc(9, 26), lc(10, 3)), range: [128, 133], body: [], }, async: false, generator: false, expression: false, }, kind: 'constructor', static: false, computed: false, decorators: [], }, { type: 'ClassProperty', loc: loc(lc(12, 2), lc(13, 3)), range: [137, 177], key: { type: 'Identifier', loc: loc(lc(12, 2), lc(12, 19)), range: [137, 154], name: 'handleChangeEvent', optional: false, }, value: { type: 'ArrowFunctionExpression', loc: loc(lc(12, 22), lc(13, 3)), range: [157, 177], params: [{ type: 'Identifier', loc: loc(lc(12, 23), lc(12, 32)), range: [158, 167], name: 'ev', typeAnnotation: { type: 'TypeAnnotation', loc: loc(lc(12, 25), lc(12, 32)), range: [160, 167], typeAnnotation: { type: 'GenericTypeAnnotation', loc: loc(lc(12, 27), lc(12, 32)), range: [162, 167], id: { type: 'Identifier', loc: loc(lc(12, 27), lc(12, 32)), range: [162, 167], name: 'Event', optional: false, }, }, }, optional: false, }], body: { type: 'BlockStatement', loc: loc(lc(12, 37), lc(13, 3)), range: [172, 177], body: [], }, async: false, generator: false, expression: false, }, computed: false, static: false, }], }, superClass: { type: 'MemberExpression', loc: loc(lc(3, 31), lc(3, 46)), range: [44, 59], object: { type: 'Identifier', loc: loc(lc(3, 31), lc(3, 36)), range: [44, 49], name: 'React', optional: false, }, property: { type: 'Identifier', loc: loc(lc(3, 37), lc(3, 46)), range: [50, 59], name: 'Component', optional: false, }, computed: false, }, implements: [], decorators: [], }, specifiers: [], exportKind: 'value', }], } /* export const restSpread = ({ ...rest }) => rest */ export const exportedFuncWithObjectRest = { errors: [], tokens: [], type: 'Program', loc: loc(lc(1, 0), lc(1, 52)), range: [0, 52], body: [ { type: 'ExportNamedDeclaration', loc: loc(lc(1, 0), lc(1, 52)), range: [0, 52], declaration: { type: 'VariableDeclaration', loc: loc(lc(1, 7), lc(1, 52)), range: [7, 52], declarations: [ { type: 'VariableDeclarator', loc: loc(lc(1, 13), lc(1, 52)), range: [13, 52], id: { type: 'Identifier', loc: loc(lc(1, 13), lc(1, 23)), range: [13, 23], name: 'restSpread', optional: false, }, init: { type: 'ArrowFunctionExpression', loc: loc(lc(1, 26), lc(1, 52)), range: [26, 52], params: [ { type: 'AssignmentPattern', loc: loc(lc(1, 27), lc(1, 43)), range: [27, 43], left: { type: 'ObjectPattern', loc: loc(lc(1, 27), lc(1, 38)), range: [27, 38], properties: [ { type: 'RestProperty', loc: loc(lc(1, 29), lc(1, 36)), range: [29, 36], argument: { type: 'Identifier', loc: loc(lc(1, 32), lc(1, 36)), range: [32, 36], name: 'rest', optional: false, }, }, ], }, right: { type: 'ObjectExpression', loc: loc(lc(1, 41), lc(1, 43)), range: [41, 43], properties: [], }, }, ], body: { type: 'Identifier', loc: loc(lc(1, 48), lc(1, 52)), range: [48, 52], name: 'rest', optional: false, }, async: false, generator: false, expression: true, }, }, ], kind: 'const', }, specifiers: [], exportKind: 'value', }, ], comments: [], } /* const { first, second: renamed, ...rest } = {} */ export const objectDestructuring = { errors: [], tokens: [], type: 'Program', loc: loc(lc(1, 0), lc(1, 46)), range: [0, 46], body: [ { type: 'VariableDeclaration', loc: loc(lc(1, 0), lc(1, 46)), range: [0, 46], declarations: [ { type: 'VariableDeclarator', loc: loc(lc(1, 6), lc(1, 46)), range: [6, 46], id: { type: 'ObjectPattern', loc: loc(lc(1, 6), lc(1, 41)), range: [6, 41], properties: [ { type: 'Property', loc: loc(lc(1, 8), lc(1, 13)), range: [8, 13], key: { type: 'Identifier', loc: loc(lc(1, 8), lc(1, 13)), range: [8, 13], name: 'first', optional: false, }, value: { type: 'Identifier', loc: loc(lc(1, 8), lc(1, 13)), range: [8, 13], name: 'first', optional: false, }, kind: 'init', method: false, shorthand: true, computed: false, }, { type: 'Property', loc: loc(lc(1, 15), lc(1, 30)), range: [15, 30], key: { type: 'Identifier', loc: loc(lc(1, 15), lc(1, 21)), range: [15, 21], name: 'second', optional: false, }, value: { type: 'Identifier', loc: loc(lc(1, 23), lc(1, 30)), range: [23, 30], name: 'renamed', optional: false, }, kind: 'init', method: false, shorthand: false, computed: false, }, { type: 'RestProperty', loc: loc(lc(1, 32), lc(1, 39)), range: [32, 39], argument: { type: 'Identifier', loc: loc(lc(1, 35), lc(1, 39)), range: [35, 39], name: 'rest', optional: false, }, }, ], }, init: { type: 'ObjectExpression', loc: loc(lc(1, 44), lc(1, 46)), range: [44, 46], properties: [], }, }, ], kind: 'const', }, ], comments: [], } /* const [first, , ...rest] = [] */ export const arrayDestructuring = { errors: [], tokens: [], type: 'Program', loc: loc(lc(1, 0), lc(1, 29)), range: [0, 29], body: [ { type: 'VariableDeclaration', loc: loc(lc(1, 0), lc(1, 29)), range: [0, 29], declarations: [ { type: 'VariableDeclarator', loc: loc(lc(1, 6), lc(1, 29)), range: [6, 29], id: { type: 'ArrayPattern', loc: loc(lc(1, 6), lc(1, 24)), range: [6, 24], elements: [ { type: 'Identifier', loc: loc(lc(1, 7), lc(1, 12)), range: [7, 12], name: 'first', typeAnnotation: null, optional: false, }, null, { type: 'RestElement', loc: loc(lc(1, 16), lc(1, 23)), range: [16, 23], argument: { type: 'Identifier', loc: loc(lc(1, 19), lc(1, 23)), range: [19, 23], name: 'rest', typeAnnotation: null, optional: false, }, }, ], typeAnnotation: null, }, init: { type: 'ArrayExpression', loc: loc(lc(1, 27), lc(1, 29)), range: [27, 29], elements: [], }, }, ], kind: 'const', }, ], comments: [], } ================================================ FILE: spec/outline/parse-spec.js ================================================ /* @flow */ /* eslint-env jasmine */ import { typeDecl, exportedTypeDecl, func, exportedFunc, variables, exportedVariables, classDecl, exportedClassDecl, objectDestructuring, arrayDestructuring, exportedFuncWithObjectRest, } from './parse-sample-ast' import * as Parse from '../../lib/outline/parse' function buildOptions(exported, args) { return { showKeywords: { export: exported, default: false, const: false, var: false, let: false, class: false, function: false, type: false, }, showFunctionArgs: args, } } function toOutlineText(item, prefix) { const output = [] const { tokenizedText } = item if (!tokenizedText) { return [] } const text = tokenizedText.map(tt => tt.value).join('') output.push(prefix + text) return output.concat( toOutlineTexts(item.children, prefix + ' '), // eslint-disable-line ) } function toOutlineTexts(tree, prefix = '') { return tree.reduce( (output, item) => output.concat(toOutlineText(item, prefix)), [], ) } describe('outline/parse', function() { describe('types', function() { it('local', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), typeDecl).outlineTrees, )).toEqual( ['LocalType'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), typeDecl).outlineTrees, )).toEqual( ['LocalType'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), typeDecl).outlineTrees, )).toEqual( ['LocalType'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), typeDecl).outlineTrees, )).toEqual( ['LocalType'], ) }) it('exported', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), exportedTypeDecl).outlineTrees, )).toEqual( ['ExportedType'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), exportedTypeDecl).outlineTrees, )).toEqual( ['export ExportedType'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), exportedTypeDecl).outlineTrees, )).toEqual( ['ExportedType'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), exportedTypeDecl).outlineTrees, )).toEqual( ['export ExportedType'], ) }) }) describe('functions', function() { it('local', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), func).outlineTrees, )).toEqual( ['func'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), func).outlineTrees, )).toEqual( ['func'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), func).outlineTrees, )).toEqual( ['func(foo, bar)'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), func).outlineTrees, )).toEqual( ['func(foo, bar)'], ) }) it('exported', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), exportedFunc).outlineTrees, )).toEqual( ['exportedFunc'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), exportedFunc).outlineTrees, )).toEqual( ['export exportedFunc'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), exportedFunc).outlineTrees, )).toEqual( ['exportedFunc(fooBar)'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), exportedFunc).outlineTrees, )).toEqual( ['export exportedFunc(fooBar)'], ) }) }) describe('variables', function() { it('local', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), variables).outlineTrees, )).toEqual( ['constantValue', 'letValue', 'varValue'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), variables).outlineTrees, )).toEqual( ['constantValue', 'letValue', 'varValue'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), variables).outlineTrees, )).toEqual( ['constantValue', 'letValue', 'varValue'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), variables).outlineTrees, )).toEqual( ['constantValue', 'letValue', 'varValue'], ) }) it('exported', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), exportedVariables).outlineTrees, )).toEqual( ['exportedConstantValue', 'exportedLetValue', 'exportedVarValue'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), exportedVariables).outlineTrees, )).toEqual( ['export exportedConstantValue', 'export exportedLetValue', 'export exportedVarValue'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), exportedVariables).outlineTrees, )).toEqual( ['exportedConstantValue', 'exportedLetValue', 'exportedVarValue'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), exportedVariables).outlineTrees, )).toEqual( ['export exportedConstantValue', 'export exportedLetValue', 'export exportedVarValue'], ) }) it('object destructuring', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), objectDestructuring).outlineTrees, )).toEqual( ['{first, renamed, ...rest}'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), objectDestructuring).outlineTrees, )).toEqual( ['{first, renamed, ...rest}'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), objectDestructuring).outlineTrees, )).toEqual( ['{first, renamed, ...rest}'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), objectDestructuring).outlineTrees, )).toEqual( ['{first, renamed, ...rest}'], ) }) it('array destructuring', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), arrayDestructuring).outlineTrees, )).toEqual( ['[first, , ...rest]'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), arrayDestructuring).outlineTrees, )).toEqual( ['[first, , ...rest]'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), arrayDestructuring).outlineTrees, )).toEqual( ['[first, , ...rest]'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), arrayDestructuring).outlineTrees, )).toEqual( ['[first, , ...rest]'], ) }) it('exported func with object rest', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), exportedFuncWithObjectRest).outlineTrees, )).toEqual( ['restSpread'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), exportedFuncWithObjectRest).outlineTrees, )).toEqual( ['export restSpread'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), exportedFuncWithObjectRest).outlineTrees, )).toEqual( ['restSpread({...rest})'], ) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), exportedFuncWithObjectRest).outlineTrees, )).toEqual( ['export restSpread({...rest})'], ) }) }) describe('class', function() { it('local', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), classDecl).outlineTrees, )).toEqual([ 'Component', ' propTypes', ' prop', ' constructor', ' handleChangeEvent', ]) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), classDecl).outlineTrees, )).toEqual([ 'Component', ' propTypes', ' prop', ' constructor', ' handleChangeEvent', ]) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), classDecl).outlineTrees, )).toEqual([ 'Component', ' propTypes', ' prop', ' constructor(props)', ' handleChangeEvent(ev)', ]) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), classDecl).outlineTrees, )).toEqual([ 'Component', ' propTypes', ' prop', ' constructor(props)', ' handleChangeEvent(ev)', ]) }) it('exported', function() { expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, false), exportedClassDecl).outlineTrees, )).toEqual([ 'ExportedComponent', ' propTypes', ' prop', ' constructor', ' handleChangeEvent', ]) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, false), exportedClassDecl).outlineTrees, )).toEqual([ 'export ExportedComponent', ' propTypes', ' prop', ' constructor', ' handleChangeEvent', ]) expect(toOutlineTexts( Parse.astToOutline(buildOptions(false, true), exportedClassDecl).outlineTrees, )).toEqual([ 'ExportedComponent', ' propTypes', ' prop', ' constructor(props)', ' handleChangeEvent(ev)', ]) expect(toOutlineTexts( Parse.astToOutline(buildOptions(true, true), exportedClassDecl).outlineTrees, )).toEqual([ 'export ExportedComponent', ' propTypes', ' prop', ' constructor(props)', ' handleChangeEvent(ev)', ]) }) }) }) ================================================ FILE: styles/flow-ide.less ================================================ .flow-ide-hide { display: none; } ================================================ FILE: vendor/.flowconfig ================================================ [ignore] [include] [libs] [options]