├── .coffeelintignore ├── .gitignore ├── .travis.yml ├── appveyor.yml ├── .github └── no-response.yml ├── coffeelint.json ├── package.json ├── README.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── spec ├── deprecation-cop-spec.coffee ├── deprecation-cop-status-bar-view-spec.coffee └── deprecation-cop-view-spec.coffee ├── lib ├── main.js ├── deprecation-cop-status-bar-view.coffee └── deprecation-cop-view.js ├── styles └── deprecation-cop.less └── ISSUE_TEMPLATE.md /.coffeelintignore: -------------------------------------------------------------------------------- 1 | spec/fixtures 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | 3 | notifications: 4 | email: 5 | on_success: never 6 | on_failure: change 7 | 8 | script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 9 | 10 | git: 11 | depth: 10 12 | 13 | branches: 14 | only: 15 | - master 16 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: x64 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | clone_depth: 10 10 | 11 | skip_tags: true 12 | 13 | environment: 14 | APM_TEST_PACKAGES: 15 | 16 | matrix: 17 | - ATOM_CHANNEL: stable 18 | - ATOM_CHANNEL: beta 19 | 20 | install: 21 | - ps: Install-Product node 4 22 | 23 | build_script: 24 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) 25 | 26 | test: off 27 | deploy: off 28 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an issue is closed for lack of response 4 | daysUntilClose: 180 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an issue for lack of response. Set to `false` to disable. 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate further. 16 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_line_length": { 3 | "level": "ignore" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "arrow_spacing": { 9 | "level": "error" 10 | }, 11 | "no_interpolation_in_single_quotes": { 12 | "level": "error" 13 | }, 14 | "no_debugger": { 15 | "level": "error" 16 | }, 17 | "prefer_english_operator": { 18 | "level": "error" 19 | }, 20 | "colon_assignment_spacing": { 21 | "spacing": { 22 | "left": 0, 23 | "right": 1 24 | }, 25 | "level": "error" 26 | }, 27 | "braces_spacing": { 28 | "spaces": 0, 29 | "level": "error" 30 | }, 31 | "spacing_after_comma": { 32 | "level": "error" 33 | }, 34 | "no_stand_alone_at": { 35 | "level": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deprecation-cop", 3 | "main": "./lib/main", 4 | "version": "0.56.9", 5 | "description": "Shows a list of deprecated calls", 6 | "repository": "https://github.com/atom/deprecation-cop", 7 | "license": "MIT", 8 | "engines": { 9 | "atom": ">0.50.0" 10 | }, 11 | "dependencies": { 12 | "etch": "0.9.0", 13 | "fs-plus": "^3.0.0", 14 | "grim": "^2.0.1", 15 | "marked": "^0.3.6", 16 | "underscore-plus": "^1.0.0" 17 | }, 18 | "consumedServices": { 19 | "status-bar": { 20 | "versions": { 21 | "^1.0.0": "consumeStatusBar" 22 | } 23 | } 24 | }, 25 | "deserializers": { 26 | "DeprecationCopView": "deserializeDeprecationCopView" 27 | }, 28 | "devDependencies": { 29 | "coffeelint": "^1.9.7" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This package is now a part of the [core Atom repository](https://github.com/atom/atom/tree/master/packages/deprecation-cop), please direct all issues and pull requests there in the future! 2 | 3 | --- 4 | 5 | # Deprecation Cop package 6 | [![OS X Build Status](https://travis-ci.org/atom/deprecation-cop.svg?branch=master)](https://travis-ci.org/atom/deprecation-cop) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/0s870q5fj3vwihjx/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/deprecation-cop/branch/master) [![Dependency Status](https://david-dm.org/atom/deprecation-cop.svg)](https://david-dm.org/atom/deprecation-cop) 7 | 8 | Shows a list of deprecated methods calls. Ideally it should show nothing! 9 | 10 | ![https://github-images.s3.amazonaws.com/skitch/Deprecation_Cop_-__Users_corey_github_deprecation-cop-20140414-144618.jpg](https://github-images.s3.amazonaws.com/skitch/Deprecation_Cop_-__Users_corey_github_deprecation-cop-20140414-144618.jpg) 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Benefits 19 | 20 | 21 | 22 | ### Possible Drawbacks 23 | 24 | 25 | 26 | ### Applicable Issues 27 | 28 | 29 | -------------------------------------------------------------------------------- /spec/deprecation-cop-spec.coffee: -------------------------------------------------------------------------------- 1 | DeprecationCopView = require '../lib/deprecation-cop-view' 2 | 3 | describe "DeprecationCop", -> 4 | [activationPromise, workspaceElement] = [] 5 | 6 | beforeEach -> 7 | workspaceElement = atom.views.getView(atom.workspace) 8 | activationPromise = atom.packages.activatePackage('deprecation-cop') 9 | expect(atom.workspace.getActivePane().getActiveItem()).not.toExist() 10 | 11 | describe "when the deprecation-cop:view event is triggered", -> 12 | it "displays the deprecation cop pane", -> 13 | atom.commands.dispatch workspaceElement, 'deprecation-cop:view' 14 | 15 | waitsForPromise -> 16 | activationPromise 17 | 18 | deprecationCopView = null 19 | waitsFor -> 20 | deprecationCopView = atom.workspace.getActivePane().getActiveItem() 21 | 22 | runs -> 23 | expect(deprecationCopView instanceof DeprecationCopView).toBeTruthy() 24 | 25 | describe "deactivating the package", -> 26 | it "removes the deprecation cop pane item", -> 27 | atom.commands.dispatch workspaceElement, 'deprecation-cop:view' 28 | 29 | waitsForPromise -> 30 | activationPromise 31 | 32 | waitsForPromise -> 33 | Promise.resolve(atom.packages.deactivatePackage('deprecation-cop')) # Wrapped for Promise & non-Promise deactivate 34 | 35 | runs -> 36 | expect(atom.workspace.getActivePane().getActiveItem()).not.toExist() 37 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | const {Disposable, CompositeDisposable} = require('atom') 2 | const DeprecationCopView = require('./deprecation-cop-view') 3 | const DeprecationCopStatusBarView = require('./deprecation-cop-status-bar-view') 4 | const ViewURI = 'atom://deprecation-cop' 5 | 6 | class DeprecationCopPackage { 7 | activate () { 8 | this.disposables = new CompositeDisposable() 9 | this.disposables.add(atom.workspace.addOpener((uri) => { 10 | if (uri === ViewURI) { 11 | return this.deserializeDeprecationCopView({uri}) 12 | } 13 | })) 14 | this.disposables.add(atom.commands.add('atom-workspace', 'deprecation-cop:view', () => { 15 | atom.workspace.open(ViewURI) 16 | })) 17 | } 18 | 19 | deactivate () { 20 | this.disposables.dispose() 21 | const pane = atom.workspace.paneForURI(ViewURI) 22 | if (pane) { 23 | pane.destroyItem(pane.itemForURI(ViewURI)) 24 | } 25 | } 26 | 27 | deserializeDeprecationCopView (state) { 28 | return new DeprecationCopView(state) 29 | } 30 | 31 | consumeStatusBar (statusBar) { 32 | const statusBarView = new DeprecationCopStatusBarView() 33 | const statusBarTile = statusBar.addRightTile({item: statusBarView, priority: 150}) 34 | this.disposables.add(new Disposable(() => { statusBarView.destroy() })) 35 | this.disposables.add(new Disposable(() => { statusBarTile.destroy() })) 36 | } 37 | } 38 | 39 | module.exports = new DeprecationCopPackage() 40 | -------------------------------------------------------------------------------- /styles/deprecation-cop.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .deprecation-cop-status { 8 | .icon.icon-alert:before { 9 | // It's a really big icon... 10 | width: 17px; 11 | } 12 | } 13 | .deprecation-cop { 14 | overflow: auto; 15 | -webkit-flex: 1; 16 | background-color: @app-background-color !important; 17 | 18 | .deprecation-overview { 19 | &:after { 20 | content: ''; 21 | clear: both; 22 | display: block; 23 | } 24 | } 25 | 26 | .deprecation-info { 27 | padding: 0 @component-padding; 28 | } 29 | 30 | .deprecation-info:hover { 31 | background-color: @background-color-selected !important; 32 | } 33 | 34 | .deprecation-detail.list-item { 35 | white-space: normal; 36 | clear: both; 37 | 38 | .deprecation-message { 39 | padding: 5px 0 5px 28px; 40 | line-height: 1.4; 41 | font-size: @font-size; 42 | 43 | p:last-child { 44 | margin-bottom: 0; 45 | } 46 | } 47 | 48 | .icon-alert { 49 | margin-top: 5px; 50 | float: left; 51 | } 52 | } 53 | 54 | .collapsed > ul { 55 | display: none; 56 | } 57 | 58 | .list { 59 | list-style-type: none; 60 | padding: 0; 61 | } 62 | 63 | .stack-trace { 64 | background-color: @tool-panel-background-color; 65 | padding: @component-padding; 66 | margin-bottom: @component-padding; 67 | } 68 | 69 | a { 70 | color: @text-color-highlight; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Prerequisites 10 | 11 | * [ ] Put an X between the brackets on this line if you have done all of the following: 12 | * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode 13 | * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/ 14 | * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq 15 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom 16 | * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages 17 | 18 | ### Description 19 | 20 | [Description of the issue] 21 | 22 | ### Steps to Reproduce 23 | 24 | 1. [First Step] 25 | 2. [Second Step] 26 | 3. [and so on...] 27 | 28 | **Expected behavior:** [What you expect to happen] 29 | 30 | **Actual behavior:** [What actually happens] 31 | 32 | **Reproduces how often:** [What percentage of the time does it reproduce?] 33 | 34 | ### Versions 35 | 36 | You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running. 37 | 38 | ### Additional Information 39 | 40 | Any additional information, configuration or data that might be necessary to reproduce the issue. 41 | -------------------------------------------------------------------------------- /lib/deprecation-cop-status-bar-view.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable, Disposable} = require 'atom' 2 | _ = require 'underscore-plus' 3 | Grim = require 'grim' 4 | 5 | module.exports = 6 | class DeprecationCopStatusBarView 7 | lastLength: null 8 | toolTipDisposable: null 9 | 10 | constructor: -> 11 | @subscriptions = new CompositeDisposable 12 | 13 | @element = document.createElement('div') 14 | @element.classList.add('deprecation-cop-status', 'inline-block', 'text-warning') 15 | @element.setAttribute('tabindex', -1) 16 | 17 | @icon = document.createElement('span') 18 | @icon.classList.add('icon', 'icon-alert') 19 | @element.appendChild(@icon) 20 | 21 | @deprecationNumber = document.createElement('span') 22 | @deprecationNumber.classList.add('deprecation-number') 23 | @deprecationNumber.textContent = '0' 24 | @element.appendChild(@deprecationNumber) 25 | 26 | clickHandler = -> 27 | workspaceElement = atom.views.getView(atom.workspace) 28 | atom.commands.dispatch workspaceElement, 'deprecation-cop:view' 29 | @element.addEventListener('click', clickHandler) 30 | @subscriptions.add(new Disposable(=> @element.removeEventListener('click', clickHandler))) 31 | 32 | @update() 33 | 34 | debouncedUpdateDeprecatedSelectorCount = _.debounce(@update, 1000) 35 | 36 | @subscriptions.add Grim.on 'updated', @update 37 | # TODO: Remove conditional when the new StyleManager deprecation APIs reach stable. 38 | if atom.styles.onDidUpdateDeprecations? 39 | @subscriptions.add(atom.styles.onDidUpdateDeprecations(debouncedUpdateDeprecatedSelectorCount)) 40 | 41 | destroy: -> 42 | @subscriptions.dispose() 43 | @element.remove() 44 | 45 | getDeprecatedCallCount: -> 46 | Grim.getDeprecations().map((d) -> d.getStackCount()).reduce(((a, b) -> a + b), 0) 47 | 48 | getDeprecatedStyleSheetsCount: -> 49 | # TODO: Remove conditional when the new StyleManager deprecation APIs reach stable. 50 | if atom.styles.getDeprecations? 51 | Object.keys(atom.styles.getDeprecations()).length 52 | else 53 | 0 54 | 55 | update: => 56 | length = @getDeprecatedCallCount() + @getDeprecatedStyleSheetsCount() 57 | 58 | return if @lastLength is length 59 | 60 | @lastLength = length 61 | @deprecationNumber.textContent = "#{_.pluralize(length, 'deprecation')}" 62 | @toolTipDisposable?.dispose() 63 | @toolTipDisposable = atom.tooltips.add @element, title: "#{_.pluralize(length, 'call')} to deprecated methods" 64 | 65 | if length is 0 66 | @element.style.display = 'none' 67 | else 68 | @element.style.display = '' 69 | -------------------------------------------------------------------------------- /spec/deprecation-cop-status-bar-view-spec.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | Grim = require 'grim' 3 | DeprecationCopView = require '../lib/deprecation-cop-view' 4 | _ = require 'underscore-plus' 5 | 6 | describe "DeprecationCopStatusBarView", -> 7 | [deprecatedMethod, statusBarView, workspaceElement] = [] 8 | 9 | beforeEach -> 10 | # jasmine.Clock.useMock() cannot mock _.debounce 11 | # http://stackoverflow.com/questions/13707047/spec-for-async-functions-using-jasmine 12 | spyOn(_, 'debounce').andCallFake (func) -> 13 | -> func.apply(this, arguments) 14 | 15 | jasmine.snapshotDeprecations() 16 | 17 | workspaceElement = atom.views.getView(atom.workspace) 18 | jasmine.attachToDOM(workspaceElement) 19 | waitsForPromise -> atom.packages.activatePackage('status-bar') 20 | waitsForPromise -> atom.packages.activatePackage('deprecation-cop') 21 | 22 | waitsFor -> 23 | statusBarView = workspaceElement.querySelector('.deprecation-cop-status') 24 | 25 | afterEach -> 26 | jasmine.restoreDeprecationsSnapshot() 27 | 28 | it "adds the status bar view when activated", -> 29 | expect(statusBarView).toExist() 30 | expect(statusBarView.textContent).toBe '0 deprecations' 31 | expect(statusBarView).not.toShow() 32 | 33 | it "increments when there are deprecated methods", -> 34 | deprecatedMethod = -> Grim.deprecate("This isn't used") 35 | anotherDeprecatedMethod = -> Grim.deprecate("This either") 36 | expect(statusBarView.style.display).toBe 'none' 37 | expect(statusBarView.offsetHeight).toBe(0) 38 | 39 | deprecatedMethod() 40 | expect(statusBarView.textContent).toBe '1 deprecation' 41 | expect(statusBarView.offsetHeight).toBeGreaterThan(0) 42 | 43 | deprecatedMethod() 44 | expect(statusBarView.textContent).toBe '2 deprecations' 45 | expect(statusBarView.offsetHeight).toBeGreaterThan(0) 46 | 47 | anotherDeprecatedMethod() 48 | expect(statusBarView.textContent).toBe '3 deprecations' 49 | expect(statusBarView.offsetHeight).toBeGreaterThan(0) 50 | 51 | # TODO: Remove conditional when the new StyleManager deprecation APIs reach stable. 52 | if atom.styles.getDeprecations? 53 | it "increments when there are deprecated selectors", -> 54 | atom.styles.addStyleSheet(""" 55 | atom-text-editor::shadow { color: red; } 56 | """, sourcePath: 'file-1') 57 | expect(statusBarView.textContent).toBe '1 deprecation' 58 | expect(statusBarView).toBeVisible() 59 | atom.styles.addStyleSheet(""" 60 | atom-text-editor::shadow { color: blue; } 61 | """, sourcePath: 'file-2') 62 | expect(statusBarView.textContent).toBe '2 deprecations' 63 | expect(statusBarView).toBeVisible() 64 | 65 | it 'opens deprecation cop tab when clicked', -> 66 | expect(atom.workspace.getActivePane().getActiveItem()).not.toExist() 67 | 68 | waitsFor (done) -> 69 | atom.workspace.onDidOpen ({item}) -> 70 | expect(item instanceof DeprecationCopView).toBe true 71 | done() 72 | statusBarView.click() 73 | -------------------------------------------------------------------------------- /spec/deprecation-cop-view-spec.coffee: -------------------------------------------------------------------------------- 1 | Grim = require 'grim' 2 | path = require 'path' 3 | _ = require 'underscore-plus' 4 | etch = require 'etch' 5 | 6 | describe "DeprecationCopView", -> 7 | [deprecationCopView, workspaceElement] = [] 8 | 9 | beforeEach -> 10 | spyOn(_, 'debounce').andCallFake (func) -> 11 | -> func.apply(this, arguments) 12 | 13 | workspaceElement = atom.views.getView(atom.workspace) 14 | jasmine.attachToDOM(workspaceElement) 15 | 16 | jasmine.snapshotDeprecations() 17 | Grim.clearDeprecations() 18 | deprecatedMethod = -> Grim.deprecate("A test deprecation. This isn't used") 19 | deprecatedMethod() 20 | 21 | spyOn(Grim, 'deprecate') # Don't fail tests if when using deprecated APIs in deprecation cop's activation 22 | activationPromise = atom.packages.activatePackage('deprecation-cop') 23 | 24 | atom.commands.dispatch workspaceElement, 'deprecation-cop:view' 25 | 26 | waitsForPromise -> 27 | activationPromise 28 | 29 | waitsFor -> deprecationCopView = atom.workspace.getActivePane().getActiveItem() 30 | 31 | runs -> 32 | jasmine.unspy(Grim, 'deprecate') 33 | 34 | afterEach -> 35 | jasmine.restoreDeprecationsSnapshot() 36 | 37 | it "displays deprecated methods", -> 38 | expect(deprecationCopView.element.textContent).toMatch /Deprecated calls/ 39 | expect(deprecationCopView.element.textContent).toMatch /This isn't used/ 40 | 41 | # TODO: Remove conditional when the new StyleManager deprecation APIs reach stable. 42 | if atom.styles.getDeprecations? 43 | it "displays deprecated selectors", -> 44 | atom.styles.addStyleSheet("atom-text-editor::shadow { color: red }", sourcePath: path.join('some-dir', 'packages', 'package-1', 'file-1.css')) 45 | atom.styles.addStyleSheet("atom-text-editor::shadow { color: yellow }", context: 'atom-text-editor', sourcePath: path.join('some-dir', 'packages', 'package-1', 'file-2.css')) 46 | atom.styles.addStyleSheet('atom-text-editor::shadow { color: blue }', sourcePath: path.join('another-dir', 'packages', 'package-2', 'file-3.css')) 47 | atom.styles.addStyleSheet('atom-text-editor::shadow { color: gray }', sourcePath: path.join('another-dir', 'node_modules', 'package-3', 'file-4.css')) 48 | 49 | promise = etch.getScheduler().getNextUpdatePromise() 50 | waitsForPromise -> promise 51 | 52 | runs -> 53 | packageItems = deprecationCopView.element.querySelectorAll("ul.selectors > li") 54 | expect(packageItems.length).toBe(3) 55 | expect(packageItems[0].textContent).toMatch /package-1/ 56 | expect(packageItems[1].textContent).toMatch /package-2/ 57 | expect(packageItems[2].textContent).toMatch /Other/ 58 | 59 | packageDeprecationItems = packageItems[0].querySelectorAll("li.source-file") 60 | expect(packageDeprecationItems.length).toBe(2) 61 | expect(packageDeprecationItems[0].textContent).toMatch /atom-text-editor/ 62 | expect(packageDeprecationItems[0].querySelector("a").href).toMatch('some-dir/packages/package-1/file-1.css') 63 | expect(packageDeprecationItems[1].textContent).toMatch /:host/ 64 | expect(packageDeprecationItems[1].querySelector("a").href).toMatch('some-dir/packages/package-1/file-2.css') 65 | 66 | it 'skips stack entries which go through node_modules/ files when determining package name', -> 67 | stack = [ 68 | { 69 | "functionName": "function0" 70 | "location": path.normalize "/Users/user/.atom/packages/package1/node_modules/atom-space-pen-viewslib/space-pen.js:55:66" 71 | "fileName": path.normalize "/Users/user/.atom/packages/package1/node_modules/atom-space-pen-views/lib/space-pen.js" 72 | } 73 | { 74 | "functionName": "function1" 75 | "location": path.normalize "/Users/user/.atom/packages/package1/node_modules/atom-space-pen-viewslib/space-pen.js:15:16" 76 | "fileName": path.normalize "/Users/user/.atom/packages/package1/node_modules/atom-space-pen-views/lib/space-pen.js" 77 | } 78 | { 79 | "functionName": "function2" 80 | "location": path.normalize "/Users/user/.atom/packages/package2/lib/module.js:13:14" 81 | "fileName": path.normalize "/Users/user/.atom/packages/package2/lib/module.js" 82 | } 83 | ] 84 | 85 | packagePathsByPackageName = new Map([ 86 | ['package1', path.normalize("/Users/user/.atom/packages/package1")], 87 | ['package2', path.normalize("/Users/user/.atom/packages/package2")] 88 | ]) 89 | 90 | spyOn(deprecationCopView, 'getPackagePathsByPackageName').andReturn(packagePathsByPackageName) 91 | 92 | packageName = deprecationCopView.getPackageName(stack) 93 | expect(packageName).toBe("package2") 94 | -------------------------------------------------------------------------------- /lib/deprecation-cop-view.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | /** @jsx etch.dom */ 3 | 4 | import _ from 'underscore-plus' 5 | import {CompositeDisposable} from 'atom' 6 | import etch from 'etch' 7 | import fs from 'fs-plus' 8 | import Grim from 'grim' 9 | import marked from 'marked' 10 | import path from 'path' 11 | import shell from 'shell' 12 | 13 | export default class DeprecationCopView { 14 | constructor ({uri}) { 15 | this.uri = uri 16 | this.subscriptions = new CompositeDisposable 17 | this.subscriptions.add(Grim.on('updated', () => { etch.update(this) })) 18 | // TODO: Remove conditional when the new StyleManager deprecation APIs reach stable. 19 | if (atom.styles.onDidUpdateDeprecations) { 20 | this.subscriptions.add(atom.styles.onDidUpdateDeprecations(() => { etch.update(this) })) 21 | } 22 | etch.initialize(this) 23 | this.subscriptions.add(atom.commands.add(this.element, { 24 | 'core:move-up': () => { this.scrollUp() }, 25 | 'core:move-down': () => { this.scrollDown() }, 26 | 'core:page-up': () => { this.pageUp() }, 27 | 'core:page-down': () => { this.pageDown() }, 28 | 'core:move-to-top': () => { this.scrollToTop() }, 29 | 'core:move-to-bottom': () => { this.scrollToBottom() } 30 | })) 31 | } 32 | 33 | serialize () { 34 | return { 35 | deserializer: this.constructor.name, 36 | uri: this.getURI(), 37 | version: 1 38 | } 39 | } 40 | 41 | destroy () { 42 | this.subscriptions.dispose() 43 | return etch.destroy(this) 44 | } 45 | 46 | update () { 47 | return etch.update(this) 48 | } 49 | 50 | render () { 51 | return ( 52 |
53 |
54 |
55 |
56 | 62 |
63 |
64 | 65 |
Deprecated calls
66 |
    67 | {this.renderDeprecatedCalls()} 68 |
69 | 70 |
Deprecated selectors
71 |
    72 | {this.renderDeprecatedSelectors()} 73 |
74 |
75 |
76 | ) 77 | } 78 | 79 | renderDeprecatedCalls () { 80 | const deprecationsByPackageName = this.getDeprecatedCallsByPackageName() 81 | const packageNames = Object.keys(deprecationsByPackageName) 82 | if (packageNames.length === 0) { 83 | return
  • No deprecated calls
  • 84 | } else { 85 | return packageNames.sort().map((packageName) => ( 86 |
  • 87 |
    event.target.parentElement.classList.toggle('collapsed')}> 88 | {packageName || 'atom core'} 89 | {` (${_.pluralize(deprecationsByPackageName[packageName].length, 'deprecation')})`} 90 |
    91 | 92 | 117 |
  • 118 | )) 119 | } 120 | } 121 | 122 | renderDeprecatedSelectors () { 123 | const deprecationsByPackageName = this.getDeprecatedSelectorsByPackageName() 124 | const packageNames = Object.keys(deprecationsByPackageName) 125 | if (packageNames.length === 0) { 126 | return ( 127 |
  • No deprecated selectors
  • 128 | ) 129 | } else { 130 | return packageNames.map((packageName) => ( 131 |
  • 132 |
    event.target.parentElement.classList.toggle('collapsed')}> 133 | {packageName} 134 |
    135 | 136 | 162 |
  • 163 | )) 164 | } 165 | } 166 | 167 | renderPackageActionsIfNeeded (packageName) { 168 | if (packageName && atom.packages.getLoadedPackage(packageName)) { 169 | return ( 170 |
    171 |
    172 | 178 | 185 |
    186 |
    187 | ) 188 | } else { 189 | return '' 190 | } 191 | } 192 | 193 | encodeURI (str) { 194 | return encodeURI(str).replace(/#/g, '%23').replace(/;/g, '%3B').replace(/%20/g, '+') 195 | } 196 | 197 | renderSelectorIssueURLIfNeeded (packageName, issueTitle, issueBody) { 198 | const repoURL = this.getRepoURL(packageName) 199 | if (repoURL) { 200 | const issueURL = `${repoURL}/issues/new?title=${this.encodeURI(issueTitle)}&body=${this.encodeURI(issueBody)}` 201 | return ( 202 |
    203 | 212 |
    213 | ) 214 | } else { 215 | return '' 216 | } 217 | } 218 | 219 | renderIssueURLIfNeeded (packageName, deprecation, issueURL) { 220 | if (packageName && issueURL) { 221 | const repoURL = this.getRepoURL(packageName) 222 | const issueTitle = `${deprecation.getOriginName()} is deprecated.` 223 | return ( 224 |
    225 | 234 |
    235 | ) 236 | } else { 237 | return '' 238 | } 239 | } 240 | 241 | buildIssueURL (packageName, deprecation, stack) { 242 | const repoURL = this.getRepoURL(packageName) 243 | if (repoURL) { 244 | const title = `${deprecation.getOriginName()} is deprecated.` 245 | const stacktrace = stack.map(({functionName, location}) => `${functionName} (${location})`).join("\n") 246 | const body = `${deprecation.getMessage()}\n\`\`\`\n${stacktrace}\n\`\`\`` 247 | return `${repoURL}/issues/new?title=${encodeURI(title)}&body=${encodeURI(body)}` 248 | } else { 249 | return null 250 | } 251 | } 252 | 253 | async openIssueURL (repoURL, issueURL, issueTitle) { 254 | const issue = await this.findSimilarIssue(repoURL, issueTitle) 255 | if (issue) { 256 | shell.openExternal(issue.html_url) 257 | } else if (process.platform === 'win32') { 258 | // Windows will not launch URLs greater than ~2000 bytes so we need to shrink it 259 | shell.openExternal((await this.shortenURL(issueURL)) || issueURL) 260 | } else { 261 | shell.openExternal(issueURL) 262 | } 263 | } 264 | 265 | async findSimilarIssue (repoURL, issueTitle) { 266 | const url = 'https://api.github.com/search/issues' 267 | const repo = repoURL.replace(/http(s)?:\/\/(\d+\.)?github.com\//gi, '') 268 | const query = `${issueTitle} repo:${repo}` 269 | const response = await window.fetch(`${url}?q=${encodeURI(query)}&sort=created`, { 270 | method: 'GET', 271 | headers: { 272 | 'Accept': 'application/vnd.github.v3+json', 273 | 'Content-Type': 'application/json' 274 | } 275 | }) 276 | 277 | if (response.ok) { 278 | const data = await response.json() 279 | if (data.items) { 280 | const issues = {} 281 | for (const issue of data.items) { 282 | if (issue.title.includes(issueTitle) && !issues[issue.state]) { 283 | issues[issue.state] = issue 284 | } 285 | } 286 | 287 | return (issues.open || issues.closed) 288 | } 289 | } 290 | } 291 | 292 | async shortenURL (url) { 293 | let encodedUrl = encodeURIComponent(url).substr(0, 5000) // is.gd has 5000 char limit 294 | let incompletePercentEncoding = encodedUrl.indexOf('%', encodedUrl.length - 2) 295 | if (incompletePercentEncoding >= 0) { // Handle an incomplete % encoding cut-off 296 | encodedUrl = encodedUrl.substr(0, incompletePercentEncoding) 297 | } 298 | 299 | let result = await fetch('https://is.gd/create.php?format=simple', { 300 | method: 'POST', 301 | headers: {'Content-Type': 'application/x-www-form-urlencoded'}, 302 | body: `url=${encodedUrl}` 303 | }) 304 | 305 | return result.text() 306 | } 307 | 308 | getRepoURL (packageName) { 309 | const loadedPackage = atom.packages.getLoadedPackage(packageName) 310 | if (loadedPackage && loadedPackage.metadata && loadedPackage.metadata.repository) { 311 | const url = loadedPackage.metadata.repository.url || loadedPackage.metadata.repository 312 | return url.replace(/\.git$/, '') 313 | } else { 314 | return null 315 | } 316 | } 317 | 318 | getDeprecatedCallsByPackageName () { 319 | const deprecatedCalls = Grim.getDeprecations() 320 | deprecatedCalls.sort((a, b) => b.getCallCount() - a.getCallCount()) 321 | const deprecatedCallsByPackageName = {} 322 | for (const deprecation of deprecatedCalls) { 323 | const stacks = deprecation.getStacks() 324 | stacks.sort((a, b) => b.callCount - a.callCount) 325 | for (const stack of stacks) { 326 | let packageName = null 327 | if (stack.metadata && stack.metadata.packageName) { 328 | packageName = stack.metadata.packageName 329 | } else { 330 | packageName = (this.getPackageName(stack) || '').toLowerCase() 331 | } 332 | 333 | deprecatedCallsByPackageName[packageName] = deprecatedCallsByPackageName[packageName] || [] 334 | deprecatedCallsByPackageName[packageName].push({deprecation, stack}) 335 | } 336 | } 337 | return deprecatedCallsByPackageName 338 | } 339 | 340 | getDeprecatedSelectorsByPackageName () { 341 | const deprecatedSelectorsByPackageName = {} 342 | if (atom.styles.getDeprecations) { 343 | const deprecatedSelectorsBySourcePath = atom.styles.getDeprecations() 344 | for (const sourcePath of Object.keys(deprecatedSelectorsBySourcePath)) { 345 | const deprecation = deprecatedSelectorsBySourcePath[sourcePath] 346 | const components = sourcePath.split(path.sep) 347 | const packagesComponentIndex = components.indexOf('packages') 348 | let packageName = null 349 | let packagePath = null 350 | if (packagesComponentIndex === -1) { 351 | packageName = 'Other' // could be Atom Core or the personal style sheet 352 | packagePath = '' 353 | } else { 354 | packageName = components[packagesComponentIndex + 1] 355 | packagePath = components.slice(0, packagesComponentIndex + 1).join(path.sep) 356 | } 357 | 358 | deprecatedSelectorsByPackageName[packageName] = deprecatedSelectorsByPackageName[packageName] || [] 359 | deprecatedSelectorsByPackageName[packageName].push({packagePath, sourcePath, deprecation}) 360 | } 361 | } 362 | 363 | return deprecatedSelectorsByPackageName 364 | } 365 | 366 | getPackageName (stack) { 367 | const packagePaths = this.getPackagePathsByPackageName() 368 | for (const [packageName, packagePath] of packagePaths) { 369 | if (packagePath.includes('.atom/dev/packages') || packagePath.includes('.atom/packages')) { 370 | packagePaths.set(packageName, fs.absolute(packagePath)) 371 | } 372 | } 373 | 374 | for (let i = 1; i < stack.length; i++) { 375 | const {fileName} = stack[i] 376 | 377 | // Empty when it was run from the dev console 378 | if (!fileName) { 379 | return null 380 | } 381 | 382 | // Continue to next stack entry if call is in node_modules 383 | if (fileName.includes(`${path.sep}node_modules${path.sep}`)) { 384 | continue 385 | } 386 | 387 | for (const [packageName, packagePath] of packagePaths) { 388 | const relativePath = path.relative(packagePath, fileName) 389 | if (!/^\.\./.test(relativePath)) { 390 | return packageName 391 | } 392 | } 393 | 394 | if (atom.getUserInitScriptPath() === fileName) { 395 | return `Your local ${path.basename(fileName)} file` 396 | } 397 | } 398 | 399 | return null 400 | } 401 | 402 | getPackagePathsByPackageName () { 403 | if (this.packagePathsByPackageName) { 404 | return this.packagePathsByPackageName 405 | } else { 406 | this.packagePathsByPackageName = new Map() 407 | for (const pack of atom.packages.getLoadedPackages()) { 408 | this.packagePathsByPackageName.set(pack.name, pack.path) 409 | } 410 | return this.packagePathsByPackageName 411 | } 412 | } 413 | 414 | checkForUpdates () { 415 | atom.workspace.open('atom://config/updates') 416 | } 417 | 418 | disablePackage (packageName) { 419 | if (packageName) { 420 | atom.packages.disablePackage(packageName) 421 | } 422 | } 423 | 424 | openLocation (location) { 425 | let pathToOpen = location.replace('file://', '') 426 | if (process.platform === 'win32') { 427 | pathToOpen = pathToOpen.replace(/^\//, '') 428 | } 429 | atom.open({pathsToOpen: [pathToOpen]}) 430 | } 431 | 432 | getURI () { 433 | return this.uri 434 | } 435 | 436 | getTitle () { 437 | return 'Deprecation Cop' 438 | } 439 | 440 | getIconName () { 441 | return 'alert' 442 | } 443 | 444 | scrollUp () { 445 | this.element.scrollTop -= document.body.offsetHeight / 20 446 | } 447 | 448 | scrollDown () { 449 | this.element.scrollTop += document.body.offsetHeight / 20 450 | } 451 | 452 | pageUp () { 453 | this.element.scrollTop -= this.element.offsetHeight 454 | } 455 | 456 | pageDown () { 457 | this.element.scrollTop += this.element.offsetHeight 458 | } 459 | 460 | scrollToTop () { 461 | this.element.scrollTop = 0 462 | } 463 | 464 | scrollToBottom () { 465 | this.element.scrollTop = this.element.scrollHeight 466 | } 467 | } 468 | --------------------------------------------------------------------------------