10 |
11 | Permission is hereby granted, free of charge, to any person obtaining a copy of
12 | this software and associated documentation files (the "Software"), to deal in
13 | the Software without restriction, including without limitation the rights to
14 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
15 | the Software, and to permit persons to whom the Software is furnished to do so,
16 | subject to the following conditions:
17 |
18 | The above copyright notice and this permission notice shall be included in all
19 | copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
23 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
24 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 |
--------------------------------------------------------------------------------
/lib/AtomConfig.coffee:
--------------------------------------------------------------------------------
1 | Config = require './Config'
2 |
3 | module.exports =
4 |
5 | ##*
6 | # Config that retrieves its settings from Atom's config.
7 | ##
8 | class AtomConfig extends Config
9 | ###*
10 | * The name of the package to use when searching for settings.
11 | ###
12 | packageName: null
13 |
14 | ###*
15 | * @inheritdoc
16 | ###
17 | constructor: (@packageName) ->
18 | super()
19 |
20 | @attachListeners()
21 |
22 | ###*
23 | * @inheritdoc
24 | ###
25 | load: () ->
26 | @set('phpCommand', atom.config.get("#{@packageName}.phpCommand"))
27 | @set('additionalIndexingDelay', atom.config.get("#{@packageName}.additionalIndexingDelay"))
28 | @set('memoryLimit', atom.config.get("#{@packageName}.memoryLimit"))
29 | @set('insertNewlinesForUseStatements', atom.config.get("#{@packageName}.insertNewlinesForUseStatements"))
30 | @set('packagePath', atom.packages.resolvePackagePath("#{@packageName}"))
31 |
32 | ###*
33 | * Attaches listeners to listen to Atom configuration changes.
34 | ###
35 | attachListeners: () ->
36 | atom.config.onDidChange "#{@packageName}.phpCommand", () =>
37 | @set('phpCommand', atom.config.get("#{@packageName}.phpCommand"))
38 |
39 | atom.config.onDidChange "#{@packageName}.additionalIndexingDelay", () =>
40 | @set('additionalIndexingDelay', atom.config.get("#{@packageName}.additionalIndexingDelay"))
41 |
42 | atom.config.onDidChange "#{@packageName}.memoryLimit", () =>
43 | @set('memoryLimit', atom.config.get("#{@packageName}.memoryLimit"))
44 |
45 | atom.config.onDidChange "#{@packageName}.insertNewlinesForUseStatements", () =>
46 | @set('insertNewlinesForUseStatements', atom.config.get("#{@packageName}.insertNewlinesForUseStatements"))
47 |
--------------------------------------------------------------------------------
/lib/CoreManager.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | path = require 'path'
3 |
4 | module.exports =
5 |
6 | ##*
7 | # Handles management of the (PHP) core that is needed to handle the server side.
8 | ##
9 | class CoreManager
10 | ###*
11 | * The commit to download from the Composer repository.
12 | *
13 | * Currently set to version 1.2.4.
14 | *
15 | * @see https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md
16 | *
17 | * @var {String}
18 | ###
19 | COMPOSER_COMMIT: 'd0310b646229c3dc57b71bfea2f14ed6c560a5bd'
20 |
21 | ###*
22 | * @var {String}
23 | ###
24 | COMPOSER_PACKAGE_NAME: 'php-integrator/core'
25 |
26 | ###*
27 | * @var {ComposerService}
28 | ###
29 | composerService: null
30 |
31 | ###*
32 | * @var {String}
33 | ###
34 | versionSpecification: null
35 |
36 | ###*
37 | * @var {String}
38 | ###
39 | folder: null
40 |
41 | ###*
42 | * @param {ComposerService} composerService
43 | * @param {String} versionSpecification
44 | * @param {String} folder
45 | ###
46 | constructor: (@composerService, @versionSpecification, @folder) ->
47 |
48 | ###*
49 | * @return {Promise}
50 | ###
51 | install: () ->
52 | return @composerService.run([
53 | 'create-project',
54 | @COMPOSER_PACKAGE_NAME,
55 | @getCoreSourcePath(),
56 | @versionSpecification,
57 | '--prefer-dist',
58 | '--no-dev',
59 | '--no-progress'
60 | ], @folder)
61 |
62 | ###*
63 | * @return {Boolean}
64 | ###
65 | isInstalled: () ->
66 | return fs.existsSync(@getComposerLockFilePath())
67 |
68 | ###*
69 | * @return {String}
70 | ###
71 | getComposerLockFilePath: () ->
72 | return path.join(@getCoreSourcePath(), 'composer.lock')
73 |
74 | ###*
75 | * @return {String}
76 | ###
77 | getCoreSourcePath: () ->
78 | return path.join(@folder, @versionSpecification)
79 |
--------------------------------------------------------------------------------
/lib/Widgets/Popover.coffee:
--------------------------------------------------------------------------------
1 | {Disposable} = require 'atom'
2 |
3 | module.exports =
4 |
5 | ##*
6 | # Widget that can be used to display information about a certain context.
7 | ##
8 | class Popover extends Disposable
9 | element: null
10 |
11 | ###*
12 | * Constructor.
13 | ###
14 | constructor: () ->
15 | @$ = require 'jquery'
16 |
17 | @element = document.createElement('div')
18 | @element.className = 'tooltip bottom fade php-integrator-popover'
19 | @element.innerHTML = ""
20 |
21 | document.body.appendChild(@element)
22 |
23 | super @destructor
24 |
25 | ###*
26 | * Destructor.
27 | ###
28 | destructor: () ->
29 | @hide()
30 | document.body.removeChild(@element)
31 |
32 | ###*
33 | * Retrieves the HTML element containing the popover.
34 | *
35 | * @return {HTMLElement}
36 | ###
37 | getElement: () ->
38 | return @element
39 |
40 | ###*
41 | * sets the text to display.
42 | *
43 | * @param {String} text
44 | ###
45 | setText: (text) ->
46 | @$('.tooltip-inner', @element).html(
47 | '' + text.replace(/\n\n/g, '
') + '
'
48 | )
49 |
50 | ###*
51 | * Shows a popover at the specified location with the specified text and fade in time.
52 | *
53 | * @param {Number} x The X coordinate to show the popover at (left).
54 | * @param {Number} y The Y coordinate to show the popover at (top).
55 | * @param {Number} fadeInTime The amount of time to take to fade in the tooltip.
56 | ###
57 | show: (x, y, fadeInTime = 100) ->
58 | @$(@element).css('left', x + 'px')
59 | @$(@element).css('top', y + 'px')
60 |
61 | @$(@element).addClass('in')
62 | @$(@element).css('opacity', 100)
63 | @$(@element).css('display', 'block')
64 |
65 | ###*
66 | * Hides the tooltip, if it is displayed.
67 | ###
68 | hide: () ->
69 | @$(@element).removeClass('in')
70 | @$(@element).css('opacity', 0)
71 | @$(@element).css('display', 'none')
72 |
--------------------------------------------------------------------------------
/lib/Config.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 |
3 | module.exports =
4 |
5 | ##*
6 | # Abstract base class for managing configurations.
7 | ##
8 | class Config
9 | ###*
10 | * Raw configuration object.
11 | ###
12 | data: null
13 |
14 | ###*
15 | * Array of change listeners.
16 | ###
17 | listeners: null
18 |
19 | ###*
20 | * Constructor.
21 | ###
22 | constructor: () ->
23 | @listeners = {}
24 |
25 | @data =
26 | phpCommand : null
27 | packagePath : null
28 | additionalIndexingDelay : 200
29 | memoryLimit : 512
30 | insertNewlinesForUseStatements : false
31 |
32 | # See also http://www.phpdoc.org/docs/latest/index.html .
33 | phpdoc_base_url : {
34 | prefix: 'http://www.phpdoc.org/docs/latest/references/phpdoc/tags/'
35 | suffix: '.html'
36 | }
37 |
38 | # See also https://secure.php.net/urlhowto.php .
39 | php_documentation_base_urls : {
40 | root : 'https://secure.php.net/'
41 | classes : 'https://secure.php.net/class.'
42 | functions : 'https://secure.php.net/function.'
43 | }
44 |
45 | @load()
46 |
47 | ###*
48 | * Loads the configuration.
49 | ###
50 | load: () ->
51 | throw new Error("This method is abstract and must be implemented!")
52 |
53 | ###*
54 | * Registers a listener that is invoked when the specified property is changed.
55 | ###
56 | onDidChange: (name, callback) ->
57 | if name not of @listeners
58 | @listeners[name] = []
59 |
60 | @listeners[name].push(callback)
61 |
62 | ###*
63 | * Retrieves the config setting with the specified name.
64 | *
65 | * @return {mixed}
66 | ###
67 | get: (name) ->
68 | return @data[name]
69 |
70 | ###*
71 | * Retrieves the config setting with the specified name.
72 | *
73 | * @param {String} name
74 | * @param {mixed} value
75 | ###
76 | set: (name, value) ->
77 | @data[name] = value
78 |
79 | if name of @listeners
80 | for listener in @listeners[name]
81 | listener(value, name)
82 |
--------------------------------------------------------------------------------
/lib/Widgets/AttachedPopover.coffee:
--------------------------------------------------------------------------------
1 | Popover = require './Popover'
2 |
3 | module.exports =
4 |
5 | ##*
6 | # Popover that is attached to an HTML element.
7 | #
8 | # NOTE: The reason we do not use Atom's native tooltip is because it is attached to an element, which caused strange
9 | # problems such as tickets #107 and #72. This implementation uses the same CSS classes and transitions but handles the
10 | # displaying manually as we don't want to attach/detach, we only want to temporarily display a popover on mouseover.
11 | ##
12 | class AttachedPopover extends Popover
13 | ###*
14 | * Timeout ID, used for setting a timeout before displaying the popover.
15 | ###
16 | timeoutId: null
17 |
18 | ###*
19 | * The element to attach the popover to.
20 | ###
21 | elementToAttachTo: null
22 |
23 | ###*
24 | * Constructor.
25 | *
26 | * @param {HTMLElement} elementToAttachTo The element to show the popover over.
27 | * @param {Number} delay How long the mouse has to hover over the elment before the popover shows
28 | * up (in miliiseconds).
29 | ###
30 | constructor: (@elementToAttachTo, delay = 500) ->
31 | super()
32 |
33 | ###*
34 | * Destructor.
35 | ###
36 | destructor: () ->
37 | if @timeoutId
38 | clearTimeout(@timeoutId)
39 | @timeoutId = null
40 |
41 | super()
42 |
43 | ###*
44 | * Shows the popover with the specified text.
45 | *
46 | * @param {Number} fadeInTime The amount of time to take to fade in the tooltip.
47 | ###
48 | show: (fadeInTime = 100) ->
49 | coordinates = @elementToAttachTo.getBoundingClientRect();
50 |
51 | centerOffset = ((coordinates.right - coordinates.left) / 2)
52 |
53 | x = coordinates.left - (@$(@getElement()).width() / 2) + centerOffset
54 | y = coordinates.bottom
55 |
56 | x = 0 if x < 0
57 | y = 0 if y < 0
58 |
59 | super(x, y, fadeInTime)
60 |
61 | ###*
62 | * Shows the popover with the specified text after the specified delay (in miliiseconds). Calling this method
63 | * multiple times will cancel previous show requests and restart.
64 | *
65 | * @param {Number} delay The delay before the tooltip shows up (in milliseconds).
66 | * @param {Number} fadeInTime The amount of time to take to fade in the tooltip.
67 | ###
68 | showAfter: (delay, fadeInTime = 100) ->
69 | @timeoutId = setTimeout(() =>
70 | @show(fadeInTime)
71 | , delay)
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # php-integrator/atom-base-legacy-php56
2 | ## Legacy
3 | This is a legacy version that requires PHP >= 5.6. Users that are on PHP 7.1 can and should use [the newer version](https://github.com/php-integrator/atom-base).
4 |
5 | This package only exists to cater towards users that are not in any position to upgrade their host PHP version. As a result, any issues that appear in this package will not be fixed, no new features will be added and no enhancements will be done.
6 |
7 | ## About
8 | This package provides Atom integration for [PHP Integrator](https://gitlab.com/php-integrator/core) and exposes a service that other packages can use to provide additional functionality, such as autocompletion,
9 | code navigation and tooltips. The user can then select his desired combination of functionalities from these other packages:
10 | * **[php-integrator-autocomplete-plus](https://github.com/php-integrator/atom-autocompletion-legacy-php56)** - Provides intelligent PHP autocompletion in combination with autocomplete-plus.
11 | * **[php-integrator-navigation](https://github.com/php-integrator/atom-navigation-legacy-php56)** - Provides code navigation and go to functionality.
12 | * **[php-integrator-tooltips](https://github.com/php-integrator/atom-tooltips-legacy-php56)** - Shows tooltips with documentation.
13 | * **[php-integrator-annotations](https://github.com/php-integrator/atom-annotations-legacy-php56)** - Shows annotations, such as for overridden methods and interface implementations.
14 | * **[php-integrator-call-tips](https://github.com/php-integrator/atom-call-tips-legacy-php56)** - Shows call tips containing parameters in your code. (Complements the autocompletion package.)
15 | * **[php-integrator-refactoring](https://github.com/php-integrator/atom-refactoring-legacy-php56)** - Provides basic refactoring capabilities.
16 | * **[php-integrator-linter](https://github.com/php-integrator/atom-linter-legacy-php56)** - Shows indexing errors and problems with your code.
17 |
18 | The following package also exists, but is currently looking for a new maintainer (see also its README):
19 | * **[php-integrator-symbol-viewer](https://github.com/tocjent/php-integrator-symbol-viewer)** - Provides a side panel listing class symbols with search and filter features.
20 |
21 | Note that the heavy lifting is performed by the [PHP core](https://gitlab.com/php-integrator/core), which is automatically installed as _payload_ for this package and kept up to date automatically.
22 |
23 | The source code was originally based on the php-autocomplete-plus code base, but has significantly diverged from it since then.
24 |
25 | ## What do I need to do to make it work?
26 | See [the website](https://php-integrator.github.io/#what-do-i-need) as well as [the wiki](https://github.com/php-integrator/atom-base/wiki).
27 |
28 | 
29 |
--------------------------------------------------------------------------------
/lib/Widgets/StatusBarManager.coffee:
--------------------------------------------------------------------------------
1 | $ = require 'jquery'
2 |
3 | module.exports =
4 |
5 | ##*
6 | # Manages the items in the status bar.
7 | ##
8 | class StatusBarManager
9 | ###*
10 | * The label to show.
11 | ###
12 | label: null
13 |
14 | ###*
15 | * The label HTML element.
16 | ###
17 | labelElement: null
18 |
19 | ###*
20 | * The progress bar HTML element.
21 | ###
22 | progressBar: null
23 |
24 | ###*
25 | * The root HTML element of the progress bar.
26 | ###
27 | element: null
28 |
29 | ###*
30 | * The tile that is located in the status bar.
31 | ###
32 | tile: null
33 |
34 | ###*
35 | * Initializes the progress bar, setting up its DOM structure.
36 | *
37 | * @param {mixed} statusBarService
38 | ###
39 | initialize: (statusBarService) ->
40 | @labelElement = document.createElement("span")
41 | @labelElement.className = ""
42 | @labelElement.innerHTML = @label
43 |
44 | @progressBar = document.createElement("progress")
45 |
46 | @element = document.createElement("div")
47 | @element.className = "php-integrator-progress-bar"
48 | @element.appendChild(@progressBar)
49 | @element.appendChild(@labelElement)
50 |
51 | atom.tooltips.add(@element, {
52 | title: '''
53 | Your project is being indexed. During this time, functionality such as autocompletion
54 | may not be available. Saved or unsaved changes made to files may also not be indexed
55 | until the next modification is made.
56 | '''
57 | })
58 |
59 | @tile = statusBarService.addRightTile(item: @element, priority: 999999)
60 |
61 | ###*
62 | * Cleans up and removes all elements.
63 | ###
64 | destroy: () ->
65 | @tile.destroy()
66 |
67 | ###*
68 | * Sets the text to show in the label.
69 | *
70 | * @param {String} label
71 | ###
72 | setLabel: (@label) ->
73 | @labelElement.innerHTML = @label
74 | @labelElement.className = ''
75 |
76 | ###*
77 | * Sets the progress value for the progress bar (between 0 and 100).
78 | *
79 | * @param {int|null} progress The progress (between 0 and 100) or null for an indeterminate status.
80 | ###
81 | setProgress: (progress) ->
82 | if progress != null
83 | @progressBar.value = Math.max(Math.min(progress, 100), 0)
84 | @progressBar.max = 100
85 |
86 | else
87 | @progressBar.removeAttribute('value')
88 | @progressBar.removeAttribute('max')
89 |
90 | ###*
91 | * Shows the element.
92 | ###
93 | show: ->
94 | $(@element).show()
95 | $(@progressBar).show()
96 |
97 | ###*
98 | * Hides the element.
99 | ###
100 | hide: ->
101 | $(@element).hide()
102 |
103 | ###*
104 | * Shows only the label.
105 | ###
106 | showLabelOnly: ->
107 | $(@element).show()
108 | $(@progressBar).hide()
109 |
110 | ###*
111 | * Shows the specified message in the status area.
112 | *
113 | * @param {String} label
114 | * @param {String} className
115 | ###
116 | showMessage: (label, className = '') ->
117 | @setLabel(label)
118 | @labelElement.className = className
119 | @showLabelOnly()
120 |
121 | ###*
122 | * Attaches to the specified element or using the specified object.
123 | *
124 | * @param {mixed} object
125 | ###
126 | attach: (object) ->
127 | throw new Error("This method is absract and must be implemented!")
128 |
129 | ###*
130 | * Detaches from the previously attached element.
131 | ###
132 | detach: ->
133 | throw new Error("This method is absract and must be implemented!")
134 |
--------------------------------------------------------------------------------
/lib/ComposerService.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | path = require 'path'
3 | download = require 'download'
4 | child_process = require 'child_process'
5 |
6 | module.exports =
7 |
8 | ##*
9 | # Handles usage of Composer (PHP package manager).
10 | ##
11 | class ComposerService
12 | ###*
13 | * The commit to download from the Composer repository.
14 | *
15 | * Currently set to version 1.2.4.
16 | *
17 | * @see https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md
18 | *
19 | * @var {String}
20 | ###
21 | COMPOSER_COMMIT: 'd0310b646229c3dc57b71bfea2f14ed6c560a5bd'
22 |
23 | ###*
24 | * @var {String}
25 | ###
26 | phpBinary: null
27 |
28 | ###*
29 | * @var {String}
30 | ###
31 | folder: null
32 |
33 | ###*
34 | * @param {String} phpBinary
35 | * @param {String} folder
36 | ###
37 | constructor: (@phpBinary, @folder) ->
38 |
39 | ###*
40 | * @param {Array} parameters
41 | * @param {String|null} workingDirectory
42 | *
43 | * @return {Promise}
44 | ###
45 | run: (parameters, workingDirectory = null) ->
46 | return @installIfNecessary().then () =>
47 | options = {}
48 |
49 | if workingDirectory?
50 | options.cwd = workingDirectory
51 |
52 | return new Promise (resolve, reject) =>
53 | process = child_process.spawn(@phpBinary, [@getPath()].concat(parameters), options)
54 |
55 | process.stdout.on 'data', (data) =>
56 | console.debug('Composer has something to say:', data.toString())
57 |
58 | process.stderr.on 'data', (data) =>
59 | console.warn('Composer has errors to report:', data.toString())
60 |
61 | process.on 'close', (code) =>
62 | console.debug('Composer exited with status code:', code)
63 |
64 | if code != 0
65 | reject()
66 |
67 | else
68 | resolve()
69 |
70 | ###*
71 | * @return {Promise}
72 | ###
73 | installIfNecessary: () ->
74 | if @isInstalled()
75 | return new Promise (resolve, reject) ->
76 | resolve()
77 |
78 | return @install()
79 |
80 | ###*
81 | * @param {Boolean}
82 | ###
83 | isInstalled: () ->
84 | return true if fs.existsSync(@getPath())
85 |
86 | ###*
87 | * @return {Promise}
88 | ###
89 | install: () ->
90 | @download().then () =>
91 | parameters = [
92 | @getInstallerFileFilePath(),
93 | '--install-dir=' + @folder + '',
94 | '--filename=' + @getFileName()
95 | ]
96 |
97 | return new Promise (resolve, reject) =>
98 | process = child_process.spawn(@phpBinary, parameters)
99 |
100 | process.stdout.on 'data', (data) =>
101 | console.debug('Composer installer has something to say:', data.toString())
102 |
103 | process.stderr.on 'data', (data) =>
104 | console.warn('Composer installer has errors to report:', data.toString())
105 |
106 | process.on 'close', (code) =>
107 | console.debug('Composer installer exited with status code:', code)
108 |
109 | if code != 0
110 | reject()
111 |
112 | else
113 | resolve()
114 |
115 | ###*
116 | * @return {Promise}
117 | ###
118 | download: () ->
119 | return download(
120 | 'https://raw.githubusercontent.com/composer/getcomposer.org/' + @COMPOSER_COMMIT + '/web/installer',
121 | @getInstallerFilePath()
122 | )
123 |
124 | ###*
125 | * @return {String}
126 | ###
127 | getInstallerFilePath: () ->
128 | return @folder
129 |
130 | ###*
131 | * @return {String}
132 | ###
133 | getInstallerFileFileName: () ->
134 | return 'installer'
135 |
136 | ###*
137 | * @return {String}
138 | ###
139 | getInstallerFileFilePath: () ->
140 | return path.join(@getInstallerFilePath(), @getInstallerFileFileName())
141 |
142 | ###*
143 | * @return {String}
144 | ###
145 | getPath: () ->
146 | return path.join(@folder, @getFileName())
147 |
148 | ###*
149 | * @return {String}
150 | ###
151 | getFileName: () ->
152 | return 'composer.phar'
153 |
--------------------------------------------------------------------------------
/lib/IndexingMediator.coffee:
--------------------------------------------------------------------------------
1 | Popover = require './Widgets/Popover'
2 | AttachedPopover = require './Widgets/AttachedPopover'
3 |
4 | module.exports =
5 |
6 | ##*
7 | # A mediator that mediates between classes that need to do indexing and keep updated about the results.
8 | ##
9 | class IndexingMediator
10 | ###*
11 | * The proxy to use to contact the PHP side.
12 | ###
13 | proxy: null
14 |
15 | ###*
16 | * The emitter to use to emit indexing events.
17 | ###
18 | indexingEventEmitter: null
19 |
20 | ###*
21 | * Constructor.
22 | *
23 | * @param {CachingProxy} proxy
24 | * @param {Emitter} indexingEventEmitter
25 | ###
26 | constructor: (@proxy, @indexingEventEmitter) ->
27 |
28 | ###*
29 | * Refreshes the specified file or folder. This method is asynchronous and will return immediately.
30 | *
31 | * @param {String|Array} path The full path to the file or folder to refresh. Alternatively,
32 | * this can be a list of items to index at the same time.
33 | * @param {String|null} source The source code of the file to index. May be null if a directory is
34 | * passed instead.
35 | * @param {Array} excludedPaths A list of paths to exclude from indexing.
36 | * @param {Array} fileExtensionsToIndex A list of file extensions (without leading dot) to index.
37 | *
38 | * @return {Promise}
39 | ###
40 | reindex: (path, source, excludedPaths, fileExtensionsToIndex) ->
41 | return new Promise (resolve, reject) =>
42 | @indexingEventEmitter.emit('php-integrator-base:indexing-started', {
43 | path : path
44 | })
45 |
46 | successHandler = (output) =>
47 | @indexingEventEmitter.emit('php-integrator-base:indexing-finished', {
48 | output : output
49 | path : path
50 | })
51 |
52 | resolve(output)
53 |
54 | failureHandler = (error) =>
55 | @indexingEventEmitter.emit('php-integrator-base:indexing-failed', {
56 | error : error
57 | path : path
58 | })
59 |
60 | reject(error)
61 |
62 | progressStreamCallback = (progress) =>
63 | progress = parseFloat(progress)
64 |
65 | if not isNaN(progress)
66 | @indexingEventEmitter.emit('php-integrator-base:indexing-progress', {
67 | path : path
68 | percentage : progress
69 | })
70 |
71 | return @proxy.reindex(
72 | path,
73 | source,
74 | progressStreamCallback,
75 | excludedPaths,
76 | fileExtensionsToIndex
77 | ).then(successHandler, failureHandler)
78 |
79 | ###*
80 | * Initializes the project.
81 | *
82 | * @return {Promise}
83 | ###
84 | initialize: () ->
85 | return @proxy.initialize()
86 |
87 | ###*
88 | * Vacuums the project.
89 | *
90 | * @return {Promise}
91 | ###
92 | vacuum: () ->
93 | return @proxy.vacuum()
94 |
95 | ###*
96 | * Attaches a callback to indexing started event. The returned disposable can be used to detach your event handler.
97 | *
98 | * @param {Callback} callback A callback that takes one parameter which contains a 'path' property.
99 | *
100 | * @return {Disposable}
101 | ###
102 | onDidStartIndexing: (callback) ->
103 | @indexingEventEmitter.on('php-integrator-base:indexing-started', callback)
104 |
105 | ###*
106 | * Attaches a callback to indexing progress event. The returned disposable can be used to detach your event handler.
107 | *
108 | * @param {Callback} callback A callback that takes one parameter which contains a 'path' and a 'percentage' property.
109 | *
110 | * @return {Disposable}
111 | ###
112 | onDidIndexingProgress: (callback) ->
113 | @indexingEventEmitter.on('php-integrator-base:indexing-progress', callback)
114 |
115 | ###*
116 | * Attaches a callback to indexing finished event. The returned disposable can be used to detach your event handler.
117 | *
118 | * @param {Callback} callback A callback that takes one parameter which contains an 'output' and a 'path' property.
119 | *
120 | * @return {Disposable}
121 | ###
122 | onDidFinishIndexing: (callback) ->
123 | @indexingEventEmitter.on('php-integrator-base:indexing-finished', callback)
124 |
125 | ###*
126 | * Attaches a callback to indexing failed event. The returned disposable can be used to detach your event handler.
127 | *
128 | * @param {Callback} callback A callback that takes one parameter which contains an 'error' and a 'path' property.
129 | *
130 | * @return {Disposable}
131 | ###
132 | onDidFailIndexing: (callback) ->
133 | @indexingEventEmitter.on('php-integrator-base:indexing-failed', callback)
134 |
--------------------------------------------------------------------------------
/lib/CachingProxy.coffee:
--------------------------------------------------------------------------------
1 | md5 = require 'md5'
2 |
3 | Proxy = require './Proxy'
4 |
5 | module.exports =
6 |
7 | ##*
8 | # Proxy that applies caching on top of its functionality.
9 | ##
10 | class CachingProxy extends Proxy
11 | ###*
12 | * @var {Object}
13 | ###
14 | cache: null
15 |
16 | ###*
17 | * @var {Object}
18 | ###
19 | pendingPromises: null
20 |
21 | ###*
22 | * @inherited
23 | ###
24 | constructor: (@config) ->
25 | super(@config)
26 |
27 | @cache = {}
28 | @pendingPromises = {}
29 |
30 | ###*
31 | * Clears the cache.
32 | ###
33 | clearCache: () ->
34 | @cache = {}
35 | @pendingPromises = {}
36 |
37 | ###*
38 | * Internal convenience method that wraps a call to a parent method.
39 | *
40 | * @param {String} cacheKey
41 | * @param {String} parentMethodName
42 | * @param {Array} parameters
43 | *
44 | * @return {Promise|Object}
45 | ###
46 | wrapCachedRequestToParent: (cacheKey, parentMethodName, parameters) ->
47 | if cacheKey of @cache
48 | return new Promise (resolve, reject) =>
49 | resolve(@cache[cacheKey])
50 |
51 | else if cacheKey of @pendingPromises
52 | # If a query with the same parameters (promise) is still running, don't start another one but just await
53 | # the results of the existing promise.
54 | return @pendingPromises[cacheKey]
55 |
56 | else
57 | successHandler = (output) =>
58 | delete @pendingPromises[cacheKey]
59 |
60 | @cache[cacheKey] = output
61 |
62 | return output
63 |
64 | @pendingPromises[cacheKey] = CachingProxy.__super__[parentMethodName].apply(this, parameters).then(successHandler)
65 |
66 | return @pendingPromises[cacheKey]
67 |
68 | ###*
69 | * @inherited
70 | ###
71 | getClassList: () ->
72 | return @wrapCachedRequestToParent("getClassList", 'getClassList', arguments)
73 |
74 | ###*
75 | * @inherited
76 | ###
77 | getClassListForFile: (file) ->
78 | return @wrapCachedRequestToParent("getClassListForFile-#{file}", 'getClassListForFile', arguments)
79 |
80 | ###*
81 | * @inherited
82 | ###
83 | getNamespaceList: () ->
84 | return @wrapCachedRequestToParent("getNamespaceList", 'getNamespaceList', arguments)
85 |
86 | ###*
87 | * @inherited
88 | ###
89 | getNamespaceListForFile: (file) ->
90 | return @wrapCachedRequestToParent("getNamespaceListForFile-#{file}", 'getNamespaceListForFile', arguments)
91 |
92 | ###*
93 | * @inherited
94 | ###
95 | getGlobalConstants: () ->
96 | return @wrapCachedRequestToParent("getGlobalConstants", 'getGlobalConstants', arguments)
97 |
98 | ###*
99 | * @inherited
100 | ###
101 | getGlobalFunctions: () ->
102 | return @wrapCachedRequestToParent("getGlobalFunctions", 'getGlobalFunctions', arguments)
103 |
104 | ###*
105 | * @inherited
106 | ###
107 | getClassInfo: (className) ->
108 | return @wrapCachedRequestToParent("getClassInfo-#{className}", 'getClassInfo', arguments)
109 |
110 | ###*
111 | * @inherited
112 | ###
113 | resolveType: (file, line, type, kind = 'classlike') ->
114 | return @wrapCachedRequestToParent("resolveType-#{file}-#{line}-#{type}-#{kind}", 'resolveType', arguments)
115 |
116 | ###*
117 | * @inherited
118 | ###
119 | localizeType: (file, line, type, kind = 'classlike') ->
120 | return @wrapCachedRequestToParent("localizeType-#{file}-#{line}-#{type}-#{kind}", 'localizeType', arguments)
121 |
122 | ###*
123 | * @inherited
124 | ###
125 | semanticLint: (file, source, options) ->
126 | # md5 may sound expensive, but it's not as expensive as spawning an extra process that parses PHP code.
127 | sourceKey = if source? then md5(source) else null
128 |
129 | optionsKey = JSON.stringify(options)
130 |
131 | return @wrapCachedRequestToParent("semanticLint-#{file}-#{sourceKey}-#{optionsKey}", 'semanticLint', arguments)
132 |
133 | ###*
134 | * @inherited
135 | ###
136 | getAvailableVariables: (file, source, offset) ->
137 | sourceKey = if source? then md5(source) else null
138 |
139 | return @wrapCachedRequestToParent("getAvailableVariables-#{file}-#{sourceKey}-#{offset}", 'getAvailableVariables', arguments)
140 |
141 | ###*
142 | * @inherited
143 | ###
144 | deduceTypes: (expression, file, source, offset, ignoreLastElement) ->
145 | sourceKey = if source? then md5(source) else null
146 |
147 | return @wrapCachedRequestToParent("deduceTypes-#{expression}-#{file}-#{sourceKey}-#{offset}-#{ignoreLastElement}", 'deduceTypes', arguments)
148 |
149 | ###*
150 | * @inherited
151 | ###
152 | getInvocationInfo: (file, source, offset) ->
153 | sourceKey = if source? then md5(source) else null
154 |
155 | return @wrapCachedRequestToParent("getInvocationInfo-#{file}-#{sourceKey}-#{offset}", 'getInvocationInfo', arguments)
156 |
157 | ###*
158 | * @inherited
159 | ###
160 | initialize: () ->
161 | return super().then (output) =>
162 | @clearCache()
163 |
164 | return output
165 |
166 | ###*
167 | * @inherited
168 | ###
169 | vacuum: () ->
170 | return super().then (output) =>
171 | @clearCache()
172 |
173 | return output
174 |
175 | ###*
176 | * @inherited
177 | ###
178 | test: () ->
179 | return super()
180 |
181 | ###*
182 | * @inherited
183 | ###
184 | reindex: (path, source, progressStreamCallback, excludedPaths, fileExtensionsToIndex) ->
185 | return super(path, source, progressStreamCallback, excludedPaths, fileExtensionsToIndex).then (output) =>
186 | @clearCache()
187 |
188 | return output
189 |
--------------------------------------------------------------------------------
/lib/UseStatementHelper.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 |
3 | ##*
4 | # Contains convenience methods for dealing with use statements.
5 | ##
6 | class UseStatementHelper
7 | ###*
8 | * Regular expression that will search for a structure (class, interface, trait, ...).
9 | *
10 | * @var {RegExp}
11 | ###
12 | structureStartRegex : /(?:abstract class|class|trait|interface)\s+(\w+)/
13 |
14 | ###*
15 | * Regular expression that will search for a use statement.
16 | *
17 | * @var {RegExp}
18 | ###
19 | useStatementRegex : /(?:use)(?:[^\w\\])([\w\\]+)(?![\w\\])(?:(?:[ ]+as[ ]+)(\w+))?(?:;)/
20 |
21 | ###*
22 | * Whether to allow adding additional newlines to attempt to group use statements.
23 | *
24 | * @var {Boolean}
25 | ###
26 | allowAdditionalNewlines : false
27 |
28 | ###*
29 | * @param {Boolean} allowAdditionalNewlines
30 | ###
31 | constructor: (@allowAdditionalNewlines) ->
32 |
33 | ###*
34 | * @param {Boolean} allowAdditionalNewlines
35 | ###
36 | setAllowAdditionalNewlines: (@allowAdditionalNewlines) ->
37 |
38 | ###*
39 | * Add the use for the given class if not already added.
40 | *
41 | * @param {TextEditor} editor Atom text editor.
42 | * @param {String} className Name of the class to add.
43 | *
44 | * @return {Number} The amount of lines added (including newlines), so you can reliably and easily offset your rows.
45 | # This could be zero if a use statement was already present.
46 | ###
47 | addUseClass: (editor, className) ->
48 | bestUseRow = 0
49 | placeBelow = true
50 | doNewLine = true
51 | lineCount = editor.getLineCount()
52 | previousMatchThatSharedNamespacePrefixRow = null
53 |
54 | # First see if the use statement is already present. The next loop stops early (and can't do this).
55 | for i in [0 .. lineCount - 1]
56 | line = editor.lineTextForBufferRow(i).trim()
57 |
58 | continue if line.length == 0
59 |
60 | scopeDescriptor = editor.scopeDescriptorForBufferPosition([i, line.length]).getScopeChain()
61 |
62 | if scopeDescriptor.indexOf('.comment') >= 0
63 | continue
64 |
65 | break if line.match(@structureStartRegex)
66 |
67 | if (matches = @useStatementRegex.exec(line))
68 | if matches[1] == className or (matches[1][0] == '\\' and matches[1].substr(1) == className)
69 | return 0
70 |
71 | # Determine an appropriate location to place the use statement.
72 | for i in [0 .. lineCount - 1]
73 | line = editor.lineTextForBufferRow(i).trim()
74 |
75 | continue if line.length == 0
76 |
77 | scopeDescriptor = editor.scopeDescriptorForBufferPosition([i, line.length]).getScopeChain()
78 |
79 | if scopeDescriptor.indexOf('.comment') >= 0
80 | continue
81 |
82 | break if line.match(@structureStartRegex)
83 |
84 | if line.indexOf('namespace ') >= 0
85 | bestUseRow = i
86 |
87 | if (matches = @useStatementRegex.exec(line))
88 | bestUseRow = i
89 |
90 | placeBelow = true
91 | shareCommonNamespacePrefix = @doShareCommonNamespacePrefix(className, matches[1])
92 |
93 | doNewLine = not shareCommonNamespacePrefix
94 |
95 | if @scoreClassName(className, matches[1]) <= 0
96 | placeBelow = false
97 |
98 | # Normally we keep going until the sorting indicates we should stop, and then place the use
99 | # statement above the 'incorrect' match, but if the previous use statement was a use statement
100 | # that has the same namespace, we want to ensure we stick close to it instead of creating additional
101 | # newlines (which the item from the same namespace already placed).
102 | if previousMatchThatSharedNamespacePrefixRow?
103 | placeBelow = true
104 | doNewLine = false
105 | bestUseRow = previousMatchThatSharedNamespacePrefixRow
106 |
107 | break
108 |
109 | previousMatchThatSharedNamespacePrefixRow = if shareCommonNamespacePrefix then i else null
110 |
111 | # Insert the use statement itself.
112 | lineEnding = editor.getBuffer().lineEndingForRow(0)
113 |
114 | if not @allowAdditionalNewlines
115 | doNewLine = false
116 |
117 | if not lineEnding
118 | lineEnding = "\n"
119 |
120 | textToInsert = ''
121 |
122 | if doNewLine and placeBelow
123 | textToInsert += lineEnding
124 |
125 | textToInsert += "use #{className};" + lineEnding
126 |
127 | if doNewLine and not placeBelow
128 | textToInsert += lineEnding
129 |
130 | lineToInsertAt = bestUseRow + (if placeBelow then 1 else 0)
131 | editor.setTextInBufferRange([[lineToInsertAt, 0], [lineToInsertAt, 0]], textToInsert)
132 |
133 | return (1 + (if doNewLine then 1 else 0))
134 |
135 | ###*
136 | * Returns a boolean indicating if the specified class names share a common namespace prefix.
137 | *
138 | * @param {String} firstClassName
139 | * @param {String} secondClassName
140 | *
141 | * @return {Boolean}
142 | ###
143 | doShareCommonNamespacePrefix: (firstClassName, secondClassName) ->
144 | firstClassNameParts = firstClassName.split('\\')
145 | secondClassNameParts = secondClassName.split('\\')
146 |
147 | firstClassNameParts.pop()
148 | secondClassNameParts.pop()
149 |
150 | return if firstClassNameParts.join('\\') == secondClassNameParts.join('\\') then true else false
151 |
152 | ###*
153 | * Scores the first class name against the second, indicating how much they 'match' each other. This can be used
154 | * to e.g. find an appropriate location to place a class in an existing list of classes.
155 | *
156 | * @param {String} firstClassName
157 | * @param {String} secondClassName
158 | *
159 | * @return {Number} A floating point number that represents the score.
160 | ###
161 | scoreClassName: (firstClassName, secondClassName) ->
162 | maxLength = 0
163 | totalScore = 0
164 |
165 | firstClassNameParts = firstClassName.split('\\')
166 | secondClassNameParts = secondClassName.split('\\')
167 |
168 | maxLength = Math.min(firstClassNameParts.length, secondClassNameParts.length)
169 |
170 | if maxLength >= 2
171 | for i in [0 .. maxLength - 2]
172 | if firstClassNameParts[i] != secondClassNameParts[i]
173 | return (firstClassNameParts[i].localeCompare(secondClassNameParts[i]))
174 |
175 | # At this point, both FQSEN's share a common namespace, e.g. A\B and A\B\C\D, or XMLElement and XMLDocument.
176 | # The one with the most namespace parts ends up last.
177 | if firstClassNameParts.length > secondClassNameParts.length
178 | return 1
179 |
180 | else if firstClassNameParts.length < secondClassNameParts.length
181 | return -1
182 |
183 | # Both items have share the same namespace, sort from shortest to longest last word (class, interface, ...).
184 | return firstClassName.length > secondClassName.length ? 1 : -1
185 |
186 | ###*
187 | * Sorts the use statements in the specified file according to the same algorithm used by 'addUseClass'.
188 | *
189 | * @param {TextEditor} editor
190 | ###
191 | sortUseStatements: (editor) ->
192 | endLine = null
193 | startLine = null
194 | useStatements = []
195 |
196 | for i in [0 .. editor.getLineCount()]
197 | lineText = editor.lineTextForBufferRow(i)
198 |
199 | endLine = i
200 |
201 | if not lineText or lineText.trim() == ''
202 | continue
203 |
204 | else if (matches = @useStatementRegex.exec(lineText))
205 | if not startLine
206 | startLine = i
207 |
208 | text = matches[1]
209 |
210 | if matches[2]?
211 | text += ' as ' + matches[2]
212 |
213 | useStatements.push(text);
214 |
215 | # We still do the regex check here to prevent continuing when there are no use statements at all.
216 | else if startLine or @structureStartRegex.test(lineText)
217 | break
218 |
219 | return if useStatements.length == 0
220 |
221 | editor.transact () =>
222 | editor.setTextInBufferRange([[startLine, 0], [endLine, 0]], '')
223 |
224 | for useStatement in useStatements
225 | # The leading slash is unnecessary, not recommended, and messes up sorting, take it out.
226 | if useStatement[0] == '\\'
227 | useStatement = useStatement.substr(1)
228 |
229 | @addUseClass(editor, useStatement, @allowAdditionalNewlines)
230 |
--------------------------------------------------------------------------------
/lib/ProjectManager.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 |
3 | ##*
4 | # Handles project management
5 | ##
6 | class ProjectManager
7 | ###*
8 | * @var {Object}
9 | ###
10 | proxy: null
11 |
12 | ###*
13 | * @var {Object}
14 | ###
15 | indexingMediator: null
16 |
17 | ###*
18 | * The service instance from the project-manager package.
19 | *
20 | * @var {Object|null}
21 | ###
22 | activeProject: null
23 |
24 | ###*
25 | * Whether project indexing is currently happening.
26 | *
27 | * @var {bool}
28 | ###
29 | isProjectIndexingFlag: false
30 |
31 | ###*
32 | * Keeps track of files that are being indexed.
33 | *
34 | * @var {Object}
35 | ###
36 | indexMap: null
37 |
38 | ###*
39 | * Default settings for projects.
40 | *
41 | * Note that this object will be shared across instances!
42 | *
43 | * @var {Object}
44 | ###
45 | defaultProjectSettings:
46 | enabled: true
47 | php_integrator:
48 | enabled: true
49 | phpVersion: 5.6
50 | excludedPaths: []
51 | fileExtensions: ['php']
52 |
53 | ###*
54 | * @param {Object} proxy
55 | * @param {Object} indexingMediator
56 | ###
57 | constructor: (@proxy, @indexingMediator) ->
58 | @indexMap = {}
59 |
60 | ###*
61 | * @return {Object|null}
62 | ###
63 | getActiveProject: () ->
64 | return @activeProject
65 |
66 | ###*
67 | * @return {bool}
68 | ###
69 | hasActiveProject: () ->
70 | if @getActiveProject()?
71 | return true
72 |
73 | return false
74 |
75 | ###*
76 | * @return {bool}
77 | ###
78 | isProjectIndexing: () ->
79 | return @isProjectIndexingFlag
80 |
81 | ###*
82 | * Sets up the specified project for usage with this package.
83 | *
84 | * Default settings will be stored inside the package, if they aren't already present. If they already exist, they
85 | * will not be overwritten.
86 | *
87 | * Note that this method does not explicitly request persisting settings from the external project manager service.
88 | *
89 | * @param {Object} project
90 | *
91 | * @return {Object} The new settings of the project (that could be persisted).
92 | ###
93 | setUpProject: (project) ->
94 | projectPhpSettings = if project.getProps().php? then project.getProps().php else {}
95 |
96 | if projectPhpSettings.php_integrator?
97 | throw new Error('''
98 | The currently active project was already initialized. To prevent existing settings from getting lost,
99 | the request has been aborted.
100 | ''')
101 |
102 | if not projectPhpSettings.enabled
103 | projectPhpSettings.enabled = true
104 |
105 | if not projectPhpSettings.php_integrator?
106 | projectPhpSettings.php_integrator = @defaultProjectSettings.php_integrator
107 |
108 | existingProps = project.getProps()
109 | existingProps.php = projectPhpSettings
110 |
111 | return existingProps
112 |
113 | ###*
114 | * @param {Object} project
115 | ###
116 | load: (project) ->
117 | @activeProject = null
118 |
119 | return if project.getProps().php?.enabled != true
120 |
121 | projectSettings = @getProjectSettings(project)
122 |
123 | return if projectSettings?.enabled != true
124 |
125 | @validateProject(project)
126 |
127 | @activeProject = project
128 |
129 | @proxy.setIndexDatabaseName(@getIndexDatabaseName(project))
130 |
131 | successHandler = (repository) =>
132 | return if not repository?
133 | return if not repository.async?
134 |
135 | # Will trigger on things such as git checkout.
136 | repository.async.onDidChangeStatuses () =>
137 | @attemptIndex(project)
138 |
139 | failureHandler = () =>
140 | return
141 |
142 | {Directory} = require 'atom'
143 |
144 | for projectDirectory in @getProjectPaths(project)
145 | projectDirectoryObject = new Directory(projectDirectory)
146 |
147 | atom.project.repositoryForDirectory(projectDirectoryObject).then(successHandler, failureHandler)
148 |
149 | ###*
150 | * @param {Object}
151 | *
152 | * @return {String}
153 | ###
154 | getIndexDatabaseName: (project) ->
155 | return project.getProps().title
156 |
157 | ###*
158 | * Validates a project by validating its settings.
159 | *
160 | * Throws an Error if something is not right with the project.
161 | *
162 | * @param {Object} project
163 | ###
164 | validateProject: (project) ->
165 | projectSettings = @getProjectSettings(project)
166 |
167 | if not projectSettings?
168 | throw new Error(
169 | 'No project settings were found, a php.php_integrator node must be present in your project settings!'
170 | )
171 |
172 | phpVersion = projectSettings.phpVersion
173 |
174 | if isNaN(parseFloat(phpVersion)) or not isFinite(phpVersion)
175 | throw new Error('''
176 | The PHP version that is set in your project settings is not valid! It must be a number, for example: 5.6
177 | ''')
178 |
179 | ###*
180 | * Retrieves a list of file extensions to include in indexing.
181 | *
182 | * @param {Object} project
183 | *
184 | * @return {Array}
185 | ###
186 | getFileExtensionsToIndex: (project) ->
187 | projectPaths = @getProjectPaths(project)
188 | projectSettings = @getProjectSettings(project)
189 |
190 | fileExtensions = projectSettings?.fileExtensions
191 |
192 | if not fileExtensions?
193 | fileExtensions = []
194 |
195 | return fileExtensions
196 |
197 | ###*
198 | * Retrieves a list of absolute paths to exclude from indexing.
199 | *
200 | * @param {Object} project
201 | *
202 | * @return {Array}
203 | ###
204 | getAbsoluteExcludedPaths: (project) ->
205 | projectPaths = @getProjectPaths(project)
206 | projectSettings = @getProjectSettings(project)
207 |
208 | excludedPaths = projectSettings?.excludedPaths
209 |
210 | if not excludedPaths?
211 | excludedPaths = []
212 |
213 | path = require 'path'
214 |
215 | absoluteExcludedPaths = []
216 |
217 | for excludedPath in excludedPaths
218 | if path.isAbsolute(excludedPath)
219 | absoluteExcludedPaths.push(excludedPath)
220 |
221 | else
222 | matches = excludedPath.match(/^\{(\d+)\}(\/.*)$/)
223 |
224 | if matches?
225 | index = matches[1]
226 |
227 | # Relative paths starting with {n} are relative to the project path at index {n}, e.g. "{0}/test".
228 | if index > projectPaths.length
229 | throw new Error("Requested project path index " + index + ", but the project does not have that many paths!")
230 |
231 | absoluteExcludedPaths.push(projectPaths[index] + matches[2])
232 |
233 | else
234 | absoluteExcludedPaths.push(path.normalize(excludedPath))
235 |
236 | return absoluteExcludedPaths
237 |
238 | ###*
239 | * Indexes the project asynchronously.
240 | *
241 | * @param {Object} project
242 | *
243 | * @return {Promise}
244 | ###
245 | performIndex: (project) ->
246 | successHandler = () =>
247 | return @indexingMediator.reindex(
248 | @getProjectPaths(project),
249 | null,
250 | @getAbsoluteExcludedPaths(project),
251 | @getFileExtensionsToIndex(project)
252 | )
253 |
254 | return @indexingMediator.vacuum().then(successHandler)
255 |
256 | ###*
257 | * Performs a project index, but only if one is not currently already happening.
258 | *
259 | * @param {Object} project
260 | *
261 | * @return {Promise|null}
262 | ###
263 | attemptIndex: (project) ->
264 | return null if @isProjectIndexing()
265 |
266 | @isProjectIndexingFlag = true
267 |
268 | handler = () =>
269 | @isProjectIndexingFlag = false
270 |
271 | successHandler = handler
272 | failureHandler = handler
273 |
274 | return @performIndex(project).then(successHandler, failureHandler)
275 |
276 | ###*
277 | * Indexes the current project, but only if one is not currently already happening.
278 | *
279 | * @return {Promise}
280 | ###
281 | attemptCurrentProjectIndex: () ->
282 | return @attemptIndex(@getActiveProject())
283 |
284 | ###*
285 | * Initializes the project.
286 | *
287 | * @return {Promise|null}
288 | ###
289 | initializeCurrentProject: () ->
290 | return @indexingMediator.initialize()
291 |
292 | ###*
293 | * Vacuums the project.
294 | *
295 | * @return {Promise|null}
296 | ###
297 | vacuumCurrentProject: () ->
298 | return @indexingMediator.vacuum()
299 |
300 | ###*
301 | * Indexes a file asynchronously.
302 | *
303 | * @param {Object} project
304 | * @param {String} fileName The file to index.
305 | * @param {String|null} source The source code of the file to index.
306 | *
307 | * @return {Promise}
308 | ###
309 | performFileIndex: (project, fileName, source = null) ->
310 | return @indexingMediator.reindex(
311 | fileName,
312 | source,
313 | @getAbsoluteExcludedPaths(project),
314 | @getFileExtensionsToIndex(project)
315 | )
316 |
317 | ###*
318 | * Performs a file index, but only if the file is not currently already being indexed (otherwise silently returns).
319 | *
320 | * @param {Object} project
321 | * @param {String} fileName The file to index.
322 | * @param {String|null} source The source code of the file to index.
323 | *
324 | * @return {Promise|null}
325 | ###
326 | attemptFileIndex: (project, fileName, source = null) ->
327 | return null if @isProjectIndexing()
328 |
329 | if fileName not of @indexMap
330 | @indexMap[fileName] = {
331 | isBeingIndexed : true
332 | nextIndexSource : null
333 | }
334 |
335 | else if @indexMap[fileName].isBeingIndexed
336 | # This file is already being indexed, so keep track of the most recent changes so we can index any changes
337 | # after the current indexing process finishes.
338 | @indexMap[fileName].nextIndexSource = source
339 | return null
340 |
341 | @indexMap[fileName].isBeingIndexed = true
342 |
343 | handler = () =>
344 | @indexMap[fileName].isBeingIndexed = false
345 |
346 | if @indexMap[fileName].nextIndexSource?
347 | nextIndexSource = @indexMap[fileName].nextIndexSource
348 |
349 | @indexMap[fileName].nextIndexSource = null
350 |
351 | @attemptFileIndex(project, fileName, nextIndexSource)
352 |
353 | successHandler = handler
354 | failureHandler = handler
355 |
356 | return @performFileIndex(project, fileName, source).then(successHandler, failureHandler)
357 |
358 | ###*
359 | * Indexes the current project asynchronously.
360 | *
361 | * @param {String} fileName The file to index.
362 | * @param {String|null} source The source code of the file to index.
363 | *
364 | * @return {Promise}
365 | ###
366 | attemptCurrentProjectFileIndex: (fileName, source = null) ->
367 | return @attemptFileIndex(@getActiveProject(), fileName, source)
368 |
369 | ###*
370 | * @return {Object|null}
371 | ###
372 | getProjectSettings: (project) ->
373 | if project.getProps().php?.php_integrator?
374 | return project.getProps().php.php_integrator
375 |
376 | return null
377 |
378 | ###*
379 | * @return {Object|null}
380 | ###
381 | getCurrentProjectSettings: () ->
382 | return @getProjectSettings(@getActiveProject())
383 |
384 | ###*
385 | * @return {Array}
386 | ###
387 | getProjectPaths: (project) ->
388 | return project.getProps().paths
389 |
390 | ###*
391 | * Indicates if the specified file is part of the project.
392 | *
393 | * @param {Object} project
394 | * @param {String} fileName
395 | *
396 | * @return {bool}
397 | ###
398 | isFilePartOfProject: (project, fileName) ->
399 | {Directory} = require 'atom'
400 |
401 | for projectDirectory in @getProjectPaths(project)
402 | projectDirectoryObject = new Directory(projectDirectory)
403 |
404 | if projectDirectoryObject.contains(fileName)
405 | return true
406 |
407 | return false
408 |
409 | ###*
410 | * Indicates if the specified file is part of the current project.
411 | *
412 | * @param {String} fileName
413 | *
414 | * @return {bool}
415 | ###
416 | isFilePartOfCurrentProject: (fileName) ->
417 | return @isFilePartOfProject(@getActiveProject(), fileName)
418 |
--------------------------------------------------------------------------------
/lib/Service.coffee:
--------------------------------------------------------------------------------
1 | Popover = require './Widgets/Popover'
2 | AttachedPopover = require './Widgets/AttachedPopover'
3 |
4 | module.exports =
5 |
6 | ##*
7 | # The service that is exposed to other packages.
8 | ##
9 | class Service
10 | ###*
11 | * @var {Object}
12 | ###
13 | config: null
14 |
15 | ###*
16 | * @var {Object}
17 | ###
18 | proxy: null
19 |
20 | ###*
21 | * @var {Object}
22 | ###
23 | projectManager: null
24 |
25 | ###*
26 | * @var {Object}
27 | ###
28 | indexingMediator: null
29 |
30 | ###*
31 | * @var {Object}
32 | ###
33 | useStatementHelper: null
34 |
35 | ###*
36 | * Constructor.
37 | *
38 | * @param {AtomConfig} config
39 | * @param {CachingProxy} proxy
40 | * @param {Object} projectManager
41 | * @param {Object} indexingMediator
42 | * @param {Object} useStatementHelper
43 | ###
44 | constructor: (@config, @proxy, @projectManager, @indexingMediator, @useStatementHelper) ->
45 |
46 | ###*
47 | * Retrieves the use statement helper, which contains utility methods for dealing with use statements.
48 | *
49 | * @return {Object}
50 | ###
51 | getUseStatementHelper: () ->
52 | return @useStatementHelper
53 |
54 | ###*
55 | * Retrieves the settings (that are specific to this package) for the currently active project. If there is no
56 | * active project or the project does not have any settings, null is returned.
57 | *
58 | * @return {Object|null}
59 | ###
60 | getCurrentProjectSettings: () ->
61 | return @projectManager.getCurrentProjectSettings()
62 |
63 | ###*
64 | * Clears the autocompletion cache. Most fetching operations such as fetching constants, autocompletion, fetching
65 | * members, etc. are cached when they are first retrieved. This clears the cache, forcing them to be retrieved
66 | # again. Clearing the cache is automatically performed, so this method is usually unnecessary.
67 | ###
68 | clearCache: () ->
69 | @proxy.clearCache()
70 |
71 | ###*
72 | * Retrieves a list of available classes.
73 | *
74 | * @return {Promise}
75 | ###
76 | getClassList: () ->
77 | return @proxy.getClassList()
78 |
79 | ###*
80 | * Retrieves a list of available classes in the specified file.
81 | *
82 | * @param {String} file
83 | *
84 | * @return {Promise}
85 | ###
86 | getClassListForFile: (file) ->
87 | return @proxy.getClassListForFile(file)
88 |
89 | ###*
90 | * Retrieves a list of namespaces.
91 | *
92 | * @return {Promise}
93 | ###
94 | getNamespaceList: () ->
95 | return @proxy.getNamespaceList()
96 |
97 | ###*
98 | * Retrieves a list of namespaces in the specified file.
99 | *
100 | * @param {String} file
101 | *
102 | * @return {Promise}
103 | ###
104 | getNamespaceListForFile: (file) ->
105 | return @proxy.getNamespaceListForFile(file)
106 |
107 | ###*
108 | * Retrieves a list of available global constants.
109 | *
110 | * @return {Promise}
111 | ###
112 | getGlobalConstants: () ->
113 | return @proxy.getGlobalConstants()
114 |
115 | ###*
116 | * Retrieves a list of available global functions.
117 | *
118 | * @return {Promise}
119 | ###
120 | getGlobalFunctions: () ->
121 | return @proxy.getGlobalFunctions()
122 |
123 | ###*
124 | * Retrieves a list of available members of the class (or interface, trait, ...) with the specified name.
125 | *
126 | * @param {String} className
127 | *
128 | * @return {Promise}
129 | ###
130 | getClassInfo: (className) ->
131 | return @proxy.getClassInfo(className)
132 |
133 | ###*
134 | * Resolves a local type in the specified file, based on use statements and the namespace.
135 | *
136 | * @param {String} file
137 | * @param {Number} line The line the type is located at. The first line is 1, not 0.
138 | * @param {String} type
139 | * @param {String} kind The kind of element. Either 'classlike', 'constant' or 'function'.
140 | *
141 | * @return {Promise}
142 | ###
143 | resolveType: (file, line, type, kind) ->
144 | return @proxy.resolveType(file, line, type, kind)
145 |
146 | ###*
147 | * Localizes a type to the specified file, making it relative to local use statements, if possible. If not possible,
148 | * null is returned.
149 | *
150 | * @param {String} file
151 | * @param {Number} line The line the type is located at. The first line is 1, not 0.
152 | * @param {String} type
153 | * @param {String} kind The kind of element. Either 'classlike', 'constant' or 'function'.
154 | *
155 | * @return {Promise}
156 | ###
157 | localizeType: (file, line, type, kind) ->
158 | return @proxy.localizeType(file, line, type, kind)
159 |
160 | ###*
161 | * Performs a semantic lint of the specified file.
162 | *
163 | * @param {String} file
164 | * @param {String|null} source The source code of the file to index. May be null if a directory is passed instead.
165 | * @param {Object} options Additional options to set. Boolean properties noUnknownClasses, noUnknownMembers,
166 | * noUnknownGlobalFunctions, noUnknownGlobalConstants, noDocblockCorrectness and
167 | * noUnusedUseStatements are supported.
168 | *
169 | * @return {Promise}
170 | ###
171 | semanticLint: (file, source, options = {}) ->
172 | return @proxy.semanticLint(file, source, options)
173 |
174 | ###*
175 | * Fetches all available variables at a specific location.
176 | *
177 | * @param {String|null} file The path to the file to examine. May be null if the source parameter is passed.
178 | * @param {String|null} source The source code to search. May be null if a file is passed instead.
179 | * @param {Number} offset The character offset into the file to examine.
180 | *
181 | * @return {Promise}
182 | ###
183 | getAvailableVariablesByOffset: (file, source, offset) ->
184 | return @proxy.getAvailableVariables(file, source, offset)
185 |
186 | ###*
187 | * Deduces the resulting types of an expression.
188 | *
189 | * @param {String|null} expression The expression to deduce the type of, e.g. '$this->foo()'. If null, the
190 | * expression just before the specified offset will be used.
191 | * @param {String} file The path to the file to examine.
192 | * @param {String|null} source The source code to search. May be null if a file is passed instead.
193 | * @param {Number} offset The character offset into the file to examine.
194 | * @param {bool} ignoreLastElement Whether to remove the last element or not, this is useful when the user
195 | * is still writing code, e.g. "$this->foo()->b" would normally return the
196 | * type (class) of 'b', as it is the last element, but as the user is still
197 | * writing code, you may instead be interested in the type of 'foo()'
198 | * instead.
199 | *
200 | * @return {Promise}
201 | ###
202 | deduceTypes: (expression, file, source, offset, ignoreLastElement) ->
203 | return @proxy.deduceTypes(expression, file, source, offset, ignoreLastElement)
204 |
205 | ###*
206 | * Retrieves the call stack of the function or method that is being invoked at the specified position. This can be
207 | * used to fetch information about the function or method call the cursor is in.
208 | *
209 | * @param {String|null} file The path to the file to examine. May be null if the source parameter is passed.
210 | * @param {String|null} source The source code to search. May be null if a file is passed instead.
211 | * @param {Number} offset The character offset into the file to examine.
212 | *
213 | * @return {Promise} With elements 'callStack' (array) as well as 'argumentIndex' which denotes the argument in the
214 | * parameter list the position is located at. Returns 'null' if not in a method or function call.
215 | ###
216 | getInvocationInfo: (file, source, offset) ->
217 | return @proxy.getInvocationInfo(file, source, offset)
218 |
219 | ###*
220 | * Convenience alias for {@see deduceTypes}.
221 | *
222 | * @param {String} expression
223 | * @param {TextEditor} editor
224 | * @param {Range} bufferPosition
225 | *
226 | * @return {Promise}
227 | ###
228 | deduceTypesAt: (expression, editor, bufferPosition) ->
229 | offset = editor.getBuffer().characterIndexForPosition(bufferPosition)
230 |
231 | bufferText = editor.getBuffer().getText()
232 |
233 | return @deduceTypes(expression, editor.getPath(), bufferText, offset)
234 |
235 | ###*
236 | * Refreshes the specified file or folder. This method is asynchronous and will return immediately.
237 | *
238 | * @param {String|Array} path The full path to the file or folder to refresh. Alternatively,
239 | * this can be a list of items to index at the same time.
240 | * @param {String|null} source The source code of the file to index. May be null if a directory is
241 | * passed instead.
242 | * @param {Array} excludedPaths A list of paths to exclude from indexing.
243 | * @param {Array} fileExtensionsToIndex A list of file extensions (without leading dot) to index.
244 | *
245 | * @return {Promise}
246 | ###
247 | reindex: (path, source, excludedPaths, fileExtensionsToIndex) ->
248 | return @indexingMediator.reindex(path, source, excludedPaths, fileExtensionsToIndex)
249 |
250 | ###*
251 | * Initializes a project.
252 | *
253 | * @return {Promise}
254 | ###
255 | initialize: () ->
256 | return @indexingMediator.initialize()
257 |
258 | ###*
259 | * Vacuums a project, cleaning up the index database (e.g. pruning files that no longer exist).
260 | *
261 | * @return {Promise}
262 | ###
263 | vacuum: () ->
264 | return @indexingMediator.vacuum()
265 |
266 | ###*
267 | * Attaches a callback to indexing started event. The returned disposable can be used to detach your event handler.
268 | *
269 | * @param {Callback} callback A callback that takes one parameter which contains a 'path' property.
270 | *
271 | * @return {Disposable}
272 | ###
273 | onDidStartIndexing: (callback) ->
274 | return @indexingMediator.onDidStartIndexing(callback)
275 |
276 | ###*
277 | * Attaches a callback to indexing progress event. The returned disposable can be used to detach your event handler.
278 | *
279 | * @param {Callback} callback A callback that takes one parameter which contains a 'path' and a 'percentage'
280 | * property.
281 | *
282 | * @return {Disposable}
283 | ###
284 | onDidIndexingProgress: (callback) ->
285 | return @indexingMediator.onDidIndexingProgress(callback)
286 |
287 | ###*
288 | * Attaches a callback to indexing finished event. The returned disposable can be used to detach your event handler.
289 | *
290 | * @param {Callback} callback A callback that takes one parameter which contains an 'output' and a 'path' property.
291 | *
292 | * @return {Disposable}
293 | ###
294 | onDidFinishIndexing: (callback) ->
295 | return @indexingMediator.onDidFinishIndexing(callback)
296 |
297 | ###*
298 | * Attaches a callback to indexing failed event. The returned disposable can be used to detach your event handler.
299 | *
300 | * @param {Callback} callback A callback that takes one parameter which contains an 'error' and a 'path' property.
301 | *
302 | * @return {Disposable}
303 | ###
304 | onDidFailIndexing: (callback) ->
305 | return @indexingMediator.onDidFailIndexing(callback)
306 |
307 | ###*
308 | * Determines the current class' FQCN based on the specified buffer position.
309 | *
310 | * @param {TextEditor} editor The editor that contains the class (needed to resolve relative class names).
311 | * @param {Point} bufferPosition
312 | *
313 | * @return {Promise}
314 | ###
315 | determineCurrentClassName: (editor, bufferPosition) ->
316 | return new Promise (resolve, reject) =>
317 | path = editor.getPath()
318 |
319 | if not path?
320 | reject()
321 | return
322 |
323 | successHandler = (classesInFile) =>
324 | for name,classInfo of classesInFile
325 | if bufferPosition.row >= classInfo.startLine and bufferPosition.row <= classInfo.endLine
326 | resolve(name)
327 |
328 | resolve(null)
329 |
330 | failureHandler = () =>
331 | reject()
332 |
333 | return @getClassListForFile(path).then(successHandler, failureHandler)
334 |
335 | ###*
336 | * Determines the current namespace on the specified buffer position.
337 | *
338 | * @param {TextEditor} editor The editor that contains the class (needed to resolve relative class names).
339 | * @param {Point} bufferPosition
340 | *
341 | * @return {Promise}
342 | ###
343 | determineCurrentNamespaceName: (editor, bufferPosition) ->
344 | return new Promise (resolve, reject) =>
345 | path = editor.getPath()
346 |
347 | if not path?
348 | reject()
349 | return
350 |
351 | successHandler = (namespacesInFile) =>
352 | for namespace in namespacesInFile
353 | if bufferPosition.row >= namespace.startLine and (bufferPosition.row <= namespace.endLine or not namespace.endLine?)
354 | resolve(namespace.name)
355 |
356 | resolve(null)
357 |
358 | failureHandler = () =>
359 | reject()
360 |
361 | return @getNamespaceListForFile(path).then(successHandler, failureHandler)
362 |
363 | ###*
364 | * Convenience function that resolves types using {@see resolveType}, automatically determining the correct
365 | * parameters for the editor and buffer position.
366 | *
367 | * @param {TextEditor} editor The editor.
368 | * @param {Point} bufferPosition The location of the type.
369 | * @param {String} type The (local) type to resolve.
370 | * @param {String} kind The kind of element. Either 'classlike', 'constant' or 'function'.
371 | *
372 | * @return {Promise}
373 | *
374 | * @example In a file with namespace A\B, determining C could lead to A\B\C.
375 | ###
376 | resolveTypeAt: (editor, bufferPosition, type, kind) ->
377 | return @resolveType(editor.getPath(), bufferPosition.row + 1, type, kind)
378 |
379 | ###*
380 | * Retrieves all variables that are available at the specified buffer position.
381 | *
382 | * @param {TextEditor} editor
383 | * @param {Range} bufferPosition
384 | *
385 | * @return {Promise}
386 | ###
387 | getAvailableVariables: (editor, bufferPosition) ->
388 | offset = editor.getBuffer().characterIndexForPosition(bufferPosition)
389 |
390 | return @getAvailableVariablesByOffset(editor.getPath(), editor.getBuffer().getText(), offset)
391 |
392 | ###*
393 | * Retrieves the types that are being used (called) at the specified location in the buffer. Note that this does not
394 | * guarantee that the returned types actually exist. You can use {@see getClassInfo} on the returned class name
395 | * to check for this instead.
396 | *
397 | * @param {TextEditor} editor The text editor to use.
398 | * @param {Point} bufferPosition The cursor location of the item, such as the class member. Note that this
399 | * should always be at the end of the actual member (i.e. just after it).
400 | * If you want to ignore the element at the buffer position itself, see
401 | * 'ignoreLastElement'.
402 | * @param {boolean} ignoreLastElement Whether to remove the last element or not, this is useful when the user
403 | * is still writing code, e.g. "$this->foo()->b" would normally return the
404 | * type (class) of 'b', as it is the last element, but as the user is still
405 | * writing code, you may instead be interested in the type of 'foo()' instead.
406 | *
407 | * @return {Promise}
408 | *
409 | * @example Invoking it on MyMethod::foo()->bar() will ask what class 'bar' is invoked on, which will whatever types
410 | * foo returns.
411 | ###
412 | getResultingTypesAt: (editor, bufferPosition, ignoreLastElement) ->
413 | offset = editor.getBuffer().characterIndexForPosition(bufferPosition)
414 |
415 | bufferText = editor.getBuffer().getText()
416 |
417 | return @deduceTypes(null, editor.getPath(), bufferText, offset, true)
418 |
419 | ###*
420 | * Retrieves the call stack of the function or method that is being invoked at the specified position. This can be
421 | * used to fetch information about the function or method call the cursor is in.
422 | *
423 | * @param {TextEditor} editor
424 | * @param {Point} bufferPosition
425 | *
426 | * @return {Promise} With elements 'callStack' (array) as well as 'argumentIndex' which denotes the argument in the
427 | * parameter list the position is located at. Returns 'null' if not in a method or function call.
428 | *
429 | * @example "$this->test(1, function () {},| 2);" (where the vertical bar denotes the cursor position) will yield
430 | * ['$this', 'test'].
431 | ###
432 | getInvocationInfoAt: (editor, bufferPosition) ->
433 | offset = editor.getBuffer().characterIndexForPosition(bufferPosition)
434 |
435 | bufferText = editor.getBuffer().getText()
436 |
437 | return @getInvocationInfo(editor.getPath(), bufferText, offset)
438 |
439 | ###*
440 | * Creates a popover with the specified constructor arguments.
441 | ###
442 | createPopover: () ->
443 | return new Popover(arguments...)
444 |
445 | ###*
446 | * Creates an attached popover with the specified constructor arguments.
447 | ###
448 | createAttachedPopover: () ->
449 | return new AttachedPopover(arguments...)
450 |
451 | ###*
452 | * Indicates if the specified type is a basic type (e.g. int, array, object, etc.).
453 | *
454 | * @param {String} type
455 | *
456 | * @return {boolean}
457 | ###
458 | isBasicType: (type) ->
459 | return /^(string|int|bool|float|object|mixed|array|resource|void|null|callable|false|true|self|static|parent|\$this)$/i.test(type)
460 |
461 | ###*
462 | * Utility function to convert byte offsets returned by the service into character offsets.
463 | *
464 | * @param {Number} byteOffset
465 | * @param {String} string
466 | *
467 | * @return {Number}
468 | ###
469 | getCharacterOffsetFromByteOffset: (byteOffset, string) ->
470 | {Buffer} = require 'buffer'
471 |
472 | buffer = new Buffer(string)
473 |
474 | return buffer.slice(0, byteOffset).toString().length
475 |
476 | ###*
477 | * @param {String} fqcn
478 | *
479 | * @return {String}
480 | ###
481 | getDocumentationUrlForClass: (fqcn) ->
482 | return @config.get('php_documentation_base_urls').classes + @getNormalizedFqcnDocumentationUrl(fqcn)
483 |
484 | ###*
485 | * @param {String} fqcn
486 | *
487 | * @return {String}
488 | ###
489 | getDocumentationUrlForFunction: (fqcn) ->
490 | return @config.get('php_documentation_base_urls').functions + @getNormalizedFqcnDocumentationUrl(fqcn)
491 |
492 | ###*
493 | * @param {String} classlikeFqcn
494 | * @param {String} name
495 | *
496 | * @return {String}
497 | ###
498 | getDocumentationUrlForClassMethod: (classlikeFqcn, name) ->
499 | return @config.get('php_documentation_base_urls').root + @getNormalizedFqcnDocumentationUrl(classlikeFqcn) + '.' + @getNormalizeMethodDocumentationUrl(name)
500 |
501 | ###*
502 | * @param {String} name
503 | *
504 | * @return {String}
505 | ###
506 | getNormalizedFqcnDocumentationUrl: (name) ->
507 | if name.length > 0 and name[0] == '\\'
508 | name = name.substr(1)
509 |
510 | return name.replace(/\\/g, '-').toLowerCase()
511 |
512 | ###*
513 | * @param {String} name
514 | *
515 | * @return {String}
516 | ###
517 | getNormalizeMethodDocumentationUrl: (name) ->
518 | return name.replace(/^__/, '')
519 |
--------------------------------------------------------------------------------
/lib/Main.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 | ###*
3 | * Configuration settings.
4 | ###
5 | config:
6 | phpCommand:
7 | title : 'PHP command'
8 | description : 'The path to your PHP binary (e.g. /usr/bin/php, php, ...). Requires a restart. If you update
9 | to a new minor or major version, you may want to force reindex your project to index the new
10 | built-in structural elements.'
11 | type : 'string'
12 | default : 'php'
13 | order : 1
14 |
15 | additionalIndexingDelay:
16 | title : 'Additional delay before reindexing'
17 | description : 'File reindexing occurs as soon as its editor\'s contents stop changing. This is after a
18 | fixed time (about 300 ms at the time of writing) and is managed by Atom itself. If this is
19 | too fast for you, you can add an additional delay with this option. Fewer indexes means less
20 | load as tasks such as linting are invoked less often. It also means that it will take longer
21 | for changes to be reflected in various components, such as autocompletion.'
22 | type : 'integer'
23 | default : 0
24 | order : 2
25 |
26 | memoryLimit:
27 | title : 'Memory limit (in MB)'
28 | description : 'The memory limit to set to the PHP process. The PHP process uses the available memory for
29 | in-memory caching as well, so it should not be too low. On the other hand, it should\'t be
30 | growing very large, so setting it to -1 is probably a bad idea as an infinite loop bug
31 | might take down your system. The default is probably a good value, unless there is a
32 | specific reason you want to change it.'
33 | type : 'integer'
34 | default : 1024
35 | order : 3
36 |
37 | insertNewlinesForUseStatements:
38 | title : 'Insert newlines for use statements'
39 | description : 'When enabled, additional newlines are inserted before or after an automatically added
40 | use statement when they can\'t be nicely added to an existing \'group\'. This results in
41 | more cleanly separated use statements but will create additional vertical whitespace.'
42 | type : 'boolean'
43 | default : false
44 | order : 4
45 |
46 | ###*
47 | * The version of the core to download (version specification string).
48 | *
49 | * @var {String}
50 | ###
51 | coreVersionSpecification: "2.1.7"
52 |
53 | ###*
54 | * The name of the package.
55 | *
56 | * @var {String}
57 | ###
58 | packageName: 'php-integrator-base-legacy-php56'
59 |
60 | ###*
61 | * The configuration object.
62 | *
63 | * @var {Object}
64 | ###
65 | configuration: null
66 |
67 | ###*
68 | * The proxy object.
69 | *
70 | * @var {Object}
71 | ###
72 | proxy: null
73 |
74 | ###*
75 | * The exposed service.
76 | *
77 | * @var {Object}
78 | ###
79 | service: null
80 |
81 | ###*
82 | * The status bar manager.
83 | *
84 | * @var {Object}
85 | ###
86 | statusBarManager: null
87 |
88 | ###*
89 | * @var {IndexingMediator}
90 | ###
91 | indexingMediator: null
92 |
93 | ###*
94 | * A list of disposables to dispose when the package deactivates.
95 | *
96 | * @var {Object|null}
97 | ###
98 | disposables: null
99 |
100 | ###*
101 | * The currently active project, if any.
102 | *
103 | * @var {Object|null}
104 | ###
105 | activeProject: null
106 |
107 | ###*
108 | * @var {String|null}
109 | ###
110 | timerName: null
111 |
112 | ###*
113 | * @var {String|null}
114 | ###
115 | progressBarTimeout: null
116 |
117 | ###*
118 | * The service instance from the project-manager package.
119 | *
120 | * @var {Object|null}
121 | ###
122 | projectManagerService: null
123 |
124 | ###*
125 | * @var {Object|null}
126 | ###
127 | editorTimeoutMap: null
128 |
129 | ###*
130 | * Tests the user's configuration.
131 | *
132 | * @param {bool} testServices
133 | *
134 | * @return {bool}
135 | ###
136 | testConfig: (testServices = true) ->
137 | ConfigTester = require './ConfigTester'
138 |
139 | configTester = new ConfigTester(@getConfiguration())
140 |
141 | result = configTester.test()
142 |
143 | if not result
144 | errorMessage =
145 | "PHP is not correctly set up and as a result PHP integrator will not work. Please visit the settings
146 | screen to correct this error. If you are not specifying an absolute path for PHP or Composer, make
147 | sure they are in your PATH."
148 |
149 | atom.notifications.addError('Incorrect setup!', {'detail': errorMessage})
150 |
151 | return false
152 |
153 | if testServices and not @projectManagerService?
154 | errorMessage =
155 | "There is no project manager service available. Install the atom-project-manager package for project
156 | support to work in its full extent."
157 |
158 | atom.notifications.addError('Incorrect setup!', {'detail': errorMessage})
159 |
160 | return false
161 |
162 | return true
163 |
164 | ###*
165 | * Registers any commands that are available to the user.
166 | ###
167 | registerCommands: () ->
168 | atom.commands.add 'atom-workspace', "php-integrator-base:set-up-current-project": =>
169 | if not @projectManagerService?
170 | errorMessage = '''
171 | The project manager service was not found. Did you perhaps forget to install the project-manager
172 | package or another package able to provide it?
173 | '''
174 |
175 | atom.notifications.addError('Incorrect setup!', {'detail': errorMessage})
176 | return
177 |
178 | if not @activeProject?
179 | errorMessage = '''
180 | No project is currently active. Please set up and activate one before attempting to set it up.
181 | '''
182 |
183 | atom.notifications.addError('Incorrect setup!', {'detail': errorMessage})
184 | return
185 |
186 | project = @activeProject
187 |
188 | newProperties = null
189 |
190 | try
191 | newProperties = @projectManager.setUpProject(project)
192 |
193 | if not newProperties?
194 | throw new Error('No properties returned, this should never happen!')
195 |
196 | catch error
197 | atom.notifications.addError('Error!', {
198 | 'detail' : error.message
199 | })
200 |
201 | return
202 |
203 | @projectManagerService.saveProject(newProperties)
204 |
205 | atom.notifications.addSuccess 'Success', {
206 | 'detail' : 'Your current project has been set up as PHP project. Indexing will now commence.'
207 | }
208 |
209 | @projectManager.load(project)
210 |
211 | @performInitialFullIndexForCurrentProject()
212 |
213 | atom.commands.add 'atom-workspace', "php-integrator-base:index-project": =>
214 | return if not @projectManager.hasActiveProject()
215 |
216 | @projectManager.attemptCurrentProjectIndex()
217 |
218 | atom.commands.add 'atom-workspace', "php-integrator-base:force-index-project": =>
219 | return if not @projectManager.hasActiveProject()
220 |
221 | @performInitialFullIndexForCurrentProject()
222 |
223 | atom.commands.add 'atom-workspace', "php-integrator-base:configuration": =>
224 | return unless @testConfig()
225 |
226 | atom.notifications.addSuccess 'Success', {
227 | 'detail' : 'Your PHP integrator configuration is working correctly!'
228 | }
229 |
230 | atom.commands.add 'atom-workspace', "php-integrator-base:sort-use-statements": =>
231 | activeTextEditor = atom.workspace.getActiveTextEditor()
232 |
233 | return if not activeTextEditor?
234 |
235 | @getUseStatementHelper().sortUseStatements(activeTextEditor)
236 |
237 | ###*
238 | * Performs the "initial" index for a new project by initializing it and then performing a project index.
239 | *
240 | * @return {Promise}
241 | ###
242 | performInitialFullIndexForCurrentProject: () ->
243 | successHandler = () =>
244 | return @projectManager.attemptCurrentProjectIndex()
245 |
246 | failureHandler = (reason) =>
247 | console.error(reason)
248 |
249 | atom.notifications.addError('Error!', {
250 | 'detail' : 'The project could not be properly initialized!'
251 | })
252 |
253 | return @projectManager.initializeCurrentProject().then(successHandler, failureHandler)
254 |
255 | ###*
256 | * Registers listeners for configuration changes.
257 | ###
258 | registerConfigListeners: () ->
259 | config = @getConfiguration()
260 |
261 | config.onDidChange 'insertNewlinesForUseStatements', (value) =>
262 | @getUseStatementHelper().setAllowAdditionalNewlines(value)
263 |
264 | ###*
265 | * Registers status bar listeners.
266 | ###
267 | registerStatusBarListeners: () ->
268 | service = @getService()
269 |
270 | service.onDidStartIndexing () =>
271 | if @progressBarTimeout
272 | clearTimeout(@progressBarTimeout)
273 |
274 | # Indexing could be anything: the entire project or just a file. If indexing anything takes too long, show
275 | # the progress bar to indicate we're doing something.
276 | @progressBarTimeout = setTimeout ( =>
277 | @progressBarTimeout = null
278 |
279 | @timerName = @packageName + " - Indexing"
280 |
281 | console.time(@timerName);
282 |
283 | if @statusBarManager?
284 | @statusBarManager.setLabel("Indexing...")
285 | @statusBarManager.setProgress(null)
286 | @statusBarManager.show()
287 | ), 1000
288 |
289 | service.onDidFinishIndexing () =>
290 | if @progressBarTimeout
291 | clearTimeout(@progressBarTimeout)
292 | @progressBarTimeout = null
293 |
294 | else
295 | console.timeEnd(@timerName)
296 |
297 | if @statusBarManager?
298 | @statusBarManager.setLabel("Indexing completed!")
299 | @statusBarManager.hide()
300 |
301 | service.onDidFailIndexing () =>
302 | if @progressBarTimeout
303 | clearTimeout(@progressBarTimeout)
304 | @progressBarTimeout = null
305 |
306 | else
307 | console.timeEnd(@timerName)
308 |
309 | if @statusBarManager?
310 | @statusBarManager.showMessage("Indexing failed!", "highlight-error")
311 | @statusBarManager.hide()
312 |
313 | service.onDidIndexingProgress (data) =>
314 | if @statusBarManager?
315 | @statusBarManager.setProgress(data.percentage)
316 | @statusBarManager.setLabel("Indexing... (" + data.percentage.toFixed(2) + " %)")
317 |
318 | ###*
319 | * Attaches items to the status bar.
320 | *
321 | * @param {mixed} statusBarService
322 | ###
323 | attachStatusBarItems: (statusBarService) ->
324 | if not @statusBarManager
325 | StatusBarManager = require "./Widgets/StatusBarManager"
326 |
327 | @statusBarManager = new StatusBarManager()
328 | @statusBarManager.initialize(statusBarService)
329 | @statusBarManager.setLabel("Indexing...")
330 |
331 | ###*
332 | * Detaches existing items from the status bar.
333 | ###
334 | detachStatusBarItems: () ->
335 | if @statusBarManager
336 | @statusBarManager.destroy()
337 | @statusBarManager = null
338 |
339 | ###*
340 | * @return {Promise}
341 | ###
342 | updateCoreIfOutdated: () ->
343 | if @getCoreManager().isInstalled()
344 | return new Promise (resolve, reject) ->
345 | resolve()
346 |
347 | message =
348 | "The core isn't installed yet or is outdated. A new version is in the process of being downloaded."
349 |
350 | atom.notifications.addInfo('PHP Integrator - Downloading Core', {'detail': message})
351 |
352 | successHandler = () ->
353 | atom.notifications.addSuccess('Core installation successful')
354 |
355 | failureHandler = () ->
356 | atom.notifications.addError('Core installation failed')
357 |
358 | return @getCoreManager().install().then(successHandler, failureHandler)
359 |
360 | ###*
361 | * Activates the package.
362 | ###
363 | activate: ->
364 | @testConfig(false)
365 |
366 | @updateCoreIfOutdated().then () =>
367 | @registerCommands()
368 | @registerConfigListeners()
369 | @registerStatusBarListeners()
370 |
371 | @editorTimeoutMap = {}
372 |
373 | @registerAtomListeners()
374 |
375 | @getCachingProxy().setIsActive(true)
376 |
377 | ###*
378 | * Registers listeners for events from Atom's API.
379 | ###
380 | registerAtomListeners: () ->
381 | @getDisposables().add atom.workspace.observeTextEditors (editor) =>
382 | @registerTextEditorListeners(editor)
383 |
384 | ###*
385 | * @param {TextEditor} editor
386 | ###
387 | registerTextEditorListeners: (editor) ->
388 | # The default onDidStopChanging timeout is 300 milliseconds. As this is notcurrently configurable (and would
389 | # also impact other packages), we install our own timeout on top of the existing one. This is useful for users
390 | # that don't type particularly fast or are on slower machines and will prevent constant indexing from happening.
391 | @getDisposables().add editor.onDidStopChanging () =>
392 | path = editor.getPath()
393 |
394 | additionalIndexingDelay = @getConfiguration().get('additionalIndexingDelay')
395 |
396 | @editorTimeoutMap[path] = setTimeout ( =>
397 | @onEditorDidStopChanging(editor)
398 | @editorTimeoutMap[path] = null
399 | ), additionalIndexingDelay
400 |
401 | @getDisposables().add editor.onDidChange () =>
402 | path = editor.getPath()
403 |
404 | if @editorTimeoutMap[path]?
405 | clearTimeout(@editorTimeoutMap[path])
406 | @editorTimeoutMap[path] = null
407 |
408 | ###*
409 | * Invoked when an editor stops changing.
410 | *
411 | * @param {TextEditor} editor
412 | ###
413 | onEditorDidStopChanging: (editor) ->
414 | return unless /text.html.php$/.test(editor.getGrammar().scopeName)
415 |
416 | fileName = editor.getPath()
417 |
418 | return if not fileName
419 |
420 | projectManager = @getProjectManager()
421 |
422 | if projectManager.hasActiveProject() and projectManager.isFilePartOfCurrentProject(fileName)
423 | projectManager.attemptCurrentProjectFileIndex(fileName, editor.getBuffer().getText())
424 |
425 | ###*
426 | * Deactivates the package.
427 | ###
428 | deactivate: ->
429 | if @disposables
430 | @disposables.dispose()
431 | @disposables = null
432 |
433 | @getCachingProxy().stopPhpServer()
434 |
435 | ###*
436 | * Sets the status bar service, which is consumed by this package.
437 | *
438 | * @param {Object} service
439 | ###
440 | setStatusBarService: (service) ->
441 | @attachStatusBarItems(service)
442 |
443 | # This method is usually invoked after the indexing has already started, hence we can't unconditionally hide it
444 | # here or it will never be made visible again.
445 | if not @getProjectManager().isProjectIndexing()
446 | @statusBarManager.hide()
447 |
448 | {Disposable} = require 'atom'
449 |
450 | return new Disposable => @detachStatusBarItems()
451 |
452 | ###*
453 | * Sets the project manager service.
454 | *
455 | * @param {Object} service
456 | ###
457 | setProjectManagerService: (service) ->
458 | @projectManagerService = service
459 |
460 | # NOTE: This method is actually called whenever the project changes as well.
461 | service.getProject (project) =>
462 | @onProjectChanged(project)
463 |
464 | ###*
465 | * @param {Object} project
466 | ###
467 | onProjectChanged: (project) ->
468 | @activeProject = project
469 |
470 | return if not project?
471 |
472 | @proxy.clearCache()
473 |
474 | projectManager = @getProjectManager()
475 | projectManager.load(project)
476 |
477 | return if not projectManager.hasActiveProject()
478 |
479 | successHandler = (isProjectInGoodShape) =>
480 | # NOTE: If the index is manually deleted, testing will return false so the project is reinitialized.
481 | # This is needed to index built-in items as they are not automatically indexed by indexing the project.
482 | if not isProjectInGoodShape
483 | return @performInitialFullIndexForCurrentProject()
484 |
485 | else
486 | return @projectManager.attemptCurrentProjectIndex()
487 |
488 | failureHandler = () ->
489 | # Ignore
490 |
491 | @proxy.test().then(successHandler, failureHandler)
492 |
493 | ###*
494 | * Retrieves the base package service that can be used by other packages.
495 | *
496 | * @return {Service}
497 | ###
498 | getServiceInstance: () ->
499 | return @getService()
500 |
501 | ###*
502 | * @return {Service}
503 | ###
504 | getService: () ->
505 | if not @disposables?
506 | Service = require './Service'
507 |
508 | @service = new Service(
509 | @getConfiguration(),
510 | @getCachingProxy(),
511 | @getProjectManager(),
512 | @getIndexingMediator(),
513 | @getUseStatementHelper()
514 | )
515 |
516 | return @service
517 |
518 | ###*
519 | * @return {Disposables}
520 | ###
521 | getDisposables: () ->
522 | if not @disposables?
523 | {CompositeDisposable} = require 'atom';
524 |
525 | @disposables = new CompositeDisposable()
526 |
527 | return @disposables
528 |
529 | ###*
530 | * @return {Configuration}
531 | ###
532 | getConfiguration: () ->
533 | if not @configuration?
534 | AtomConfig = require './AtomConfig'
535 |
536 | @configuration = new AtomConfig(@packageName)
537 |
538 | return @configuration
539 |
540 | ###*
541 | * @return {CachingProxy}
542 | ###
543 | getCachingProxy: () ->
544 | if not @proxy?
545 | CachingProxy = require './CachingProxy'
546 |
547 | @proxy = new CachingProxy(@getConfiguration())
548 | @proxy.setCorePath(@getCoreManager().getCoreSourcePath())
549 |
550 | return @proxy
551 |
552 | ###*
553 | * @return {Emitter}
554 | ###
555 | getEmitter: () ->
556 | if not @emitter?
557 | {Emitter} = require 'event-kit';
558 |
559 | @emitter = new Emitter()
560 |
561 | return @emitter
562 |
563 | ###*
564 | * @return {ComposerService}
565 | ###
566 | getComposerService: () ->
567 | if not @composerService?
568 | ComposerService = require './ComposerService';
569 |
570 | @composerService = new ComposerService(
571 | @getConfiguration().get('phpCommand'),
572 | @getConfiguration().get('packagePath') + '/core/'
573 | )
574 |
575 | return @composerService
576 |
577 | ###*
578 | * @return {CoreManager}
579 | ###
580 | getCoreManager: () ->
581 | if not @coreManager?
582 | CoreManager = require './CoreManager';
583 |
584 | @coreManager = new CoreManager(
585 | @getComposerService(),
586 | @coreVersionSpecification,
587 | @getConfiguration().get('packagePath') + '/core/'
588 | )
589 |
590 | return @coreManager
591 |
592 | ###*
593 | * @return {UseStatementHelper}
594 | ###
595 | getUseStatementHelper: () ->
596 | if not @useStatementHelper?
597 | UseStatementHelper = require './UseStatementHelper';
598 |
599 | @useStatementHelper = new UseStatementHelper(@getConfiguration().get('insertNewlinesForUseStatements'))
600 |
601 | return @useStatementHelper
602 |
603 | ###*
604 | * @return {IndexingMediator}
605 | ###
606 | getIndexingMediator: () ->
607 | if not @indexingMediator?
608 | IndexingMediator = require './IndexingMediator'
609 |
610 | @indexingMediator = new IndexingMediator(@getCachingProxy(), @getEmitter())
611 |
612 | return @indexingMediator
613 |
614 | ###*
615 | * @return {ProjectManager}
616 | ###
617 | getProjectManager: () ->
618 | if not @projectManager?
619 | ProjectManager = require './ProjectManager'
620 |
621 | @projectManager = new ProjectManager(@getCachingProxy(), @getIndexingMediator())
622 |
623 | return @projectManager
624 |
--------------------------------------------------------------------------------
/lib/Proxy.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | net = require 'net'
3 | stream = require 'stream'
4 | child_process = require 'child_process'
5 |
6 | module.exports =
7 |
8 | ##*
9 | # Proxy that handles communicating with the PHP side.
10 | ##
11 | class Proxy
12 | ###*
13 | * The config to use.
14 | *
15 | * @var {Object}
16 | ###
17 | config: null
18 |
19 | ###*
20 | * The name (without path or extension) of the database file to use.
21 | *
22 | * @var {Object}
23 | ###
24 | indexDatabaseName: null
25 |
26 | ###*
27 | * @var {Boolean}
28 | ###
29 | isActive: false
30 |
31 | ###*
32 | * @var {String}
33 | ###
34 | corePath: null
35 |
36 | ###*
37 | * @var {Object}
38 | ###
39 | phpServer: null
40 |
41 | ###*
42 | * @var {Promise}
43 | ###
44 | phpServerPromise: null
45 |
46 | ###*
47 | * @var {Object}
48 | ###
49 | client: null
50 |
51 | ###*
52 | * @var {Object}
53 | ###
54 | requestQueue: null
55 |
56 | ###*
57 | * @var {Number}
58 | ###
59 | nextRequestId: 1
60 |
61 | ###*
62 | * @var {Object}
63 | ###
64 | response: null
65 |
66 | ###*
67 | * @var {String}
68 | ###
69 | HEADER_DELIMITER: "\r\n"
70 |
71 | ###*
72 | * @var {Number}
73 | ###
74 | FATAL_SERVER_ERROR: -32000
75 |
76 | ###*
77 | * Constructor.
78 | *
79 | * @param {Config} config
80 | ###
81 | constructor: (@config) ->
82 | @requestQueue = {}
83 | @port = @getRandomServerPort()
84 |
85 | @resetResponseState()
86 |
87 | ###*
88 | * Spawns the PHP socket server process.
89 | *
90 | * @param {Number} port
91 | *
92 | * @return {Promise}
93 | ###
94 | spawnPhpServer: (port) ->
95 | php = @config.get('phpCommand')
96 | memoryLimit = @config.get('memoryLimit')
97 |
98 | parameters = [
99 | '-d memory_limit=' + memoryLimit + 'M',
100 | @corePath + "/src/Main.php",
101 | '--port=' + port
102 | ]
103 |
104 | process = child_process.spawn(php, parameters)
105 |
106 | return new Promise (resolve, reject) =>
107 | process.stdout.on 'data', (data) =>
108 | message = data.toString()
109 |
110 | console.debug('The PHP server has something to say:', message)
111 |
112 | if message.startsWith('Starting socket server')
113 | # Assume the server has successfully spawned the moment it says its first words.
114 | resolve(process)
115 |
116 | process.stderr.on 'data', (data) =>
117 | console.warn('The PHP server has errors to report:', data.toString())
118 |
119 | process.on 'error', (error) =>
120 | console.error('An error ocurred whilst invoking PHP', error)
121 | reject()
122 |
123 | process.on 'close', (code) =>
124 | if code == 2
125 | console.error('Port ' + port + ' is already taken')
126 | return
127 |
128 | console.error('PHP socket server exited by itself, a fatal error must have occurred.')
129 |
130 | @closeServerConnection()
131 |
132 | @phpServer = null
133 | reject()
134 |
135 | ###*
136 | * @return {Number}
137 | ###
138 | getRandomServerPort: () ->
139 | minPort = 10000
140 | maxPort = 40000
141 |
142 | return Math.floor(Math.random() * (maxPort - minPort) + minPort)
143 |
144 | ###*
145 | * Spawns the PHP socket server process.
146 | *
147 | * @param {Number} port
148 | *
149 | * @return {Promise}
150 | ###
151 | spawnPhpServerIfNecessary: (port) ->
152 | if @phpServer
153 | @phpServerPromise = null
154 |
155 | return new Promise (resolve, reject) =>
156 | resolve(@phpServer)
157 |
158 | else if @phpServerPromise
159 | return @phpServerPromise
160 |
161 | successHandler = (phpServer) =>
162 | @phpServer = phpServer
163 |
164 | return phpServer
165 |
166 | failureHandler = () =>
167 | @phpServerPromise = null
168 |
169 | @phpServerPromise = @spawnPhpServer(port).then(successHandler, failureHandler)
170 |
171 | return @phpServerPromise
172 |
173 | ###*
174 | * Sends the kill signal to the socket server.
175 | *
176 | * Note that this is a signal, the process may ignore it (but it usually will not, unless it's really persistent in
177 | * continuing whatever it's doing).
178 | *
179 | * @param {Array} parameters
180 | *
181 | * @return {Array}
182 | ###
183 | stopPhpServer: (port) ->
184 | @closeServerConnection()
185 |
186 | return if not @phpServer
187 |
188 | @phpServer.kill()
189 | @phpServer = null
190 |
191 | ###*
192 | * Closes the socket connection to the server.
193 | ###
194 | closeServerConnection: () ->
195 | @rejectAllOpenRequests()
196 |
197 | return if not @client
198 |
199 | @client.destroy()
200 | @client = null
201 |
202 | @resetResponseState()
203 |
204 | ###*
205 | * Rejects all currently open requests.
206 | ###
207 | rejectAllOpenRequests: () ->
208 | for id,request of @requestQueue
209 | request.promise.reject('The socket connection was closed and had to reconnect')
210 |
211 | @requestQueue = {}
212 |
213 | ###*
214 | * @return {Object}
215 | ###
216 | getSocketConnection: () ->
217 | return new Promise (resolve, reject) =>
218 | @spawnPhpServerIfNecessary(@port).then () =>
219 | if @client?
220 | resolve(@client)
221 | return
222 |
223 | @client = net.createConnection {port: @port}, () =>
224 | resolve(@client)
225 |
226 | @client.setNoDelay(true)
227 | @client.on('error', @onSocketError.bind(this))
228 | @client.on('data', @onDataReceived.bind(this))
229 | @client.on('close', @onConnectionClosed.bind(this))
230 |
231 | ###*
232 | * @param {String} data
233 | ###
234 | onDataReceived: (data) ->
235 | try
236 | @processDataBuffer(data)
237 |
238 | catch error
239 | console.warn('Encountered some invalid data, resetting state. Error: ', error)
240 |
241 | @resetResponseState()
242 |
243 | ###*
244 | * @param {Object} error
245 | ###
246 | onSocketError: (error) ->
247 | # Do nothing here, this should silence socket errors such as ECONNRESET. After this is called, the socket will
248 | # be closed and all handling is performed there.
249 | console.warn('The socket connection notified us of an error', error)
250 |
251 | ###*
252 | * @param {Boolean} hadError
253 | ###
254 | onConnectionClosed: (hadError) ->
255 | detail =
256 | "The socket connection to the PHP server was unexpectedly closed. Either something caused the " +
257 | "process to stop, it crashed, or the socket closed. In case of the first two, you should see " +
258 | "additional output indicating this is the case and you can report a bug. If there is no additional " +
259 | "output, the socket connection should automatically be reestablished and everything should continue " +
260 | "working."
261 |
262 | if hadError
263 | detail += ' It was also reported that an error was involved'
264 |
265 | console.warn(detail)
266 |
267 | @closeServerConnection()
268 |
269 | ###*
270 | * @param {Buffer} dataBuffer
271 | ###
272 | processDataBuffer: (dataBuffer) ->
273 | if not @response.length?
274 | contentLengthHeader = @readRawHeader(dataBuffer)
275 | @response.length = @getLengthFromContentLengthHeader(contentLengthHeader)
276 |
277 | bytesRead = contentLengthHeader.length + @HEADER_DELIMITER.length
278 |
279 | else if not @response.wasBoundaryFound
280 | header = @readRawHeader(dataBuffer)
281 |
282 | if header.length == 0
283 | @response.wasBoundaryFound = true
284 |
285 | bytesRead = header.length + @HEADER_DELIMITER.length
286 |
287 | else
288 | bytesRead = Math.min(dataBuffer.length, @response.length - @response.bytesRead)
289 |
290 | @response.content = Buffer.concat([@response.content, dataBuffer.slice(0, bytesRead)])
291 | @response.bytesRead += bytesRead
292 |
293 | if @response.bytesRead == @response.length
294 | jsonRpcResponse = @getJsonRpcResponseFromResponseBuffer(@response.content)
295 |
296 | @processJsonRpcResponse(jsonRpcResponse)
297 |
298 | @resetResponseState()
299 |
300 | dataBuffer = dataBuffer.slice(bytesRead)
301 |
302 | if dataBuffer.length > 0
303 | @processDataBuffer(dataBuffer)
304 |
305 | ###*
306 | * @param {Object} jsonRpcResponse
307 | ###
308 | processJsonRpcResponse: (jsonRpcResponse) ->
309 | if jsonRpcResponse.id?
310 | jsonRpcRequest = @requestQueue[jsonRpcResponse.id]
311 |
312 | @processJsonRpcResponseForRequest(jsonRpcResponse, jsonRpcRequest)
313 |
314 | delete @requestQueue[jsonRpcResponse.id]
315 |
316 | else
317 | @processNotificationJsonRpcResponse(jsonRpcResponse)
318 |
319 | ###*
320 | * @param {Object} jsonRpcResponse
321 | * @param {Object} jsonRpcRequest
322 | ###
323 | processJsonRpcResponseForRequest: (jsonRpcResponse, jsonRpcRequest) ->
324 | if jsonRpcResponse.error?
325 | jsonRpcRequest.promise.reject({
326 | request : jsonRpcRequest
327 | response : jsonRpcResponse
328 | error : jsonRpcResponse.error
329 | })
330 |
331 | if jsonRpcResponse.error.code == @FATAL_SERVER_ERROR
332 | @showUnexpectedSocketResponseError(jsonRpcResponse.error.message)
333 |
334 | else
335 | jsonRpcRequest.promise.resolve(jsonRpcResponse.result)
336 |
337 | ###*
338 | * @param {Object} jsonRpcResponse
339 | * @param {Object} jsonRpcRequest
340 | ###
341 | processNotificationJsonRpcResponse: (jsonRpcResponse) ->
342 | if not jsonRpcResponse.result?
343 | console.warn('Received a server notification without a result', jsonRpcResponse)
344 | return
345 |
346 | if jsonRpcResponse.result.type == 'reindexProgressInformation'
347 | if not jsonRpcResponse.result.requestId?
348 | console.warn('Received progress information without a request ID to go with it', jsonRpcResponse)
349 | return
350 |
351 | relatedJsonRpcRequest = @requestQueue[jsonRpcResponse.result.requestId]
352 |
353 | if not relatedJsonRpcRequest.streamCallback?
354 | console.warn('Received progress information for a request that isn\'t interested in it')
355 | return
356 |
357 | relatedJsonRpcRequest.streamCallback(jsonRpcResponse.result.progress)
358 |
359 | else
360 | console.warn('Received a server notification with an unknown type', jsonRpcResponse)
361 |
362 | ###*
363 | * @param {Buffer} dataBuffer
364 | *
365 | * @return {Object}
366 | ###
367 | getJsonRpcResponseFromResponseBuffer: (dataBuffer) ->
368 | jsonRpcResponseString = dataBuffer.toString()
369 |
370 | return @getJsonRpcResponseFromResponseContent(jsonRpcResponseString)
371 |
372 | ###*
373 | * @param {String} content
374 | *
375 | * @return {Object}
376 | ###
377 | getJsonRpcResponseFromResponseContent: (content) ->
378 | return JSON.parse(content)
379 |
380 | ###*
381 | * @param {Buffer} dataBuffer
382 | *
383 | * @throws {Error}
384 | *
385 | * @return {String}
386 | ###
387 | readRawHeader: (dataBuffer) ->
388 | end = dataBuffer.indexOf(@HEADER_DELIMITER)
389 |
390 | if end == -1
391 | throw new Error('Header delimiter not found');
392 |
393 | return dataBuffer.slice(0, end).toString()
394 |
395 | ###*
396 | * @param {String} rawHeader
397 | *
398 | * @throws {Error}
399 | *
400 | * @return {Number}
401 | ###
402 | getLengthFromContentLengthHeader: (rawHeader) ->
403 | parts = rawHeader.split(':')
404 |
405 | if parts.length != 2
406 | throw new Error('Unexpected amount of header parts found')
407 |
408 | contentLength = parseInt(parts[1])
409 |
410 | if not contentLength?
411 | throw new Error('Content length header does not have an integer as value')
412 |
413 | return contentLength
414 |
415 | ###*
416 | * Resets the current response's state.
417 | ###
418 | resetResponseState: () ->
419 | @response =
420 | length : null
421 | wasBoundaryFound : false
422 | bytesRead : 0
423 | content : new Buffer([])
424 |
425 | ###*
426 | * Performs an asynchronous request to the PHP side.
427 | *
428 | * @param {Number} id
429 | * @param {String} method
430 | * @param {Object} parameters
431 | * @param {Callback} streamCallback
432 | *
433 | * @return {Promise}
434 | ###
435 | performJsonRpcRequest: (id, method, parameters, streamCallback = null) ->
436 | return new Promise (resolve, reject) =>
437 | if not @getIsActive()
438 | reject('The proxy is not yet active, the core may be in the process of being downloaded')
439 | return
440 |
441 | JsonRpcRequest =
442 | jsonrpc : 2.0
443 | id : id
444 | method : method
445 | params : parameters
446 |
447 | @requestQueue[id] = {
448 | id : id
449 | streamCallback : streamCallback
450 | request : JsonRpcRequest
451 |
452 | promise: {
453 | resolve : resolve
454 | reject : reject
455 | }
456 | }
457 |
458 | content = @getContentForJsonRpcRequest(JsonRpcRequest)
459 |
460 | @writeRawRequest(content)
461 |
462 | ###*
463 | * @param {Object} request
464 | *
465 | * @return {String}
466 | ###
467 | getContentForJsonRpcRequest: (request) ->
468 | return JSON.stringify(request)
469 |
470 | ###*
471 | * Writes a raw request to the connection.
472 | *
473 | * This may not happen immediately if the connection is not available yet. In that case, the request will be
474 | * dispatched as soon as the connection becomes available.
475 | *
476 | * @param {String} content The content (body) of the request.
477 | ###
478 | writeRawRequest: (content) ->
479 | @getSocketConnection().then (connection) =>
480 | lengthInBytes = (new TextEncoder('utf-8').encode(content)).length
481 |
482 | connection.write("Content-Length: " + lengthInBytes + @HEADER_DELIMITER)
483 | connection.write(@HEADER_DELIMITER);
484 | connection.write(content)
485 |
486 | ###*
487 | * @param {String} rawOutput
488 | * @param {Array|null} parameters
489 | ###
490 | showUnexpectedSocketResponseError: (rawOutput, parameters = null) ->
491 | detail =
492 | "The socket server sent back something unexpected. This could be a bug, but it could also be a problem " +
493 | "with your setup. If you're sure it is a bug, feel free to report it on the bug tracker."
494 |
495 | if parameters?
496 | detail += "\n \nCommand\n → " + parameters.join(' ')
497 |
498 | detail += "\n \nOutput\n → " + rawOutput
499 |
500 | atom.notifications.addError('PHP Integrator - Oops, something went wrong!', {
501 | dismissable : true
502 | detail : detail
503 | })
504 |
505 | ###*
506 | * @param {String} method
507 | * @param {Object} parameters
508 | * @param {Callback} streamCallback A method to invoke each time streaming data is received.
509 | * @param {String|null} stdinData The data to pass to STDIN.
510 | *
511 | * @return {Promise}
512 | ###
513 | performRequest: (method, parameters, streamCallback = null, stdinData = null) ->
514 | if stdinData?
515 | parameters.stdin = true
516 | parameters.stdinData = stdinData
517 |
518 | requestId = @nextRequestId++
519 |
520 | return @performJsonRpcRequest(requestId, method, parameters, streamCallback)
521 |
522 | ###*
523 | * Retrieves a list of available classes.
524 | *
525 | * @return {Promise}
526 | ###
527 | getClassList: () ->
528 | if not @getIndexDatabasePath()?
529 | return new Promise (resolve, reject) ->
530 | reject('Request aborted as there is no project active (yet)')
531 |
532 | parameters = {
533 | database : @getIndexDatabasePath()
534 | }
535 |
536 | return @performRequest('classList', parameters)
537 |
538 | ###*
539 | * Retrieves a list of available classes in the specified file.
540 | *
541 | * @param {String} file
542 | *
543 | * @return {Promise}
544 | ###
545 | getClassListForFile: (file) ->
546 | if not file
547 | return new Promise (resolve, reject) ->
548 | reject('No file passed!')
549 |
550 | if not @getIndexDatabasePath()?
551 | return new Promise (resolve, reject) ->
552 | reject('Request aborted as there is no project active (yet)')
553 |
554 | parameters = {
555 | database : @getIndexDatabasePath()
556 | file : file
557 | }
558 |
559 | return @performRequest('classList', parameters)
560 |
561 | ###*
562 | * Retrieves a list of namespaces.
563 | *
564 | * @return {Promise}
565 | ###
566 | getNamespaceList: () ->
567 | if not @getIndexDatabasePath()?
568 | return new Promise (resolve, reject) ->
569 | reject('Request aborted as there is no project active (yet)')
570 |
571 | parameters = {
572 | database : @getIndexDatabasePath()
573 | }
574 |
575 | return @performRequest('namespaceList', parameters)
576 |
577 | ###*
578 | * Retrieves a list of namespaces in the specified file.
579 | *
580 | * @param {String} file
581 | *
582 | * @return {Promise}
583 | ###
584 | getNamespaceListForFile: (file) ->
585 | if not file
586 | return new Promise (resolve, reject) ->
587 | reject('No file passed!')
588 |
589 | if not @getIndexDatabasePath()?
590 | return new Promise (resolve, reject) ->
591 | reject('Request aborted as there is no project active (yet)')
592 |
593 | parameters = {
594 | database : @getIndexDatabasePath()
595 | file : file
596 | }
597 |
598 | return @performRequest('namespaceList', parameters)
599 |
600 | ###*
601 | * Retrieves a list of available global constants.
602 | *
603 | * @return {Promise}
604 | ###
605 | getGlobalConstants: () ->
606 | if not @getIndexDatabasePath()?
607 | return new Promise (resolve, reject) ->
608 | reject('Request aborted as there is no project active (yet)')
609 |
610 | parameters = {
611 | database : @getIndexDatabasePath()
612 | }
613 |
614 | return @performRequest('globalConstants', parameters)
615 |
616 | ###*
617 | * Retrieves a list of available global functions.
618 | *
619 | * @return {Promise}
620 | ###
621 | getGlobalFunctions: () ->
622 | if not @getIndexDatabasePath()?
623 | return new Promise (resolve, reject) ->
624 | reject('Request aborted as there is no project active (yet)')
625 |
626 | parameters = {
627 | database : @getIndexDatabasePath()
628 | }
629 |
630 | return @performRequest('globalFunctions', parameters)
631 |
632 | ###*
633 | * Retrieves a list of available members of the class (or interface, trait, ...) with the specified name.
634 | *
635 | * @param {String} className
636 | *
637 | * @return {Promise}
638 | ###
639 | getClassInfo: (className) ->
640 | if not className
641 | return new Promise (resolve, reject) ->
642 | reject('No class name passed!')
643 |
644 | if not @getIndexDatabasePath()?
645 | return new Promise (resolve, reject) ->
646 | reject('Request aborted as there is no project active (yet)')
647 |
648 | parameters = {
649 | database : @getIndexDatabasePath()
650 | name : className
651 | }
652 |
653 | return @performRequest('classInfo', parameters)
654 |
655 | ###*
656 | * Resolves a local type in the specified file, based on use statements and the namespace.
657 | *
658 | * @param {String} file
659 | * @param {Number} line The line the type is located at. The first line is 1, not 0.
660 | * @param {String} type
661 | * @param {String} kind The kind of element. Either 'classlike', 'constant' or 'function'.
662 | *
663 | * @return {Promise}
664 | ###
665 | resolveType: (file, line, type, kind = 'classlike') ->
666 | if not file
667 | return new Promise (resolve, reject) ->
668 | reject('No file passed!')
669 |
670 | if not line
671 | return new Promise (resolve, reject) ->
672 | reject('No line passed!')
673 |
674 | if not type
675 | return new Promise (resolve, reject) ->
676 | reject('No type passed!')
677 |
678 | if not kind
679 | return new Promise (resolve, reject) ->
680 | reject('No kind passed!')
681 |
682 | if not @getIndexDatabasePath()?
683 | return new Promise (resolve, reject) ->
684 | reject('Request aborted as there is no project active (yet)')
685 |
686 | parameters = {
687 | database : @getIndexDatabasePath()
688 | file : file
689 | line : line
690 | type : type
691 | kind : kind
692 | }
693 |
694 | return @performRequest('resolveType', parameters)
695 |
696 | ###*
697 | * Localizes a type to the specified file, making it relative to local use statements, if possible. If not possible,
698 | * null is returned.
699 | *
700 | * @param {String} file
701 | * @param {Number} line The line the type is located at. The first line is 1, not 0.
702 | * @param {String} type
703 | * @param {String} kind The kind of element. Either 'classlike', 'constant' or 'function'.
704 | *
705 | * @return {Promise}
706 | ###
707 | localizeType: (file, line, type, kind = 'classlike') ->
708 | if not file
709 | return new Promise (resolve, reject) ->
710 | reject('No file passed!')
711 |
712 | if not line
713 | return new Promise (resolve, reject) ->
714 | reject('No line passed!')
715 |
716 | if not type
717 | return new Promise (resolve, reject) ->
718 | reject('No type passed!')
719 |
720 | if not kind
721 | return new Promise (resolve, reject) ->
722 | reject('No kind passed!')
723 |
724 | if not @getIndexDatabasePath()?
725 | return new Promise (resolve, reject) ->
726 | reject('Request aborted as there is no project active (yet)')
727 |
728 | parameters = {
729 | database : @getIndexDatabasePath()
730 | file : file
731 | line : line
732 | type : type
733 | kind : kind
734 | }
735 |
736 | return @performRequest('localizeType', parameters)
737 |
738 | ###*
739 | * Performs a semantic lint of the specified file.
740 | *
741 | * @param {String} file
742 | * @param {String|null} source The source code of the file to index. May be null if a directory is passed instead.
743 | * @param {Object} options Additional options to set. Boolean properties noUnknownClasses, noUnknownMembers,
744 | * noUnknownGlobalFunctions, noUnknownGlobalConstants, noDocblockCorrectness and
745 | * noUnusedUseStatements are supported.
746 | *
747 | * @return {Promise}
748 | ###
749 | semanticLint: (file, source, options = {}) ->
750 | if not file
751 | return new Promise (resolve, reject) ->
752 | reject('No file passed!')
753 |
754 | if not @getIndexDatabasePath()?
755 | return new Promise (resolve, reject) ->
756 | reject('Request aborted as there is no project active (yet)')
757 |
758 | parameters = {
759 | database : @getIndexDatabasePath()
760 | file : file
761 | stdin : true
762 | }
763 |
764 | if options.noUnknownClasses == true
765 | parameters['no-unknown-classes'] = true
766 |
767 | if options.noUnknownMembers == true
768 | parameters['no-unknown-members'] = true
769 |
770 | if options.noUnknownGlobalFunctions == true
771 | parameters['no-unknown-global-functions'] = true
772 |
773 | if options.noUnknownGlobalConstants == true
774 | parameters['no-unknown-global-constants'] = true
775 |
776 | if options.noDocblockCorrectness == true
777 | parameters['no-docblock-correctness'] = true
778 |
779 | if options.noUnusedUseStatements == true
780 | parameters['no-unused-use-statements'] = true
781 |
782 | return @performRequest('semanticLint', parameters, null, source)
783 |
784 | ###*
785 | * Fetches all available variables at a specific location.
786 | *
787 | * @param {String|null} file The path to the file to examine. May be null if the source parameter is passed.
788 | * @param {String|null} source The source code to search. May be null if a file is passed instead.
789 | * @param {Number} offset The character offset into the file to examine.
790 | *
791 | * @return {Promise}
792 | ###
793 | getAvailableVariables: (file, source, offset) ->
794 | if not file? and not source?
795 | return new Promise (resolve, reject) ->
796 | reject('Either a path to a file or source code must be passed!')
797 |
798 | if not @getIndexDatabasePath()?
799 | return new Promise (resolve, reject) ->
800 | reject('Request aborted as there is no project active (yet)')
801 |
802 | parameters = {
803 | database : @getIndexDatabasePath()
804 | offset : offset
805 | charoffset : true
806 | }
807 |
808 | if file?
809 | parameters.file = file
810 |
811 | return @performRequest('availableVariables', parameters, null, source)
812 |
813 | ###*
814 | * Deduces the resulting types of an expression.
815 | *
816 | * @param {String|null} expression The expression to deduce the type of, e.g. '$this->foo()'. If null, the
817 | * expression just before the specified offset will be used.
818 | * @param {String} file The path to the file to examine.
819 | * @param {String|null} source The source code to search. May be null if a file is passed instead.
820 | * @param {Number} offset The character offset into the file to examine.
821 | * @param {bool} ignoreLastElement Whether to remove the last element or not, this is useful when the user
822 | * is still writing code, e.g. "$this->foo()->b" would normally return the
823 | * type (class) of 'b', as it is the last element, but as the user is still
824 | * writing code, you may instead be interested in the type of 'foo()'
825 | * instead.
826 | *
827 | * @return {Promise}
828 | ###
829 | deduceTypes: (expression, file, source, offset, ignoreLastElement) ->
830 | if not file?
831 | return new Promise (resolve, reject) ->
832 | reject('A path to a file must be passed!')
833 |
834 | if not @getIndexDatabasePath()?
835 | return new Promise (resolve, reject) ->
836 | reject('Request aborted as there is no project active (yet)')
837 |
838 | parameters = {
839 | database : @getIndexDatabasePath()
840 | offset : offset
841 | charoffset : true
842 | }
843 |
844 | if file?
845 | parameters.file = file
846 |
847 | if ignoreLastElement
848 | parameters['ignore-last-element'] = true
849 |
850 | if expression?
851 | parameters.expression = expression
852 |
853 | return @performRequest('deduceTypes', parameters, null, source)
854 |
855 | ###*
856 | * Fetches invocation information of a method or function call.
857 | *
858 | * @param {String|null} file The path to the file to examine. May be null if the source parameter is passed.
859 | * @param {String|null} source The source code to search. May be null if a file is passed instead.
860 | * @param {Number} offset The character offset into the file to examine.
861 | *
862 | * @return {Promise}
863 | ###
864 | getInvocationInfo: (file, source, offset) ->
865 | if not file? and not source?
866 | return new Promise (resolve, reject) ->
867 | reject('Either a path to a file or source code must be passed!')
868 |
869 | if not @getIndexDatabasePath()?
870 | return new Promise (resolve, reject) ->
871 | reject('Request aborted as there is no project active (yet)')
872 |
873 | parameters = {
874 | database : @getIndexDatabasePath()
875 | offset : offset
876 | charoffset : true
877 | }
878 |
879 | if file?
880 | parameters.file = file
881 |
882 | return @performRequest('invocationInfo', parameters, null, source)
883 |
884 | ###*
885 | * Initializes a project.
886 | *
887 | * @return {Promise}
888 | ###
889 | initialize: () ->
890 | if not @getIndexDatabasePath()?
891 | return new Promise (resolve, reject) ->
892 | reject('Request aborted as there is no project active (yet)')
893 |
894 | parameters = {
895 | database : @getIndexDatabasePath()
896 | }
897 |
898 | return @performRequest('initialize', parameters, null, null)
899 |
900 | ###*
901 | * Vacuums a project, cleaning up the index database (e.g. pruning files that no longer exist).
902 | *
903 | * @return {Promise}
904 | ###
905 | vacuum: () ->
906 | if not @getIndexDatabasePath()?
907 | return new Promise (resolve, reject) ->
908 | reject('Request aborted as there is no project active (yet)')
909 |
910 | parameters = {
911 | database : @getIndexDatabasePath()
912 | }
913 |
914 | return @performRequest('vacuum', parameters, null, null)
915 |
916 | ###*
917 | * Tests a project, to see if it is in a properly usable state.
918 | *
919 | * @return {Promise}
920 | ###
921 | test: () ->
922 | if not @getIndexDatabasePath()?
923 | return new Promise (resolve, reject) ->
924 | reject('Request aborted as there is no project active (yet)')
925 |
926 | parameters = {
927 | database : @getIndexDatabasePath()
928 | }
929 |
930 | return @performRequest('test', parameters, null, null)
931 |
932 | ###*
933 | * Refreshes the specified file or folder. This method is asynchronous and will return immediately.
934 | *
935 | * @param {String|Array} path The full path to the file or folder to refresh. Alternatively,
936 | * this can be a list of items to index at the same time.
937 | * @param {String|null} source The source code of the file to index. May be null if a directory is
938 | * passed instead.
939 | * @param {Callback|null} progressStreamCallback A method to invoke each time progress streaming data is received.
940 | * @param {Array} excludedPaths A list of paths to exclude from indexing.
941 | * @param {Array} fileExtensionsToIndex A list of file extensions (without leading dot) to index.
942 | *
943 | * @return {Promise}
944 | ###
945 | reindex: (path, source, progressStreamCallback, excludedPaths, fileExtensionsToIndex) ->
946 | if typeof path == "string"
947 | pathsToIndex = []
948 |
949 | if path
950 | pathsToIndex.push(path)
951 |
952 | else
953 | pathsToIndex = path
954 |
955 | if path.length == 0
956 | return new Promise (resolve, reject) ->
957 | reject('No filename passed!')
958 |
959 | if not @getIndexDatabasePath()?
960 | return new Promise (resolve, reject) ->
961 | reject('Request aborted as there is no project active (yet)')
962 |
963 | progressStreamCallbackWrapper = null
964 |
965 | parameters = {
966 | database : @getIndexDatabasePath()
967 | }
968 |
969 | if progressStreamCallback?
970 | parameters['stream-progress'] = true
971 |
972 | progressStreamCallbackWrapper = progressStreamCallback
973 |
974 | parameters.source = pathsToIndex
975 | parameters.exclude = excludedPaths
976 | parameters.extension = fileExtensionsToIndex
977 |
978 | return @performRequest('reindex', parameters, progressStreamCallbackWrapper, source)
979 |
980 | ###*
981 | * Sets the name (without path or extension) of the database file to use.
982 | *
983 | * @param {String} name
984 | ###
985 | setIndexDatabaseName: (name) ->
986 | @indexDatabaseName = name
987 |
988 | ###*
989 | * Retrieves the full path to the database file to use.
990 | *
991 | * @return {String}
992 | ###
993 | getIndexDatabasePath: () ->
994 | return @config.get('packagePath') + '/indexes/' + @indexDatabaseName + '.sqlite'
995 |
996 | ###*
997 | * @param {String} corePath
998 | ###
999 | setCorePath: (corePath) ->
1000 | @corePath = corePath
1001 |
1002 | ###*
1003 | * @return {Boolean}
1004 | ###
1005 | getIsActive: (isActive) ->
1006 | return @isActive
1007 |
1008 | ###*
1009 | * @param {Boolean} isActive
1010 | ###
1011 | setIsActive: (isActive) ->
1012 | @isActive = isActive
1013 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### Note
2 | Starting with version **2.0.0**, this repository only contains the CoffeeScript or _client_ side (for Atom) of the indexer. Most of the interesting changes are happening on the PHP or _server_ side. You can view its changelog [here](https://gitlab.com/php-integrator/core/blob/master/CHANGELOG.md) for the master branch or [here](https://gitlab.com/php-integrator/core/blob/development/CHANGELOG.md) for the development branch.
3 |
4 | ## 2.1.12
5 | * Update to core [2.1.6](https://gitlab.com/php-integrator/core/tags/2.1.7).
6 |
7 | ## 2.1.11
8 | * Update to core [2.1.6](https://gitlab.com/php-integrator/core/tags/2.1.6).
9 |
10 | ## 2.1.10
11 | * Update to core [2.1.5](https://gitlab.com/php-integrator/core/tags/2.1.5).
12 |
13 | ## 2.1.9
14 | * Nothing changed, `apm` just failed to publish 2.1.8 and decided to bump the version, even though I explicitly asked it to try and publish 2.1.8 again.
15 |
16 | ## 2.1.8
17 | * Update to core [2.1.4](https://gitlab.com/php-integrator/core/tags/2.1.4).
18 |
19 | ## 2.1.7
20 | * Update to core [2.1.3](https://gitlab.com/php-integrator/core/tags/2.1.3).
21 |
22 | ## 2.1.6
23 | * Update to core [2.1.2](https://gitlab.com/php-integrator/core/tags/2.1.2).
24 | * Socket closes and reconnections will no longer display errors to the user. See also 46fc09f9b072a601dd6f6b02ee753a9785bfa397.
25 |
26 | ## 2.1.5
27 | * When the socket connection closes and a reconnect happens, the state of response reading is now reset.
28 | * Previously, the base package kept accumulating response data in a buffer, never seeing the end of the one that was interrupted.
29 |
30 | ## 2.1.4
31 | * Update to core [2.1.1](https://gitlab.com/php-integrator/core/tags/2.1.1).
32 | * Composer output will no longer be silenced when installing the core.
33 |
34 | ## 2.1.3
35 | * Fix incorrect method call that only occurred on shutdown or when deactivating the package.
36 | * All open requests will now be rejected whenever the socket is unexpectedly closed.
37 | * This fixes the issue where some requests would hang up because the promise they received was never resolved or rejected.
38 | * This should fix the issue where indexing gets stuck (and blocks further indexing events) whenever the socket reconnects.
39 |
40 | ## 2.1.2
41 | * Sockets closing without an error code will now warn instead of throw an error.
42 | * Socket connections that _do_ have an error code will still show an error message.
43 | * An attempt to automatically reestablish the socket connections will always be made.
44 | * In the case that the server crashed, an error will still also be printed in the developer tools, to distinguish between seemingly "magic self-closing" and actual server crashes.
45 |
46 | ## 2.1.1
47 | * Fix another issue where the server wasn't always properly restarted after it unexpectedly stopped.
48 | * Explicitly close open socket connections when the server dies as a safety measure (as the port is reused when the server is spawned again).
49 |
50 | ## 2.1.0
51 | * Update to core [2.1.0](https://gitlab.com/php-integrator/core/tags/2.1.0).
52 | * The server wasn't always being restarted automatically when it died or the connection failed. This should be fixed now.
53 | * The core is now downloaded using Composer instead of apm, which is more robust and allows properly selecting the right version to download.
54 |
55 | ## 2.0.2
56 | * Update to core [2.0.2](https://gitlab.com/php-integrator/core/tags/2.0.2).
57 |
58 | ## 2.0.1
59 | * Update to core [2.0.1](https://gitlab.com/php-integrator/core/tags/2.0.1).
60 |
61 | ## 2.0.0
62 | ### Features and enhancements
63 | * The PHP side is no longer part of the base package. Instead, it has been separated into the [php-integrator/core](https://gitlab.com/php-integrator/core) repository so it can be more easily installed via Composer for use in other projects.
64 | * This change should not impact users as they upgrade, as this package will be automatically installed and upgraded along with this one if necessary (a notification will be shown whenever that happens).
65 | * Communication with the core now happens via sockets. This means only a single process is spawned on startup and kept active throughout the lifetime of Atom.
66 | * This should reduce latency across the board as process spawning is rather expensive in comparison. It will also ensure there is never more than one process active when multiple requests are fired simultaneously (they will simply be queued and handled one by one, which will not freeze up Atom as communication is completely asynchronous).
67 | * It is now also possible to specify an additional indexing delay via the settings screen.
68 | * It's currently set to `200 ms` by default. As Atom's default delay before invoking an event after an editor stopped changing is about `300 ms`, this results in indexing happening after `500 ms` by default. Increasing this will reduce the load of constant reindexing happening, but will also make results from autocompletion and linting less current.
69 | * Error messages will now be shown if setting up the current project fails because there is no active project or the project manager service is not available.
70 | * Caching has been improved, improving performance and responsiveness.
71 | * Previously, similar queries to the PHP side that were happening closely in succession did not hit the cache because the promise of the similar query had not resolved yet. In this case, two promises were resolved, fetching the same information.
72 | * A memory limit for the PHP process is now configurable in the package settings.
73 |
74 | ### Bugs fixed
75 | * The status bar was not showing progress when a project index happened through a repository status change.
76 | * Popovers will no longer go beyond the left or top part of the screen. They will move respectively right or down in that case.
77 |
78 | ### Changes for developers (see also the core)
79 | * `getVariableTypes` has been removed as it was deprecated and just an alias for `deduceTypes`.
80 | * The `truncate` call was removed as you can now simply call `initialize` again to reinitialize a project.
81 | * The `deduceTypes` call now expects the (optional) entire expression to be passed rather than just its parts.
82 | * The `reindex` call no longer automatically indexes built-in structural elements, nor will it automatically prune removed files from the database.
83 | * A new call, `vacuum`, can be used to vacuum a project and prune removed files from its index.
84 | * A new call, `initialize`, can be used to initialize a project and index built-in structural elements.
85 | * The `reindex` call no longer takes a `progressStreamCallback`, this was necessary because it was holding back refactoring and it really did not belong there.
86 | * It also wasn't really useful in its current form, as the only the one doing the indexing could register a callback.
87 | * As a better alternative, you can now register a callback with `onDidIndexingProgress` to listen to the progress, even if you did not spawn the reindex yourself.
88 | * A new call, `onDidStartIndexing`, has been added that allows you to listen to an indexing action starting.
89 | * A new service method, `getCurrentProjectSettings`, allows retrieving the settings (specific to this package) of a project. This includes things such as the PHP version, paths and excluded folders.
90 | * A new service method `getUseStatementHelper`, can be used to retrieve an object that handles adding use statements and sorting them. This has been imported from the autocompletion package so it can be reused in other packages as well (and bugfixes are centralized).
91 | * A couple of new service methods have been added to allow fetching namespace information:
92 | * `getNamespaceList`
93 | * `getNamespaceListForFile`
94 | * `determineCurrentNamespaceName`
95 | * A couple of new service methods have been added to allow fetching documentation URL's for built-in structural elements. These were imported from php-integrator-navigation so they can be reused by other packages as well:
96 | * `getDocumentationUrlForClass`
97 | * `getDocumentationUrlForFunction`
98 | * `getDocumentationUrlForClassMethod`
99 | * The `resolveType` and `localizeType` commands now have a `kind` parameter that indicates what type of element te resolve. This is required as duplicate use statements may exist in PHP as long as the 'kind' is different (i.e. a `use const A\FOO` may exist alongside a `use A\FOO`).
100 | * Built-in interfaces no longer have `isAbstract` set to true. They _are_ abstract in a certain sense, but this property is meant to indicate if a classlike has been defined using the abstract keyword. It was also not consistent with the behavior for non-built-in interfaces.
101 | * Proxy methods will no longer throw exceptions if some parameters are missing or invalid. Instead, a promise rejection will occur.
102 |
103 | ## 1.2.6
104 | ### Bugs fixed
105 | * Rename the package and repository.
106 |
107 | ## 1.2.5
108 | ### Bugs fixed
109 | * Thanks to a quick response by [danielbrodin](https://github.com/danielbrodin), all functionality related to project management should now once again be working.
110 |
111 | ## 1.2.4
112 | ### Bugs fixed
113 | * Add workarounds for project-manager 3.0.0. Unfortunately, some functionalities are currently broken because of the backwards-incompatible upgrade. See also [this ticket](https://github.com/danielbrodin/atom-project-manager/issues/252) for more information.
114 | * Setting up new projects is currently impossible via the package, you can however still manually set up projects by adding PHP settings to their root object via the `projects.cson` file:
115 | ```
116 | php:
117 | enabled: true
118 | php_integrator:
119 | enabled: true
120 | phpVersion: 5.6
121 | excludedPaths: []
122 | fileExtensions: [
123 | "php"
124 | ]
125 | ```
126 | >>>>>>> master
127 |
128 | ## 1.2.3
129 | ### Bugs fixed
130 | * Fix built-in classes with FQCN's with more than one part, such as from the MongoDB extension, not properly being indexed.
131 |
132 | ## 1.2.2
133 | ### Bugs fixed
134 | * Fixed an error related to `JSON_PRESERVE_ZERO_FRACTION`, which needs PHP >= 5.6.
135 |
136 | ## 1.2.1
137 | ### Bugs fixed
138 | * Fixed `exclude` warnings (thanks to [@UziTech](https://github.com/UziTech)).
139 |
140 | ## 1.2.0
141 | ### Features and enhancements
142 | * Project support has (finally) arrived in a basic form. Unfortunately this change **needs user intervention** and will possibly break the workflow of some users:
143 | 1. Install the [atom-project-manager](https://github.com/danielbrodin/atom-project-manager) package [1].
144 | 2. Ensure that your projects are saved using `atom-project-manager`.
145 | 3. Click `Packages → PHP Integrator → Set Up Current Project` (or use the command palette).
146 | * Built-in functions and methods will now have (mostly) proper documentation and return types.
147 | * This is achieved by keeping an internal list of stubs of the online PHP documentation fetched using [php-doc-parser](https://github.com/martinsik/php-doc-parser).
148 | * Note that this will never be perfect as, even though the online documentation is much more complete than reflection, it is also not always entirely correct. For example, `DateTime::createFromFormat` can actually return `DateTime|false`, but accordiing to its online documentation signature it always returns a `DateTime`).
149 | * Regardless of the previous item, it will still remain a major improvement over the old reflection-only approach. Various built-in functions and methods will now properly show documentation and have proper return types, easing the pain of having to work with PHP's built-in functionality.
150 | * Project and folder indexing performance has improved.
151 | * `define()` statements will now be indexed much like global constants.
152 | * Specifying the file extensions to index is now supported via project settings.
153 | * Excluding directories from indexing is now supported via project settings, see also [the wiki](https://github.com/Gert-dev/php-integrator-base/wiki/Excluding-Folders-From-Indexing) for more information.
154 | * For those interested, the wiki now [has an article](https://github.com/Gert-dev/php-integrator-base/wiki/Proper-Documentation-And-Type-Hinting) with information about how analysis of your code happens regarding docblocks and type hinting. Reading it may help you improve your code as well as code assistance from this package.
155 | * The remaining parts of the analyzer that were implemented in CoffeeScript have been moved to PHP. This means the base package is now only reliant on PHP itself for processing. This may positively affect performance, but more importantly allows extracting and using the analyzer in its entirety outside Atom as well (i.e. for other editors or projects).
156 | * Previously, ternary expressions could only be properly analyzed if they had the same return type:
157 |
158 | ```php
159 | $a1 = new A();
160 | $a2 = new A();
161 |
162 | $a3 = some_condition() ? $a1 : $a2;
163 |
164 | // $a3 is now an 'A' because both conditions yield the same type.
165 | ```
166 |
167 | This restriction has now been lifted. Using ternary operators with conditions resulting in different types will now simply yield multiple return types:
168 |
169 | ```php
170 | $a = new A();
171 | $b = new B();
172 |
173 | $c = some_condition() ? $a : $b;
174 |
175 | // $c is now of type A|B.
176 | ```
177 |
178 | * The default value for classlike properties and constants is now used for type deduction. This will improve type guessing in several situations. For example, when generating a docblock for `$foo`:
179 |
180 | ```php
181 | // Before:
182 |
183 | /**
184 | * @var mixed
185 | */
186 | protected $foo = 'test';
187 |
188 | // After:
189 |
190 | /**
191 | * @var string
192 | */
193 | protected $foo = 'test';
194 |
195 | ```
196 |
197 | ### Bugs fixed
198 | * The return type of global functions was being ignored if they had multiple return types.
199 | * In rare cases, caching would complain that it could not create the `accessing_shared_cache.lock` file.
200 | * When force indexing, the cache was not properly invalidated, sometimes leading to the wrong data being fetched.
201 | * Retrieving locally available variables wasn't always using the most up to date version of the editor's contents.
202 | * Fix an empty error message and notification sometimes being shown in Atom because the PHP side was incorrectly parsing docblocks containing Unicode characters.
203 | * When retrieving available variables or deducing types, character offsets were not correctly being translated to byte offsets, sometimes leading to incorrect results.
204 |
205 | ### Changes for developers
206 | * `getInvocationInfo` is now also available separately as PHP command.
207 | * Constants and properties will now also return their default values, if present.
208 | * `determineCurrentClassName` was not causing a promise rejection when fetching the class list failed internally.
209 | * A new command `truncate` now does what a force reindex does, but also ensures the cache is properly invalidated.
210 | * `getInvocationInfoAt` will now return an `offset` rather than a `bufferPosition` to be consistent with the other commands.
211 | * `getResultingTypesAt` is now simply a convenience call to `deduceTypes` as its underlying code has been completely moved to PHP.
212 | * `deduceTypes` gained a new parameter `ignoreLastElement`, which does the same as the identically-named parameter for `getResultingTypesAt` does.
213 |
214 | ### Notes
215 | [1] Why `atom-project-manager`? The truth is that I would have preferred not to select any specific third-party package for project support so users can use the package they prefer. Users familiar with the project discussions will remember that I have long postponed this change for this reason. Unfortunately, as nothing seems to change and project support is feeling more and more like a missing feature, I decided to go ahead and link to the most popular project management package currently out there. `atom-project-manager` also supports other generic project settings in its CSON file, which leaves room for other Atom packages to also save settings to the same file in harmony with this one. In the future, someone could even develop a GUI for managing project settings.
216 |
217 | ## 1.1.2
218 | ### Bugs fixed
219 | * Fixed grouped use statements never being seen as unused.
220 | * Fixed grouped use statements being marked as unknown classes.
221 | * Fixed grouped use statements being ignored for type resolution when indexing.
222 | * Files that do not have UTF-8 encoding will be converted before they are indexed.
223 |
224 | ## 1.1.1
225 | ### Bugs fixed
226 | * Fixed the various `codeCoverageIgnore` tags from PHPUnit being seen as invalid tags (thanks to [@hultberg](https://github.com/hultberg)).
227 |
228 | ## 1.1.0
229 | ### Features and enhancements
230 | * Caching performance has been improved.
231 | * Added simple support for multiple root folders in the tree view.
232 | * At least PHP 5.5 is now required to run the service. PHP 5.4 has been declared end of life for quite some time now and 5.5 will be declared end of life 10 July 2016. This does not affect the code you can actually write, the indexer still supports PHP 5.2 up to PHP 7.0, it is just the PHP interpreter running the indexer that had a required version bump.
233 | * Some commands delegate work to other commands, but each command that requires parsing performed its own parsing of the (same) source code, even though it only needs to happen once. This unnecessary overhead has been removed, resulting in performance improvements across the board.
234 | * The strictness on `instanceof` has been lifted. The variable type deducer is now able to parse somewhat more complex if statements:
235 |
236 | ```php
237 | if ((1 ^ 0) && true && $b instanceof B && ($test || false && true)) {
238 | // $b will now be recognized as an instance of B.
239 | }
240 | ```
241 |
242 | * If-statements containing variable handling functions such as `is_string`, `is_bool` will now influence type deduction:
243 |
244 | ```php
245 | if (is_string($b) || is_array($b)) {
246 | // $b is now of type string|array.
247 | }
248 | ```
249 |
250 | ### Bugs fixed
251 | * The PHP Mess Detector `@SuppressWarnings` docblock tag will no longer be linted as unknown tag.
252 | * Different projects weren't using different caches. This means that in some cases the wrong cache was being used.
253 | * In some cases, the internal cache wasn't cleared when a class was modified, which resulted in old data being displayed.
254 | * Text following the `@var` tag in docblocks for class constants will now serve as short descriptions (summaries), similar to class properties.
255 | * Some internal PHP classes, such as `COM` have inconsistent naming (i.e. `COM` is actually returned as being named `com`). These are now corrected during indexing so you can use the names from the documentation. (For PHP this isn't a problem as it is mostly case insensitive, but we are.)
256 | * *Caching has been reenabled on Windows*, a fix has been applied that should refrain errors from popping up. The cache will simply reset itself if it runs into the erroneous condition (the reason behind which, up this date, is still unknown to me). This way, users are still able to enjoy some caching (users that did not experience any problems at all previously will be able to enjoy full caching).
257 |
258 | ### Changes for developers
259 | * Builtin functions did not have a FQCN set.
260 | * Added `deduceTypesAt` as a convenience alias.
261 | * The global function and constant list will now return a mapping of FQCN's to data (instead of names to data).
262 | * `semanticLint` learned how to validate unknown class members, global functions and global constants, which can be used by linter packages.
263 | * The path passed to handlers registered using `onDidFinishIndexing` and `onDidFailIndexing` will now be an array for project indexes (but not file indexes) as they can contain multiple root folders.
264 | * `getVariableTypes` is now deprecated as it is just an alias for calling `deduceTypes` and `deduceTypes` with the variable name as the sole part. It will now also just proxy calls to deduceTypes internally.
265 |
266 | ## 1.0.10
267 | ### Bugs fixed
268 | * Cleaned up the reindexing process. The locks that were causing so much trouble have been removed for now.
269 | * It was originally added as multiple concurrent indexing processes locked the database to ensure the other processes wait their turn. However, testing this again without it seems to indicate SQLite (automatically) gracefully waits for the transaction to finish. Either they accidentally solved the original problem, or the original problem might only manifest in certain circumstances. If the problem reappears anyway, I will investigate alternative solutions.
270 |
271 | ## 1.0.9
272 | ### Bugs fixed
273 | * An error is now returned if a file is not in UTF-8 encoding.
274 | * The encoding is now explicitly set to UTF-8, in case the encoding in your php.ini is set to something else.
275 |
276 | ## 1.0.8
277 | ### Bugs fixed
278 | * Fixed the database file never getting unlocked if indexing failed.
279 |
280 | ## 1.0.7
281 | ### Bugs fixed
282 | * Fixed the `setCachePrefix` error on Windows.
283 |
284 | ## 1.0.6
285 | ### Bugs fixed
286 | * Fixed docblock correctness linting going haywire when there were unicode characters inside the docblock.
287 | * The caching fix from 1.0.5 was rolled back because it didn't work. Caching has been disabled on Windows until a solution can be found.
288 | * Fixed words after an `@` symbol in docblocks incorrectly being marked as unknown annotation class (e.g. `@author Someone `).
289 |
290 | ## 1.0.5
291 | ### Bugs fixed
292 | * Attempt to fix the cache permission denied issue on Windows by using a different caching directory.
293 |
294 | ## 1.0.4
295 | ### Bugs fixed
296 | * Byte and character offsets were being mixed up. The expected formats have been documented in the README:
297 | * Output offsets retrieved from the service (and PHP side) have always been byte offsets, and this will also remain the same. To deal with the conversion, a new service method, `getCharacterOffsetFromByteOffset`, has been added.
298 | * The PHP side expects byte offsets, but commands that need offsets also gained an option `charoffset` to switch to pass character offsets instead. The CoffeeScript side has always expected character offsets as input because Atom mainly works with these, and this will remain the same.
299 | * In short, nothing has changed for dependent packages, but packages that use byte offsets incorrectly as character offsets may want to use the new service method to perform the conversion.
300 |
301 | ## 1.0.3
302 | ### Bugs fixed
303 | * Fixed namespaces without a name causing an error when resolving types.
304 |
305 | ## 1.0.2
306 | ### Bugs fixed
307 | * Fixed a circular dependency exception being thrown if a classlike implemented the same interface twice via separate paths.
308 | * If you have the same FQCN twice in your code base, only one of them will be analyzed when examining classlikes for associations (parents, traits, interfaces, ...). Having two classes with the same FQCN is actually an error, but it can happen if you store files that aren't actually directly part of your code inside your project folder. A better solution for this is to exclude those folders from indexing, which will be possible as soon as project support is implemented. Until that happens, this should mitigate the circular dependency exception that ensued because two classlikes with the same name were examined.
309 |
310 | ## 1.0.1
311 | ### Bugs fixed
312 | * Fixed error regarding `$type` when not using PHP 7 (thanks to [@UziTech](https://github.com/UziTech)).
313 |
314 | ## 1.0.0
315 | ### Features and enhancements
316 | * Minor performance improvements when calculating the global class list.
317 | * Annotation tags in docblocks will now be subject to unknown class examination.
318 | * A caching layer was introduced that will drastically cut down on time needed to calculate information about structures. This information is used almost everywhere throughout the code so an increase in responsiveness should be noticable across the board. This will especially be the case in complex classes that have many dependencies. The cache files will be stored in your system's temporary folder so they will get recycled automatically when necessary.
319 | * Type deduction learned how to deal with ternary expressions where both operands have the same resulting type:
320 |
321 | ```php
322 | $a1 = new A();
323 | $a2 = new A();
324 |
325 | $a = true ? $a1 : $a2;
326 |
327 | $a-> // Did not work before. Will now autocomplete A, as the type is guaranteed.
328 |
329 | $b1 = new B();
330 | $b2 = new \B();
331 |
332 | $b = $b1 ?: $b2;
333 |
334 | $b-> // Did not work before. Will now autocomplete B, for the same reasons.
335 | ```
336 |
337 | * Type deduction learned how to deal with ternary expressions containing instanceof:
338 |
339 | ```php
340 | $a = ($foo instanceof Foo) ? $foo-> // Will now autocomplete Foo.
341 | ```
342 |
343 | ### Bugs fixed
344 | * The type of global built-in function parameters was not getting analyzed correctly.
345 | * Built-in functions that have invalid UTF-8 characters in their name are now ignored.
346 | * Fixed docblock tags containing an @ sign in their description being incorrectly parsed.
347 | * Docblock types did not always get precedence over type hints of function or method parameters.
348 | * Use statements are no longer marked as unused if they are used inside docblocks as annotation.
349 | * Parameters that have the type `self` (in docblock or type hint), `static` or `$this` (in docblock) will now correctly be examined.
350 | * Don't freeze up until the call stack overflows when classes try to implement themselves (as interface) or use themselves (as trait).
351 | * Foreach statements that contain a list() expression as value will no longer cause an error (e.g. `foreach ($array as list($a, $b)) { ... }`).
352 | * Type localization didn't work properly for classlikes that were in the same namespace as the active one. For example, in namespace `A`, type `\A\B` would be localized to `A\B` instead of just `B`.
353 | * Fixed a classlike suddenly no longer being found if you defined another classlike with the same FQCN in another file (i.e. after copying a file). This happened, most annoyingly, even if you then changed the FQCN in the copied file, and couldn't be fixed without reindexing the original file.
354 | * Static method calls where the class name had a leading slash were not being examined correctly:
355 |
356 | ```php
357 | $foo = \Foo\Bar::method();
358 | $foo-> // Didn't work, should work now.
359 | ```
360 |
361 | * Type overrides with multiple types were not analyzed properly:
362 |
363 | ```php
364 | /** @var Foo|null $foo */
365 | $foo-> // Did not work, should work now.
366 | ```
367 |
368 | * Fix call stacks not correctly being retrieved after the new keyword, for example:
369 |
370 | ```php
371 | $test = new $this-> // Was seen as "new $this" rather than just "$this".
372 | ```
373 |
374 | * Fix overridden or implemented methods with different parameter lists losing information about their changed parameter list. Whenever such a method now specifies different parameters than the "parent" method, parameters are no longer inherited and a docblock should be specified to document them.
375 |
376 | ```php
377 | class A
378 | {
379 | /**
380 | * @param Foo $foo
381 | */
382 | public function __construct($foo) { ... }
383 | }
384 |
385 | class B extends A
386 | {
387 | // Previously, these two parameters would get overwritten and the parameter list would be
388 | // just "$foo" as the parent documentation is inherited.
389 | public function __construct($bar, $test) { ... }
390 | }
391 | ```
392 |
393 | * Conditionals with instanceof in them will now no longer be examined outside their scope:
394 |
395 | ```php
396 | if ($foo instanceof Foo) {
397 | $foo-> // Autocompletion for Foo as before.
398 | }
399 |
400 | $foo-> // Worked before, now no longer works.
401 | ```
402 |
403 | * More elaborate inline docblocks with type annotations will now also be parsed:
404 |
405 | ```php
406 | /**
407 | * Some documentation.
408 | *
409 | * @var D $d A description.
410 | */
411 | $d = 5;
412 | $d-> // Didn't work because a docblock spanning multiple lines was not examined, should work now.
413 | ```
414 |
415 | * Compound class property statements now properly have their docblocks examined:
416 |
417 | ```php
418 | /**
419 | * This is the summary.
420 | *
421 | * This is a long description.
422 | *
423 | * @var Foo1 $testProperty1 A description of the first property.
424 | * @var Foo2 $testProperty2 A description of the second property.
425 | */
426 | protected $testProperty1, $testProperty2;
427 | ```
428 |
429 | * In property docblocks with ambiguous summary and `@var` descriptions, the more specific `@var` will now take precedence. This more neatly fits in with the compound statements described above:
430 |
431 | ```php
432 | /**
433 | * This is the docblock summary, it will not be used or available anywhere as it is overridden below.
434 | *
435 | * This is a long description. It will be used as long description for all of the properties described below.
436 | *
437 | * @var Foo1 $testProperty1 A description of the first property. This will be used as the summary of this property.
438 | */
439 | protected $testProperty1;
440 | ```
441 |
442 | ### Changes for developers
443 | * All structural elements that involve types will now return arrays of type objects instead of a single type object. The following methods have been renamed to reflect this change:
444 | * `deduceType` -> `deduceTypes`.
445 | * `getVariableType` -> `getVariableTypes`.
446 | * `getResultingTypeAt` -> `getResultingTypesAt`.
447 | * `getVariableTypeByOffset` -> `getVariableTypesByOffset`.
448 | * An `isFinal` property is now returned for classes and methods.
449 | * An `isNullable` property is now returned for function and method parameters.
450 | * A `defaultValue` property is now returned for function and method parameters with the default value in string format.
451 | * `resolveType` will now also return types with a leading slash, consistent with other commands.
452 | * Return types for functions and types for function parameters will now also include a `typeHint` property that is set to the actual type hint that was specified (the type and resolvedType fall back to the docblock if it is present).
453 | * `localizeType` will now return the FQCN instead of null if it couldn't localize a type based on use statements. The reasoning behind this is that you want a localized version of the type you pass, if there are no use statements to localize it, the FQCN is the only way to address the type locally.
454 | * The format the descriptions are returned in has changed; there is no more `descriptions` property that has a `short` and `long` property. Instead, there is now the `shortDescription` and `longDescription` property. Similarly, the description of the return value of functions and methods has moved to `returnDescription` and the description of the type of properties and constants to `typeDescription`.
455 |
456 | ## 0.9.5
457 | ### Bugs fixed
458 | * Fixed the database handle being opened in an incorrect way.
459 |
460 | ## 0.9.4
461 | ### Bugs fixed
462 | * Fixed the database handle never being closed after it was opened.
463 |
464 | ## 0.9.3
465 | ### Bugs fixed
466 | * Fixed variables nclosure use statements not having their type resolved properly in some corner cases.
467 |
468 | ## 0.9.2
469 | ### Bugs fixed
470 | * (Attempt to) fix indexing for built-in classes returning malformed method parameters.
471 |
472 | ## 0.9.1
473 | ### Bugs fixed
474 | * Fixed variables used in closure use statements not having their type resolved from the parent scope, for example:
475 |
476 | ```php
477 | $a = new B();
478 |
479 | $closure = new function () use ($a) {
480 | $a-> // Now correctly resolves to 'B' instead of not working at all.
481 | };
482 | ```
483 |
484 | ## 0.9.0
485 | ### Features and enhancements
486 | * An error will now be shown if your SQLite version is out of date.
487 | * Unknown classes in docblocks will now actually be underlined instead of the structural element they were part of.
488 | * Indexing performance has been improved, especially the scanning phase (before the progress bar actually started filling) has been improved.
489 | * Indexing is now more fault-tolerant: in some cases, indexing will still be able to complete even if there are syntax errors in the file.
490 | * Docblock types now take precedence over type hints. The reason for this is that docblock types are optional and they can be more specific. Take for example, the fluent interface for setters, PHP does not allow specifying `static` or `$this` as a return type using scalar type hinting, but you may still want to automatically resolve to child classes when the setter is inherited:
491 |
492 | ```php
493 | /**
494 | * @return static
495 | */
496 | public function setFoo(string $foo): self
497 | {
498 |
499 | }
500 | ```
501 |
502 | ### Bugs fixed
503 | * The return type of PHP 7 methods was not properly used as fallback.
504 | * If you (incorrectly) declare or define the same member twice in a class, one of them will now no longer be picked up as an override.
505 | * The `@inheritDoc` syntax without curly braces wasn't always correctly being handled, resulting in docblocks containing only them still being treated as actual docblocks that weren't inherited.
506 |
507 | ### Changes for developers
508 | * Semantic linting can now lint docblock correctness.
509 | * Semantic linting will now also return syntax errors.
510 | * Support for calling most methods synchronously has been removed.
511 | * Classes didn't return information about whether they have a docblock or documentation.
512 | * When fetching class information, types were sometimes returned without their leading slash.
513 | * Because of semantic linting now supporting syntax errors, the reindex command will no longer return them.
514 | * Global constants and functions will now also return an FQCN so you can deduce in what namespace they are located.
515 | * When returning types such as `string[]`, the `fullType` was still trying to resolve the type as if it were a class type.
516 | * The reindex command did not return false when indexing failed and the promise was, by consequence, not rejected.
517 | * Added a new command `localizeType` to localize FQCN's based on use statements, turning them back into relative class names.
518 | * The `semanticLint` method now takes an `options` object that allows you to disable certain parts of the linting process.
519 | * Classes will now return information about whether they are usable as annotation or not (determined by an `@Annotation` tag in their docblock). This is non-standard, but is becoming more and more commonly used in the PHP world (e.g. Symfony and Doctrine).
520 |
521 | ## 0.8.2
522 | ### Bugs fixed
523 | * Circular dependencies should no longer freeze but show an error notification instead.
524 | * Fixed the argument index (used by the call tips package) not being correct for function calls containing SQL strings.
525 |
526 | ## 0.8.1
527 | ### Bugs fixed
528 | * Fixed infinite loop occurring when assigning variables to an expression containing themselves.
529 |
530 | ## 0.8.0
531 | ### Features and enhancements
532 | * Some internal logic has been rewritten to support working asynchronously. The existing list of packages have already been adjusted to make use of this change, which will improve apparant responsiveness across the board.
533 | * Memory-mapped I/O is now enabled on the SQLite back end on systems that support it. This can drastically improve performance and latency of some queries. (In a local test with a large codebase, this literally halved a large class information fetch from 250 milliseconds down to 125 milliseconds).
534 | * A project index will now be triggered when the repository (if present) changes statusses. This isn't as aggressive as a file monitor, but will at least remove the annoyance of having to manually rescan when checking out a different branch to avoid incorrect data being served. Events that will trigger this involve:
535 | * Doing a checkout to switch to a different branch.
536 | * Modifying a file from the project tree externally and then coming back to Atom.
537 |
538 | ### Bugs fixed
539 | * Fixed PHP 7 anonymous classes still being parsed.
540 | * Fixed a rare error relating to an "undefined progressStreamCallback".
541 | * Fixed arrays of class names (e.g. `Foo[]`) in docblocks not being semantically linted.
542 | * Fixed `getInvocationInfoAt` incorrectly walking past control keywords such as `elseif`.
543 |
544 | ### Changes for developers
545 | * Almost all service methods now have an async parameter. It is recommended that you always use this functionality as it will ensure the editor remains responsive for the end user. In a future release, support for synchronous calls **will _probably_ be removed**.
546 | * A new method `deduceType` has been added.
547 | * A new method `getVariableTypeByOffset` has been added.
548 | * `getVariableType`, `getResultingTypeAt`, `resolveTypeAt` and `getInvocationInfoAt` have received an additional parameter, `async`, that will make them (mostly) asynchronous.
549 | * `getVariableType` and `getResultingTypeAt` have been rewritten in PHP. Class types returned by these methods will now _always_ be absolute and _always_ include a leading slash. Previously the returned type was _sometimes_ relative to the current file and _sometimes_ absolute. To make things worse, absolute types _sometimes_ contained a leading slash, leading to confusion. (Scalar types will still not include a leading slash.)
550 | * A new property `hasDocumentation` is now returned for items already having the `hasDocblock` property. The latter will still return false if the docblock is inherited from the parent, but the former will return true in that case.
551 | * The following methods have been removed, they were barely used and just provided very thin wrappers over existing functionality:
552 | * `getClassMember`
553 | * `getClassMemberAt`
554 | * `getClassConstant`
555 | * `getClassConstantAt`
556 | * `getClassMethod`
557 | * `getClassMethodAt`
558 | * `getClassProperty`
559 | * `getClassPropertyAt`
560 | * `getClassSelectorFromEvent` (didn't really belong in the base package).
561 |
562 | ## 0.7.2
563 | ### Bugs fixed
564 | * Fixed minor error where the service version was bumped incorrectly.
565 |
566 | ## 0.7.1
567 | ### Bugs fixed
568 | * Fixed semantic linting not marking use statements in docblocks as used when their types were used inside a type union (e.g. `string|DateTime`).
569 | * Fixed semantic linting not checking if class names in docblocks existed when they occurred inside a type union (e.g. `string|DateTime`).
570 | * Fixed semantic linting not validating class names that were used inside function calls or in class constant fetching.
571 | * Fixed semantic linting marking use statements as unused whilst they were being used inside function calls or in class constant fetching.
572 |
573 | ### Changes for developers
574 | * Methods will now contain information about whether they are abstract or not.
575 | * Methods will now contain information about whether the method they override was abstract or not.
576 |
577 | ## 0.7.0
578 | ### Features and enhancements
579 | * The SQLite database will now use the WAL journal mode, which offers performance benefits. (See also [the SQLite documentation](https://www.sqlite.org/draft/wal.html) for those interested.)
580 | * Additional checks have been added to ensure the indexing database doesn't go into an invalid state.
581 | * The use of `{@inheritDoc}` to extend long descriptions will now also work for child classes and extending interfaces.
582 | * Indexing will now respond less aggressively to changes in the buffer. Instead of spawning an indexing process unconditionally each time a file stops changing (after a couple hundred milliseconds by default), an indexing process will now only be spawned as soon as the previous one finishes. In essence, if an indexing process is already running, the indexer holds on to the changes and issues a reindex as soon as the previous one finishes. If a file takes very long to index and at this point you make multiple changes to the same file, only the last version will be reindexed (i.e. reindexing actions are not actually queued and the indexer does not need to catch up on a possibly long list of changes).
583 |
584 | ### Bugs fixed
585 | * Fixed some parameters of magic methods not showing up at all.
586 | * Fixed event handlers not being disposed when the package was deactivated.
587 | * Fixed parameter names for magic methods containing a comma in some cases.
588 | * Fixed parameters for magic methods being incorrectly marked as being optional.
589 | * Return types for magic properties were not being fetched at all.
590 | * Fixed problems with paths containing spaces on anything non-Windows (Linux, Mac OS X, ...).
591 | * Fixed the indexing progress bar disappearing sometimes if you edited a file while it was busy.
592 | * Fixed the `Warning: is_dir(): Unable to find the wrapper "atom"` error showing up in rare cases.
593 | * Fixed call stacks of simple expressions interpolated inside strings not being correctly retrieved.
594 | * Return types for magic methods were not being resolved relative to use statements or the namespace.
595 | * Parameter names for magic methods no longer contain a dollar sign (consistent with everything else).
596 | * Always set the timezone to UTC. (I changed my mind about this, due to reasons listed in [PR #129](https://github.com/Gert-dev/php-integrator-base/pull/129).) (thanks to [@tillkruss](https://github.com/tillkruss)).
597 | * Fixed issues with the indexer not correctly dealing with class names starting with a lower case letter.
598 | * Some non-standard behavior has been removed that may or may not be noticable to users:
599 | * Constants in traits will no longer be picked up (PHP error).
600 | * Properties in traits will no longer be scanned for aliases (PHP error).
601 | * Properties inside interfaces will not be "inherited" anymore (PHP error).
602 |
603 | ### Changes for developers
604 | * Magic properties and methods will no longer return true for `hasDocblock`.
605 | * `getAvailableVariables` received an extra (optional) `async` parameter.
606 | * `determineCurrentClassName` received an extra (optional) `async` parameter.
607 | * Magic properties and methods now return the class start line as their end line.
608 | * A new method `semanticLint` is now available that can semantically lint files and return various issues with it.
609 | * A new method `getAvailableVariablesByOffset` has been added that allows fetching available variables via an offset in a file. This method is implemented in PHP, supports asynchronous operation and the existing `getAvailableVariables` has been rewritten to use this method.
610 |
611 | ## 0.6.10
612 | ### Bugs fixed
613 | * Fixed the type resolver (--resolve-type) returning types prefixed with a leading slash when a file had no namespace, which is unnecessary and confusing as this slash isn't prepended anywhere else.
614 |
615 | ## 0.6.9
616 | ### Bugs fixed
617 | * Fixed built-in functions not being marked as being built-in.
618 | * Fixed the "Oops, something went wrong!" message when opening a comment that encompassed the rest of the file.
619 |
620 | ## 0.6.8
621 | ### Bugs fixed
622 | * Fixed no internal namespace record being generated when there were no use statements nor a namespace in a file (resulting in notifications being shown relating to no namespace being found).
623 |
624 | ## 0.6.7
625 | ### Bugs fixed
626 | * Fixed incorrect array key being used (`Undefined index: end_line`).
627 | * Notices and warnings from the PHP side will now be shown in the error notification.
628 | * Error reporting is now enabled explicitly in case you have it disabled on a global scale so you can see what is going wrong in the Atom error notification (without having to manually debug it).
629 |
630 | ## 0.6.6
631 | ### Bugs fixed
632 | * Fixed compatibility with PHP < 5.6 by avoiding use of ARRAY_FILTER_USE_KEY.
633 |
634 | ## 0.6.5
635 | ### Bugs fixed
636 | * Reintroduced the xdebug max nesting level, but this time set it to a ridiculously large number for compatibility.
637 |
638 | ## 0.6.4
639 | ### Bugs fixed
640 | * Fixed a minor logic error.
641 |
642 | ## 0.6.3
643 | ### Bugs fixed
644 | * Don't treat invalid JSON output as a JavaScript error, as it's usually not a bug in our package. Just display an error to the user instead.
645 |
646 | ## 0.6.2
647 | ### Bugs fixed
648 | * The "Indexing failed!" message has disappeared. If you wish to know when the indexer is having problems, consider using the new linter package (see also the README).
649 |
650 | ## 0.6.1
651 | ### Bugs fixed
652 | * Removed the dependency on fuzzaldrin.
653 | * Fixed the type of new instances, wrapped in parentheses and spread over several lines, not being properly recognized:
654 |
655 | ```php
656 | // Works:
657 | (new \IteratorIterator)->
658 |
659 | // Fails (fixed):
660 | (new \IteratorIterator(
661 |
662 | ))->
663 | ```
664 |
665 | ## 0.6.0
666 | ### Features and enhancements
667 | * xdebug will now be disabled in the indexer if it is present, it will only slow down the indexer.
668 | * Support for type inference when using the `clone` keyword, for example:
669 |
670 | ```php
671 | $a = new \DateTime();
672 | $b = clone $a;
673 | $b-> // Autocompletion for DateTime.
674 | ```
675 |
676 | * The draft PSR-5's `@inheritDoc` is now supported to indicate documentation was not forgotten. Also, note the difference with the non-standard ("incorrect"), yet commonly used, curly brace syntax used by Symfony 2 and other frameworks (which is also supported).
677 |
678 | ```php
679 | /**
680 | * @inheritDoc
681 | */
682 | public function explicitInheritanceAccordingToDraftPsr5()
683 | {
684 | ...
685 | }
686 |
687 | /**
688 | * {@inheritDoc}
689 | */
690 | public function incorrectInheritanceButCommonlyUsed()
691 | {
692 | ...
693 | }
694 | ```
695 |
696 | ### Bugs fixed
697 | * Fixed autocompletion not working after the concatenation operator.
698 | * Fixed author tags that contained mail addresses (such as used in Symfony) being taken up in extended descriptions.
699 | * Fixed an incorrect parameter index being returned in cases where you had array access via keys in function or method invocations.
700 | * Fixed a database constraint error when indexing parameters, which can happen if you have certain PHP extensions enabled (I'm looking at you, ssh2).
701 | * Resolving types is now more correct, taking multiple namespaces per file into account as well as only utilizing use statements that actually apply for a specific line.
702 |
703 | ### Changes for developers
704 | * A new call `getClassListForFile` takes a file path to filter the class list by.
705 | * `getClassSelectorFromEvent` will no longer return null for built-in classes (you can still easily check this yourself).
706 | * Next to `startLine` information, `endLine` information will now also be returned.
707 | * Fetching class information will now also return information about direct and indirect implemented interfaces and used traits via the properties `parents`, `directParents`, `interfaces`, `directInterfaces`, `traits` and `directTraits`.
708 | * Fetching class information will now also return information about direct children, direct implementors (if it's an interface) and direct users (if it's a trait).
709 | * `determineFullClassName` was split up into two methods (separation of concerns):
710 | * `determineCurrentClassName` - Takes an editor and a buffer position and returns the FQCN of the class the location is in. The buffer position is now required as a file can contain multiple classes, which were not retrievable before.
711 | * `resolveType` (with accompanying convenience method `resolveTypeAt`) - Takes an editor, a line, and a type name and resolves that type to its FQCN based on local use statements and namespace rules.
712 |
713 | ## 0.5.4
714 | ### Features and enhancements
715 | * Better error reporting. Exceptions thrown by the PHP side will now be displayed in an Atom error.
716 |
717 | ### Bugs fixed
718 | * Fixed `getInvocationInfoAt` not giving correct results when inside an argument that was wrapped in parentheses.
719 | * Fixed `getInvocationInfoAt` not giving a correct argument index when inside an array parameter with square brackets.
720 |
721 | ## 0.5.3
722 | ### Bugs fixed
723 | * Fixed expressions inside ternary operators not correctly being fetched.
724 |
725 | ## 0.5.2
726 | ### Bugs fixed
727 | * Fixed an error being thrown when writing code in a PHP file that hadn't been saved yet.
728 |
729 | ## 0.5.1
730 | ### Bugs fixed
731 | * Fixed classes never being marked as being abstract.
732 |
733 | ## 0.5.0
734 | ### Features and enhancements
735 | * Minor performance improvements when resolving local class names from docblocks.
736 | * Indexing will now happen continuously (onDidStopChanging of the text buffer) instead of only on save.
737 | * Descriptions from base classes or base interfaces will now be inherited if no description is present for a child class.
738 | * Type inference has been added for arrays, e.g.:
739 |
740 | ```php
741 | /** @var Foo[] $list */
742 | foreach ($list as $item) {
743 | $item-> // Autocompletion will be provided for 'Foo'.
744 | }
745 | ```
746 |
747 | ### Bugs fixed
748 | * Fixed docblocks with an empty throws tag causing the indexer to fail (Filter/Encrypt/Mcrypt.php in Zend 1 has one of these, apparently).
749 | * Fixed types not being properly inferred with the new keyword in combination with keywords such as `self`, `static` and `parent`, e.g. `$foo = new static();`.
750 | * Fixed issues with retrieving types in call stacks including static access, such as `self::$property->foo`.
751 | * Fixed built-in methods and functions having class types as parameters not being properly indexed. Instead, a PHP object would be returned in the parameter list. (Only applies to PHP 7.)
752 | * Fixed paths with spaces not indexing properly on Windows. (thanks to [@dipyalov](https://github.com/dipyalov))
753 | * Fixed variables being assigned to an expression on themselves sometimes not having their correct type deduced, for example:
754 |
755 | ```php
756 | $foo = new Foo();
757 | $foo = $foo-> // The cursor is here.
758 |
759 | moreCode();
760 |
761 | // The type would now be based on "$foo = $foo->moreCode()" instead of on "$foo = new Foo()".
762 | ```
763 |
764 | ### Changes for developers
765 | * Popovers will, by default, no longer catch pointer events (making them click-through).
766 | * You can now listen to indexing succeeding or failing using `onDidFinishIndexing` and `onDidFailIndexing`. (thanks to [@tocjent](https://github.com/tocjent))
767 | * The type for parameters of methods will now return the type as it was found in the file. If you wish to access the full resolved class type, you can use the new `fullType` property instead.
768 | * A new method `getInvocationInfoAt` is now available that allows fetching information about the function or method being invoked at the specified cursor position in an editor.
769 |
770 | ## 0.4.9
771 | ### Bugs fixed
772 | * Fixed the type of function or method parameters that were not documented incorrectly being set to the type of `$this`.
773 |
774 | ## 0.4.8
775 | ### Bugs fixed
776 | * The command names in the command palette will now be separated by spaces.
777 | * The indexer will no longer show warnings when `file_get_contents` fails. This will be caught and a warning will be displayed in verbose mode instead, ensuring that it will no longer cause indexing to fail in its entirety.
778 | * Fixed interfaces extending multiple other interfaces only having the first parent interface examined. This resulted in some class members not being listed if you implemented such an interface and could also result in files being scanned in the incorrect order (i.e. a child interface before a parent interface). This also resulted in a minor performance increase when fetching class information regarding inheritance as less queries are performed.
779 |
780 | ## 0.4.7
781 | ### Bugs fixed
782 | * Bump the database version.
783 |
784 | ## 0.4.6
785 | ### Bugs fixed
786 | * Fixed types sometimes showing up as [Object object] because the indexer was incorrectly saving an object instead of a string type.
787 | * Fixed built-in functions not having an empty array serialized to the throws_serialized field, resulting in warnings when iterating over them, which in turn caused problems on the CoffeeScript side.
788 |
789 | ## 0.4.5
790 | ### Bugs fixed
791 | * Fixed magic properties ending up in the index with a dollar sign in their name.
792 | * Fixed built-in class constants being indexed by value instead of name. This also resulted in a constraint error in the database. (thanks to [@tocjent](https://github.com/tocjent))
793 |
794 | ## 0.4.4
795 | ### Bugs fixed
796 | * Do not try to index structural elements that do not have a namespaced name, which can happen for anonymous PHP 7 classes.
797 |
798 | ## 0.4.3
799 | ### Bugs fixed
800 | * Disable the xdebug nesting limit by setting it to -1, which was posing problems when resolving names.
801 |
802 | ## 0.4.2
803 | ### Bugs fixed
804 | * Fixed the `indexes` folder not automatically being created.
805 |
806 | ## 0.4.1
807 | ### Bugs fixed
808 | * Removed the 'old version' note from the README.
809 |
810 | ## 0.4.0
811 | ### Features and enhancements
812 | * The PHP side now no longer uses Reflection, but a php-parser-based incremental indexer. This has the following advantages:
813 | * Composer is no longer required.
814 | * Autoloading is no longer required.
815 | * Multiple structural elements (classes) in a file will correctly end up in the index.
816 | * Parsing is inherently more correct, no more misbehaving corner cases or regex-based parsing.
817 | * Incremental indexing, no more composer classmap dumping on startup and a long indexing process.
818 | * Paves the road for the future more detailed parsing, analyzing and better performance. (As a side-effect, the PHP side can also be used for other editors than Atom.)
819 | * The package still requires PHP >= 5.4, but the code you're writing can be anything that php-parser 2 supports, which is currently PHP >= 5.2 up to (and including) PHP 7. Note that the CoffeeScript side has not changed and also does some regex-based parsing, which may still lack some functionality (especially with relation to PHP 7).
820 |
821 | * Made it possible to make magic members static. This is not documented in phpDocumentor's documentation, but useful and also used by PHPStorm:
822 |
823 | ```php
824 | /**
825 | * @method static void foo()
826 | * @property static Foo $test
827 | */
828 | class Foo
829 | {
830 | ...
831 | }
832 | ```
833 |
834 | * Added menu items for some commands.
835 | * The progress bar will now actually show progress, which is useful for large projects.
836 | * There is now a command available in the command palette to reindex your project (thanks to [@wcatron](https://github.com/wcatron)).
837 | * Similarly, there is now also a command available to forcibly reindex the project (i.e. remove the entire database first).
838 |
839 | ### Bugs fixed
840 | * A progress bar will no longer be shown for windows opened without a project.
841 | * Methods returning `static` were not properly resolving to the current class if they were not overridden (they were resolving to the closest parent class that defined them).
842 | * Project and file paths that contain spaces should no longer pose a problem (thanks to [@wcatron](https://github.com/wcatron)).
843 |
844 | ### Changes for developers
845 | * Constants and class properties will now retrieve their start line as well. These items not being available was previously manually worked around in the php-integrator-navigation package. This manual workaround is now present in the base package as CoffeeScript should not have to bend itself backwards to get this information because PHP Reflection didn't offer it.
846 | * `declaringStructure` will now also contain a `startLineMember` property that indicates the start line of the member in the structural element.
847 | * Retrieving types for the list of local variables has been disabled for now as the performance impact is too large in longer functions. It may be reactivated in a future release.
848 | * Constructor information is no longer retrieved when fetching the class list as the performance hit is too large. In fact, this was also the case before the new indexer, but then a cache file was used, which was never properly updated with new classes and was the result of the very long indexing process at start-up.
849 | * `deprecated` was renamed to `isDeprecated` for consistency.
850 | * `wasFound` was removed when fetching class (structural element) information as a failure status is now returned instead.
851 | * `class` was removed when fetching class (structural element) information as this information is already available in the `name` property.
852 | * `isMethod` and `isProperty` were removed as they are no longer necessary since members are now in separate associative arrays.
853 | * Added the more specific convenience methods `getClassMethodAt`, `getClassPropertyAt` and `getClassConstantAt`.
854 | * `isTrait`, `isInterface` and `isClass` were replaced with a single string `type` property.
855 | * Functions and methods no longer return a separate `parameters` and `optionals` property. The two have now been merged and provide more information (such as whether the parameters are a reference, variadic, optional, ...).
856 |
857 | ## 0.3.1
858 | ### Bugs fixed
859 | * Fixed methods returning `static`, `self` or `$this` not properly having their full type deduced.
860 | * Fixed inline type override annotations not being able to contain descriptions (e.g. `/** @var Class $foo Some description. */`).
861 |
862 | ## 0.3.0
863 | ### Features and enhancements
864 | * Performance in general should be improved as the same parsing operations are now cached as often as possible.
865 | * Types of variables that are assigned to basic type expressions will now be deduced properly instead of just having the expression as type:
866 |
867 | ```php
868 | $var = 5; // $var = int
869 | $var = 5.0; // $var = float
870 | $var = true; // $var = bool
871 | $var = ""; // $var = string
872 | $var = []; // $var = array
873 | ```
874 |
875 | * If statements that have (only) an 'instanceof' of a variable in them will now be used to deduce the type of a variable. (More complex if statements will, at least for now, not be picked up.) For example (when using php-integrator-autocomplete-plus):
876 |
877 | ```php
878 | if ($foo instanceof Foo) {
879 | $foo-> // Autocompletion for Foo will be shown.
880 | }
881 | ```
882 |
883 | * Closures will now be detected, for example (when using php-integrator-autocomplete-plus):
884 |
885 | ```php
886 | $foo = function () {
887 |
888 | };
889 |
890 | $foo-> // Autocompletion for Closure, listing bind and bindTo.
891 | ```
892 |
893 | * Added support for parsing magic properties and methods for classes, which will now also be returned (property-read and property-write are also returned):
894 |
895 | ```php
896 | /**
897 | * @property Foo $someProperty A description.
898 | * @method magicFoo($param1, array $param2 = null) My magic method.
899 | */
900 | class MyClass
901 | {
902 |
903 | }
904 | ```
905 |
906 | ### Bugs fixed
907 | * The indexer will no longer try to index PHP files that don't belong to the project on save.
908 | * Docblock parameters weren't being analyzed for deducing the type of local variables when in a global function.
909 | * Types of variables that had their assigned values spread over multiple lines will now correctly have their type deduced.
910 | * In rare cases, types could not be properly deduced, such as in `$a = ['b' => (new Foo())->]` (`Foo` would incorrectly not be returned as type).
911 | * Only the relevant scopes will now be searched for the type of variables, previously all code was examined, even code outside the current scope.
912 | * Descriptions after the `@var` tag, i.e. `@var Foo $foo My description` , will now be used as fall back when there is no short description present.
913 | * The wrong type was sometimes shown for variables as their type was determined at the point they were found instead of the point at which they were requested.
914 | * Functions that had no docblock were wrongly assumed to return 'void' (or 'self' in the case of constructors). This only applies to functions that do have a docblock, but no `@return` tag in the docblock.
915 | * Support for the short annotation style, `/** @var FooClass */`, was dropped. The reason for this is that it's not supported by any IDE and is very specific to this package. It's also completely inflexible because it needs to be directly above the last assignment or other type deduction (such as a catch block) for it to be picked up incorrectly. The other annotation styles have none of these restrictions and also work in IDE's such as PHPStorm.
916 |
917 | ### Changes for developers
918 | * `determineFullClassName` will now return basic types as is, without prefixing them with the current namespace.
919 | * A new method `isBasicType` has been introduced, that will return true for basic types such as "int", "BOOL", "array", "string", ...
920 | * The `getDocParams` method has been removed. It was obsolete as the same information is already returned by `getClassInfo`. Also more caches can be reused by using `getClassInfo`.
921 | * The `autocomplete` method has been removed. It was confusing and also mostly obsolete as its functionality can already be mimicked through other methods (it was only internally used).
922 | * Data returned about methods, constants, functions and structures will no longer have an 'args' property containing information such as descriptions. Instead these were moved up one level (in other words you can just replace the `.args.property` with just `.property` everywhere). It wasn't clear what exactly belonged in `args` and what didn't, hence its entire removal.
923 |
924 | ## 0.2.0
925 | ### Features and enhancements
926 | * There was no convenient visual indicator of when indexing failed, a label is now shown in the status bar if that is indeed the case.
927 | * When the initial PHP process that indexes the entire project fails or is killed, it will now be picked up and displayed as an error.
928 | * The list of variables returned will now try to skip scopes that don't apply. For example, you will now only see variables that are relevant to your closure when inside one.
929 | * It is now possible to specify a list of additional scripts to load, which allows you to add things such as bootstrap scripts or scripts with global helper functions, which will then be made available to other packages (such as autocompletion).
930 | * The return type of your global functions will now be correctly analyzed, the following will now work:
931 |
932 | ```php
933 | /**
934 | * @return \DateTime
935 | */
936 | function foo()
937 | {
938 |
939 | }
940 |
941 | foo()-> // Autocompletion for DateTime.
942 | ```
943 |
944 | ### Bugs fixed
945 | * Fixed the 'className.split is not a function' error popping up sometimes.
946 | * Fixed type hints from function parameters not being correctly deduced in some cases.
947 | * Return values such as `\DateTime` (with a leading slash) were not always being found.
948 | * Numbers and underscores were not permitted in class names in several locations (they are now).
949 | * The PHP FileParser will no longer trip over class docblocks containing the pattern `class MyClass`.
950 | * Classes from this package are now no longer included in the class list and will no longer be indexed.
951 | * Fix several issues with autocompletion of `(new Foo())->` in corner cases such as inside arrays and function calls.
952 | * Global class names in combination with the 'new' operator such as `new \My\Class` were not properly having their type deduced (and e.g. getting no autocompletion as a result).
953 | * Fixed an issue where the package would attempt to index the project on shutdown. This could result in a message being displayed at shutdown about the Atom window not being responsive.
954 |
955 | ### Changes for developers
956 | * New service methods:
957 | * `getClassMethod` - Retrieves information about a specific class method.
958 | * `getClassProperty` - Retrieves information about a specific class property.
959 | * `getClassConstant` - Retrieves information about a specific class constant.
960 | * Previously, `getCalledClass` always ignored the last element in a call stack, causing `$this->foo->b` to return the type of `foo` instead of `b`. Because this behavior is unexpected and undocumented, this no longer happens. To maintain this 'feature', a new parameter `ignoreLastElement` has been added that can be set to true to restore this behavior (i.e. it will return the type of `foo`). Setting it to false will return the type of `b` instead.
961 | * `getGlobalFunctions` will now also return user-defined global functions (i.e. non-internal global functions).
962 | * `getGlobalConstants` will now also return information about if a constant is built-in or not (as opposed to user defined).
963 | * `getAvailableVariables` now returns an object with variable names mapped to information such as their type (if found).
964 | * `getClassMemberAt` will now return the correct member if a structure has a property and a method with the same name.
965 | * `getCalledClass` is now called `getResultingTypeAt` to better indicate what it does and that it needs a buffer position.
966 | * Class constants will now contain information about their declaring class and declaring structure, just like other members.
967 | * Several methods such as `getClassInfo` now take an additional parameter to make them execute asynchronously (a promise will be returned instead of the actual results).
968 |
969 | ## 0.1.0
970 | * Initial release.
971 |
--------------------------------------------------------------------------------