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

#### 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<string>,
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<string>,
};
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<atom$CommandDescriptor>,
}
declare class atom$CompositeDisposable {
constructor(...disposables: Array<IDisposable>): void,
dispose(): void,
add(...disposables: Array<IDisposable>): 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<string | {value: string, description: string}>,
maximum?: number,
minimum?: number,
properties?: Object,
title?: string,
type: Array<atom$ConfigType> | 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<string>,
sources?: Array<string>,
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<atom$Marker>,
getMarkers(): Array<atom$Marker>,
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>) => 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>): 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<T>(keyPath: string, version: string, service: T): IDisposable,
consume<T>(
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<string>,
// 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<atom$Package>,
isPackageActive(name: string): boolean,
hasActivatedInitialPackages(): boolean,
// Activating and deactivating packages
activatePackage(name: string): Promise<atom$Package>,
// Accessing loaded packages
getLoadedPackage(name: string): ?atom$Package,
getLoadedPackages(): Array<atom$Package>,
isPackageLoaded(name: string): boolean,
hasLoadedInitialPackages(): boolean,
// Accessing available packages
getAvailablePackageNames(): Array<string>,
getAvailablePackageMetadata(): Array<atom$PackageMetadata>,
// (Undocumented.)
activate(): Promise<any>,
deactivatePackages(): Promise<void>,
deactivatePackage(name: string, suppressSerialization?: boolean): Promise<void>,
emitter: atom$Emitter,
loadedPackages: {[packageName: string]: atom$Package},
loadPackage(name: string): void,
loadPackages(): void,
serializePackage(pkg: atom$Package): void,
serviceHub: atom$ServiceHub,
packageDirPaths: Array<string>,
triggerActivationHook(hook: string): void,
triggerDeferredActivationHooks(): void,
unloadPackage(name: string): void,
unloadPackages(): void,
}
declare class atom$StyleManager {
// Event Subscription
// Reading Style Elements
getStyleElements(): Array<HTMLStyleElement>,
// Paths
getUserStyleSheetPath(): string,
// (Undocumented.)
addStyleSheet(
source: string,
params: {
sourcePath?: string,
context?: boolean,
priority?: number,
skipDeprecatedSelectorsTransformation?: boolean
}
): IDisposable,
}
type atom$PaneSplitParams = {
copyActiveItem?: boolean,
items?: Array<Object>,
};
type atom$PaneSplitOrientation = 'horizontal' | 'vertical';
type atom$PaneSplitSide = 'before' | 'after';
// Undocumented class
declare class atom$applicationDelegate {
focusWindow(): Promise<mixed>,
open(params: {
pathsToOpen: Array<string>,
newWindow?: boolean,
devMode?: boolean,
safeMode?: boolean,
}): void,
// Used by nuclide-config to replicate atom.config saveCallback
setUserSettings(config: atom$Config, configFilePath: string): Promise<mixed>;
}
type atom$PaneParams = {
activeItem?: Object,
applicationDelegate: atom$applicationDelegate,
focused?: boolean,
container: Object,
config: atom$Config,
notificationManager: atom$NotificationManager,
deserializerManager: atom$DeserializerManager,
items?: Array<Object>,
itemStackIndices?: Array<number>,
flexScale?: number,
};
declare class atom$Pane {
// Items
addItem(item: Object, options?: {index?: number, pending?: boolean}): Object,
getItems(): Array<Object>,
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<boolean>,
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<Object>,
}
// 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<atom$Pane>,
getPaneItems(): Array<atom$PaneItem>,
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<Array<number>>,
translate(startDelta: atom$PointLike, endDelta?: atom$PointLike): atom$Range,
getRowCount(): number,
getRows(): Array<number>,
}
type RawStatusBarTile = {
item: HTMLElement,
priority: number,
};
type atom$StatusBarTile = {
getPriority(): number,
getItem(): HTMLElement,
destroy(): void,
};
declare class atom$ScopeDescriptor {
constructor(object: {scopes: Array<string>}): void,
getScopesArray(): Array<string>,
}
type atom$ScopeDescriptorLike = atom$ScopeDescriptor | Array<string>;
/**
* 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<atom$StatusBarTile>,
getRightTiles(): Array<atom$StatusBarTile>,
}
// 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<atom$TextEditor>,
}
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<string>,
getLoadedThemes(): Array<mixed>, // TODO: Define undocumented ThemePackage class.
// Accessing Active Themes
getActiveThemeNames(): Array<string>,
getActiveThemes(): Array<mixed>, // TODO: Define undocumented ThemePackage class.
// Managing Enabled Themes
getEnabledThemeNames(): Array<string>,
// Private
activateThemes(): Promise<any>,
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<HTMLElement, Array<atom$Tooltip>>;
add(
target: HTMLElement,
options: atom$TooltipsAddOptions,
): IDisposable,
findTooltips(HTMLElement): Array<atom$Tooltip>,
}
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<void>,
// 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<atom$Range> | 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<Object>},
getDecorations(options?: {class?: string, type?: string}): Array<atom$Decoration>,
// 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<atom$Marker>,
getMarkerCount(): number,
// Cursors
getCursors(): Array<atom$Cursor>,
setCursorBufferPosition(
position: atom$PointLike,
options?: {
autoscroll?: boolean,
wrapBeyondNewlines?: boolean,
wrapAtSoftNewlines?: boolean,
screenLine?: boolean,
}): void,
getCursorBufferPosition(): atom$Point,
getCursorBufferPositions(): Array<atom$Point>,
getCursorScreenPosition(): atom$Point,
getCursorScreenPositions(): Array<atom$Point>,
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<atom$Range>,
getSelections(): Array<atom$Selection>,
selectToBufferPosition(point: atom$Point): void,
setSelectedBufferRange(
bufferRange: atom$RangeLike,
options?: {
reversed?: boolean,
preserveFolds?: boolean,
},
): void,
setSelectedBufferRanges(
bufferRanges: Array<atom$Range>,
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<atom$Gutter>,
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(<TextEditor>)`.
*/
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<void>,
// `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<any>) => ?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<atom$ViewProvider>,
}
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<atom$TextEditor>,
openURIInPane(
uri?: string,
pane: atom$Pane,
options?: {
initialLine?: number,
initialColumn?: number,
activePane?: boolean,
searchAllPanes?: boolean,
}
): Promise<atom$TextEditor>,
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<?atom$TextEditor>,
addOpener(callback: (uri: string) => any): IDisposable,
hide(uriOrItem: string | Object): void,
toggle(uriOrItem: string | Object): void,
// Pane Containers
getPaneContainers(): Array<atom$AbstractPaneContainer>,
paneContainerForItem(item: ?mixed): ?atom$AbstractPaneContainer,
// Pane Items
getPaneItems(): Array<Object>,
getActivePaneItem(): ?Object,
getActivePaneContainer(): atom$PaneContainer,
getTextEditors(): Array<atom$TextEditor>,
getActiveTextEditor(): ?atom$TextEditor,
createItemForURI(uri: string, options: atom$WorkspaceOpenOptions): atom$PaneItem,
// Panes
getPanes(): Array<atom$Pane>,
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<atom$Panel>,
addBottomPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel,
getLeftPanels(): Array<atom$Panel>,
addLeftPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel,
getRightPanels(): Array<atom$Panel>,
addRightPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel,
getTopPanels(): Array<atom$Panel>,
addTopPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel,
getModalPanels(): Array<atom$Panel>,
addModalPanel(options: atom$WorkspaceAddPanelOptions): atom$Panel,
getHeaderPanels(): Array<atom$Panel>,
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<string>,
onPathsSearched?: (numSearched: number) => mixed,
leadingContextLineCount?: number,
trailingContextLineCount?: number,
},
iterator: (
?{
filePath: string,
matches: Array<{
leadingContextLines: Array<string>,
lineText: string,
lineTextOffset: number,
range: atom$RangeLike,
matchText: string,
trailingContextLines: Array<string>,
}>,
},
Error,
) => mixed,
): Promise<?string>,
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<atom$Pane>,
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<atom$PaneItem>,
getActivePaneItem(): ?atom$PaneItem,
getTextEditors(): Array<atom$TextEditor>,
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<atom$ContextMenuItem>}): IDisposable,
itemSets: Array<atom$ContextMenuItemSet>,
// Undocumented methods
showForEvent(event: Event): void,
templateForEvent(event: Event): Array<Object>,
}
declare class atom$ContextMenuItemSet {
items: Array<atom$ContextMenuItem>,
selector: string,
}
type atom$ContextMenuItem = {
command?: string,
created?: (event: MouseEvent) => void,
enabled?: boolean,
label?: string,
shouldDisplay?: (event: MouseEvent) => boolean,
submenu?: Array<atom$ContextMenuItem>,
type?: string,
visible?: boolean,
};
type atom$Deserializer = {
name: string,
deserialize: (state: Object) => mixed,
};
declare class atom$DeserializerManager {
add(...deserializers: Array<atom$Deserializer>): 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<boolean>,
// Event Subscription
onDidChange(callback: () => mixed): IDisposable,
// Directory Metadata
isFile(): boolean,
isDirectory(): boolean,
exists():Promise<boolean>,
// 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<atom$Directory | atom$File>,
) => 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<boolean>,
// File Metadata
isFile(): boolean,
isDirectory(): boolean,
exists(): Promise<boolean>,
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<string>,
write(text: string): Promise<void>,
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<Array<atom$GrammarToken>>,
tokenizeLine(line: string, ruleStack: mixed, firstLine: boolean): {
line: string,
tags: Array<number>,
// Dynamic property: invoking it will incur additional overhead
tokens: Array<atom$GrammarToken>,
ruleStack: mixed
},
}
type atom$GrammarToken = {
value: string,
scopes: Array<string>,
};
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<string>): void,
getProjects(): Array<atom$HistoryProject>,
}
declare class atom$HistoryProject {
paths: Array<string>;
lastOpened: Date;
}
// https://github.com/atom/atom-keymap/blob/18f00ac307de5770bb8f98958bd9a13ecffa9e68/src/key-binding.coffee
declare class atom$KeyBinding {
cachedKeyups: ?Array<string>,
command: string,
index: number,
keystrokeArray : Array<string>,
keystrokeCount: number,
keystrokes: string,
priority: number,
selector: string,
source: string,
specificity: number,
matches(keystroke: string): boolean,
compare(keybinding: atom$KeyBinding): number,
getKeyups(): ?Array<string>,
matchesKeystrokes(userKeystrokes: Array<string>): 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<atom$KeyBinding>,
findKeyBindings(params: {
keystrokes?: string,
command?: string,
target?: HTMLElement,
}): Array<atom$KeyBinding>,
// 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<Object>): IDisposable,
update(): void,
// Private API
template: Array<Object>,
}
type atom$ProjectSpecification = {
originPath: string,
paths?: Array<string>,
config?: {[string]: mixed}
};
declare class atom$Project {
// Event Subscription
onDidChangePaths(callback: (projectPaths: Array<string>) => 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<?atom$Repository>,
repositoryForDirectory(directory: atom$Directory): Promise<?atom$Repository>,
// Managing Paths
getPaths(): Array<string>,
addPath(projectPath: string, options?: {
emitEvent?: boolean,
exact?: boolean,
mustExist?: boolean,
}): void,
setPaths(paths: Array<string>): void,
removePath(projectPath: string): void,
getDirectories(): Array<atom$Directory>,
relativizePath(relativizePath?: string): Array<string>, // [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<atom$TextBuffer>,
}
type TextBufferScanIterator = (arg: {
match: Array<string>,
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<atom$TextEditEvent>,
};
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<atom$TextBuffer>,
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<string>,
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<mixed>,
// 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<void>,
save(): Promise<void>,
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<void>,
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<atom$NotificationButton>,
};
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<atom$Notification>,
}
// 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<string>,
},
(number) => void,
) => void)
// The synchronous form. You really shouldn't use this.
& (({
message: string,
detailedMessage?: string,
buttons?: Array<string> | {[buttonName: string]: () => mixed},
}) => ?number),
open(params: {
pathsToOpen?: Array<string>,
newWindow?: boolean,
devMode?: boolean,
safeMode?: boolean,
}): void,
reload(): void,
restartApplication(): void,
// Undocumented Methods
getConfigDirPath(): string,
showSaveDialogSync(options: Object): string,
loadState(): Promise<?Object>,
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<void>,
// Retrieving Diffs
getDiffStats: (filePath: string) => {added: number, deleted: number},
getLineDiffs: (aPath: string, text: string) => Array<RepositoryLineDiff>,
// Checking Out
checkoutHead: (aPath: string) => boolean,
checkoutReference: (reference: string, create: boolean) => Promise<void>,
// 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<atom$AutocompleteSuggestion>> | ?Array<atom$AutocompleteSuggestion>,
+getSuggestionDetailsOnSelect?: (
suggestion: atom$AutocompleteSuggestion
) => Promise<?atom$AutocompleteSuggestion>,
+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<string>,
) => 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<atom$Panel>,
subscriptions: atom$CompositeDisposable,
viewRegistry: atom$ViewRegistry,
getPanels(): Array<atom$Panel>,
};
================================================
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<?HyperclickSuggestion>,
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<?HyperclickSuggestion>,
// 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<Range>,
// 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<mixed>,
func?: () => Promise<mixed>
): 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> | 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<TextToken>;
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<OutlineTree>,
};
declare type Outline = {
outlineTrees: Array<OutlineTree>,
};
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<string>,
updateOnEdit?: boolean,
getOutline(editor: TextEditor): Promise<?Outline>,
};
}
================================================
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)<br>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<string> {
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<?string> {
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<string>,
}
const servers: Map<string, Server> = new Map()
export class Flow {
subscriptions: CompositeDisposable
executablePath: string
onlyIfAppropriate: boolean
hyperclickPriority: number
showUncovered: boolean
coverageView: any
coverages: WeakMap<TextEditor, CoverageObject>
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<?string> {
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<?string> {
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<?HyperclickSuggestion> => {
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<any> => {
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=<path>&row=<number>&column=<number>
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 '<div style="white-space: pre-wrap">' + messages.map(msg => prettyPrintMessage(mainFile, msg)).join('\n') + '</div>'
}
================================================
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=<path>&row=<number>&column=<number>
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<Object>): 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<Object>,
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<Object> = 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<Object> {
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<Object>): Array<OutlineTree> {
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]
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
SYMBOL INDEX (64 symbols across 14 files)
FILE: decls/atom.js
class atom$Model (line 66) | class atom$Model {
method startCursorBlinking (line 1127) | startCursorBlinking: () => void,
method pixelPositionForBufferPosition (line 1184) | pixelPositionForBufferPosition(
FILE: lib/coverage-view.js
class CoverageView (line 5) | class CoverageView extends HTMLElement {
method initialize (line 8) | initialize(): void {
method update (line 16) | update(json: CoverageObject): void {
method reset (line 34) | reset() {
method destroy (line 42) | destroy(): void {
FILE: lib/helpers.js
function getExecutablePathSync (line 23) | function getExecutablePathSync(fileDirectory: string): string {
FILE: lib/index.js
constant INIT_MESSAGE (line 22) | const INIT_MESSAGE = 'flow server'
constant RECHECKING_MESSAGE (line 23) | const RECHECKING_MESSAGE = 'flow is'
method if (line 81) | if (this.coverageView) {
method callback (line 441) | callback() {
FILE: lib/language-client.js
method getGrammarScopes (line 11) | getGrammarScopes() { return Helpers.grammarScopes }
method getLanguageName (line 12) | getLanguageName() { return 'Flowtype' }
method getServerName (line 13) | getServerName() { return 'flow' }
method startServerProcess (line 15) | async startServerProcess(projectPath: string) {
FILE: lib/linter/v1/index.js
function locationToRange (line 10) | function locationToRange({ start, end }: Location): Range {
function toLinterLocation (line 16) | function toLinterLocation(loc: Location) {
function toLinterReference (line 23) | function toLinterReference(messages: StatusMessage[]) {
function toLinterMessages (line 36) | function toLinterMessages(contents: string): Message[] {
FILE: lib/linter/v1/pretty.js
function fileUrl (line 14) | function fileUrl({ source, start }: Location): string {
function linterLink (line 26) | function linterLink(loc: Location, text: string) {
function fileLink (line 29) | function fileLink(loc: Location): string {
function lineLink (line 32) | function lineLink(loc: Location): string {
function mainMessageOfError (line 36) | function mainMessageOfError(error: StatusError): StatusMessage {
function getExtraMessages (line 44) | function getExtraMessages(extra: ?StatusExtra[]): StatusMessage[] {
function getTraceReasons (line 59) | function getTraceReasons(trace: ?StatusMessage[]): StatusMessage[] {
function mkComment (line 66) | function mkComment(descr: string): StatusMessage {
function getOpReason (line 70) | function getOpReason(op: ?StatusMessage): StatusMessage[] {
function getHeader (line 80) | function getHeader(mainLoc: ?Location, kind: string, level: string): Sta...
function prettyPrintMessage (line 108) | function prettyPrintMessage(mainFile: string, { context, descr, loc, ind...
function mergedMessagesOfError (line 157) | function mergedMessagesOfError(error: StatusError): StatusMessage[] {
function prettyPrintError (line 180) | function prettyPrintError(error: StatusError): string {
FILE: lib/linter/v2/index.js
function locationToRange (line 8) | function locationToRange({ start, end }: Location): Range {
function locactionToLinterLocation (line 15) | function locactionToLinterLocation(loc: Location) {
function plainText (line 22) | function plainText(messages: InlineMarkup[], references: ReferenceLocs):...
function fileUrl (line 45) | function fileUrl(loc: Location, path: string): string {
function markup (line 63) | function markup(messages: MessageMarkup, references: ReferenceLocs, path...
function toLinterMessages (line 95) | function toLinterMessages(json: string, path: string): Message[] {
FILE: lib/main.js
method onDidClick (line 9) | onDidClick () { return atom.restartApplication() }
FILE: lib/outline/index.js
function toOutline (line 7) | function toOutline(result: string, options: OutlineOptions): Outline {
FILE: lib/outline/parse.js
function itemToTree (line 13) | function exportDeclaration(
method if (line 319) | if (fn == null) {
method if (line 322) | if (
method switch (line 374) | switch (expressionStatement.expression.type) {
method if (line 390) | if (
method if (line 430) | if (item == null) {
method let (line 445) | let representativeName
method if (line 455) | if (options.showKeywords.class) {
FILE: lib/outline/text.js
function buildToken (line 5) | function buildToken(kind: TokenKind, value: string): TextToken {
function keyword (line 9) | function keyword(value: string): TextToken {
function className (line 13) | function className(value: string): TextToken {
function constructor (line 17) | function constructor(value: string): TextToken {
function method (line 21) | function method(value: string): TextToken {
function param (line 25) | function param(value: string): TextToken {
function string (line 29) | function string(value: string): TextToken {
function whitespace (line 33) | function whitespace(value: string): TextToken {
function plain (line 37) | function plain(value: string): TextToken {
function type (line 41) | function type(value: string): TextToken {
FILE: spec/linter/v2/index-spec.js
function fileUrl (line 7) | function fileUrl(file, row, column) {
FILE: spec/outline/parse-spec.js
function buildOptions (line 14) | function buildOptions(exported, args) {
function toOutlineText (line 30) | function toOutlineText(item, prefix) {
function toOutlineTexts (line 44) | function toOutlineTexts(tree, prefix = '') {
Condensed preview — 36 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (189K chars).
[
{
"path": ".babelrc",
"chars": 221,
"preview": "{\n \"presets\": [\n [\"env\", { \"targets\": { \"node\": \"current\" } }]\n ],\n \"sourceMap\": \"inline\",\n \"plugins\": [\n \"tra"
},
{
"path": ".editorconfig",
"chars": 171,
"preview": "root = true\n\n[*]\nindent_style = space\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newl"
},
{
"path": ".eslintignore",
"chars": 6,
"preview": "decls\n"
},
{
"path": ".eslintrc.json",
"chars": 289,
"preview": "{\n \"extends\": \"steelbrain\",\n \"plugins\": [\n \"flowtype\"\n ],\n \"rules\": {\n \"import/prefer-default-export\": \"off\",\n"
},
{
"path": ".flowconfig",
"chars": 245,
"preview": "[ignore]\n\n[include]\n\n[libs]\ndecls\n\n[options]\nmodule.system=node\ninclude_warnings=true\nsuppress_comment= \\\\(.\\\\|\\n\\\\)*\\\\$"
},
{
"path": ".gitignore",
"chars": 35,
"preview": ".idea\nnode_modules\n*.log\n.DS_Store\n"
},
{
"path": "CHANGELOG.md",
"chars": 3659,
"preview": "#### 1.13.0\n\n- Use the official [language server protocol](https://microsoft.github.io/language-server-protocol/) to com"
},
{
"path": "LICENSE.md",
"chars": 1054,
"preview": "Copyright (c) 2016 steelbrain\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this soft"
},
{
"path": "README.md",
"chars": 2116,
"preview": "Flow-IDE\n=======\n\nFlow IDE is a lightweight package that provides IDE features for [FlowType][FlowType] for [Atom Editor"
},
{
"path": "decls/atom.js",
"chars": 69354,
"preview": "/**\n * Copyright (c) 2015-present, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the l"
},
{
"path": "decls/hyperclick.js",
"chars": 1196,
"preview": "/* @flow */\n\ndeclare module 'atom-ide-ui/hyperclick' {\n import type { Point, Range, TextEditor } from 'atom'\n\n declare"
},
{
"path": "decls/jasmine-atom.js",
"chars": 186,
"preview": "/* @flow */\n\ndeclare function waitsForPromise(\n optionsOrFunc: {timeout?: number, shouldReject?: boolean, label?: strin"
},
{
"path": "decls/jasmine.js",
"chars": 544,
"preview": "/* @flow */\n\ndeclare var jasmine: Object;\ndeclare function it(name: string, callback: (() => void)): void;\ndeclare funct"
},
{
"path": "decls/linter.js",
"chars": 741,
"preview": "/* @flow */\n\ndeclare module 'linter' {\n import type { Point, Range } from 'atom'\n\n declare type LinterProvider = {\n }"
},
{
"path": "decls/outline.js",
"chars": 1686,
"preview": "/* @flow */\n\ndeclare module 'atom-ide-ui/outline' {\n import type { Point, TextEditor } from 'atom'\n\n declare type Toke"
},
{
"path": "lib/coverage-view.js",
"chars": 1383,
"preview": "/* @flow */\n\nimport type { CoverageObject } from './types'\n\nclass CoverageView extends HTMLElement {\n tooltipDisposable"
},
{
"path": "lib/helpers.js",
"chars": 1044,
"preview": "/* @flow */\n\nimport { findCached, findCachedAsync } from 'atom-linter'\nimport * as Path from 'path'\n\nconst executable = "
},
{
"path": "lib/index.js",
"chars": 19931,
"preview": "/* @flow */\n\nimport { CompositeDisposable } from 'atom'\nimport type { TextEditor, Point, Range } from 'atom'\nimport { sh"
},
{
"path": "lib/language-client.js",
"chars": 945,
"preview": "/* @flow */\n\nimport { TextEditor } from 'atom'\nimport { AutoLanguageClient } from '@lloiser/atom-languageclient'\nimport "
},
{
"path": "lib/linter/v1/index.js",
"chars": 1643,
"preview": "/* @flow */\n\nimport { Range } from 'atom'\nimport type { Message } from 'linter'\n\nimport { mainMessageOfError, prettyPrin"
},
{
"path": "lib/linter/v1/pretty.js",
"chars": 5569,
"preview": "/* @flow */\n\n// Note: the following code is based on\n// https://github.com/facebook/flow/blob/v0.47.0/tsrc/flowResult.js"
},
{
"path": "lib/linter/v1/types.js",
"chars": 622,
"preview": "/* @flow */\n\nimport type { Location } from '../../types'\n\nexport type StatusMessage = {\n context: ?string,\n descr: str"
},
{
"path": "lib/linter/v2/index.js",
"chars": 3098,
"preview": "/* @flow */\n\nimport { Range } from 'atom'\nimport type { Message } from 'linter'\n\nimport type { Location, MessageMarkup, "
},
{
"path": "lib/linter/v2/types.js",
"chars": 1965,
"preview": "/* @flow */\n\nexport type Position = {\n line: number,\n column: number,\n offset: number\n}\n\nexport type Location = {\n s"
},
{
"path": "lib/main.js",
"chars": 734,
"preview": "/* @flow */\n\nconst useLSP = atom.config.get('flow-ide.useLSP')\n\n// notify to restart if the value changes\natom.config.on"
},
{
"path": "lib/outline/index.js",
"chars": 305,
"preview": "/* @flow */\n\nimport type { Outline } from 'atom-ide-ui/outline'\nimport { astToOutline } from './parse'\nimport type { Out"
},
{
"path": "lib/outline/parse.js",
"chars": 14324,
"preview": "/* @flow */\n\n// NOTE: the following code is based on\n// https://github.com/flowtype/flow-language-server/blob/fbd1bc3/sr"
},
{
"path": "lib/outline/text.js",
"chars": 1008,
"preview": "/* @flow */\n\nimport type { TokenKind, TextToken } from 'atom-ide-ui/outline'\n\nfunction buildToken(kind: TokenKind, value"
},
{
"path": "lib/outline/types.js",
"chars": 369,
"preview": "/* @flow */\n\nimport type { Point } from 'atom'\n\nexport type OutlineOptions = {\n showKeywords: {\n export: boolean,\n "
},
{
"path": "lib/types.js",
"chars": 654,
"preview": "/* @flow */\n\nexport type Position = {\n line: number,\n column: number,\n offset: number,\n end: number,\n}\n\nexport type "
},
{
"path": "package.json",
"chars": 4634,
"preview": "{\n \"name\": \"flow-ide\",\n \"private\": true,\n \"version\": \"1.13.0\",\n \"description\": \"Flowtype support in Atom without any"
},
{
"path": "spec/linter/v2/index-spec.js",
"chars": 2886,
"preview": "/* @flow */\n/* eslint-env jasmine */\n\nimport { findCached, exec } from 'atom-linter'\nimport * as LinterV2 from '../../.."
},
{
"path": "spec/outline/parse-sample-ast.js",
"chars": 27680,
"preview": "/* @flow */\n\nconst lc = (line, column) => ({ line, column })\nconst loc = (start, end, source = null) => ({ start, end, s"
},
{
"path": "spec/outline/parse-spec.js",
"chars": 10307,
"preview": "/* @flow */\n/* eslint-env jasmine */\n\nimport {\n typeDecl, exportedTypeDecl,\n func, exportedFunc,\n variables, exported"
},
{
"path": "styles/flow-ide.less",
"chars": 36,
"preview": ".flow-ide-hide {\n display: none;\n}\n"
},
{
"path": "vendor/.flowconfig",
"chars": 39,
"preview": "[ignore]\n\n[include]\n\n[libs]\n\n[options]\n"
}
]
About this extraction
This page contains the full source code of the steelbrain/flow-ide GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 36 files (176.4 KB), approximately 47.2k tokens, and a symbol index with 64 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.