├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── LICENSE
├── README.md
├── icon.png
├── package.json
├── src
├── auto-import.ts
├── extension.ts
├── helpers
│ ├── error-helper.ts
│ └── path-helper.ts
├── import-action.ts
├── import-completion.ts
├── import-db.ts
├── import-fixer.ts
├── import-scanner.ts
└── node-upload.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 | icon
4 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | {
3 | "version": "0.1.0",
4 | "configurations": [
5 | {
6 | "name": "Launch Extension",
7 | "type": "extensionHost",
8 | "request": "launch",
9 | "runtimeExecutable": "${execPath}",
10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
11 | "stopOnEntry": false,
12 | "sourceMaps": true,
13 | "outDir": "${workspaceRoot}/out/src",
14 | "preLaunchTask": "npm"
15 | },
16 | {
17 | "name": "Launch Tests",
18 | "type": "extensionHost",
19 | "request": "launch",
20 | "runtimeExecutable": "${execPath}",
21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
22 | "stopOnEntry": false,
23 | "sourceMaps": true,
24 | "outDir": "${workspaceRoot}/out/test",
25 | "preLaunchTask": "npm"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // Available variables which can be used inside of strings.
2 | // ${workspaceRoot}: the root folder of the team
3 | // ${file}: the current opened file
4 | // ${fileBasename}: the current opened file's basename
5 | // ${fileDirname}: the current opened file's dirname
6 | // ${fileExtname}: the current opened file's extension
7 | // ${cwd}: the current working directory of the spawned process
8 |
9 | // A task runner that calls a custom npm script that compiles the extension.
10 | {
11 | "version": "2.0.0",
12 |
13 | // we want to run npm
14 | "command": "npm",
15 |
16 | // the command is a shell script
17 | "isShellCommand": true,
18 |
19 | // show the output window only if unrecognized errors occur.
20 | "showOutput": "silent",
21 |
22 | // we run the custom script "compile" as defined in package.json
23 | "args": ["run", "compile", "--loglevel", "silent"],
24 |
25 | // The tsc compiler is started in watching mode
26 | "isWatching": true,
27 |
28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output.
29 | "problemMatcher": "$tsc-watch"
30 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 soates
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Auto Import
2 |
3 | Automatically finds, parses and provides code actions and code completion for all available imports. Works with Typescript and TSX.
4 |
5 | #### Multi-root workspace Ready!
6 |
7 | ----
8 |
9 |
10 |
11 | ----
12 |
13 | ## Configuration
14 |
15 | > filesToScan - Glob for which files in your workspace to scan, defaults to '**/*.{ts, tsx}'
16 |
17 | > showNotifications - Controls if the annoying notifications should be shown, defaults to false
18 |
19 | > doubleQuotes - Use double quotes rather than single
20 |
21 | > spaceBetweenBraces - Difference between import {test} and import { test }
22 |
23 | > autoComplete - Adds found items to intellisense and automatically imports then
24 |
25 | > useSemiColon - Use ; at the end of a line e.g Import * from ./app or Import * from ./app; - Default True
26 |
27 | ----
28 |
29 |
30 | ## Changelog
31 |
32 | ### 1.5.3
33 |
34 | - Finally merged long awaited pull requsts :)
35 | - Multi-root workspace ready!
36 |
37 | ### 1.5.2
38 |
39 | - Added support for Enum & Type imports.
40 | - Added ability to toggle semi colons - see setting useSemiColon.
41 | - Added [AI] before import statements - so you know if its come from Auto Import or Typescript.
42 |
43 | ### 1.2.2
44 |
45 | - Fix for imports not being merged.
46 |
47 | ### 1.2.1
48 |
49 | - Added optional auto completion for all known imports ( enabled by default ).
50 | - Improved scanning and seeking speed for large projects.
51 | - TSX Supported added, Thanks to [lukeautry](https://github.com/lukeautry "lukeautry")
52 | - Minor bug fixes and improvements.
53 |
54 | ### 1.0.2/1.0.3
55 |
56 | - Merged Pull Request from [lukeautry](https://github.com/lukeautry "lukeautry") and [zhaoshengjun](https://github.com/zhaoshengjun "zhaoshengjun") , Big thanks to both.
57 |
58 |
59 | ### 1.0.1
60 | 32swinnqchdncsrvqcnb4wzr2t3e5nopblvhsbhkky4sj2dhyp7a
61 | - Fixed breaking bug with vs 1.5.* and < TypeScript 2.0.0.
62 |
63 | ### 1.0
64 |
65 | - Few small tweaks and fixed error with vscode 1.5.*.
66 |
67 | ### 0.9
68 |
69 | - Added Import status bar, currently show you how many importable objects you have.
70 | - Correctly uses configured file paths for fileWatcher.
71 | - Fixed new exports not being immediately discovered.
72 | - CodeAction import paths are relative to the current file.
73 | - Typings are now excluded by default (along with node_modules and jspm_packages)
74 |
75 | ### 0.8.1
76 |
77 | - Fixed Windows paths issue
78 |
79 | ### 0.8
80 |
81 | - Nicer import paths.
82 | - Imports are now merged if they are from the same location.
83 | - Configuration for ' or ".
84 | - Works on Windows.
85 | - Now on Github.
86 |
87 | ### 0.7 / 0.7 / 0.7.2
88 |
89 | - Add configuration to control notifications and files to scan
90 | - Fixed a few bugs
91 | - Refactored code
92 |
93 | ### 0.6.0
94 |
95 | - Partial support for node_modules imports. AutoImport will scan your already used imports and provide them as suggestions when appropriate, so you will only need to type out your import once in one file and all other times will be handled by AutoImport. Version 0.7 will have full support for this.
96 |
97 | ### 0.5.1
98 | - General improvements, icon added and extension will now also watch for local file changes.
99 |
100 | ----
101 |
102 | ## Todo
103 |
104 | - Work with node_modules (@angular / underscore for example).
105 |
106 |
107 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/soates/Auto-Import/bb6f075f36d520e2560505276ef7064ec1c7172f/icon.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "autoimport",
3 | "displayName": "Auto Import",
4 | "description": "Automatically finds, parses and provides code actions and code completion for all available imports. Works with Typescript and TSX",
5 | "version": "1.5.3",
6 | "publisher": "steoates",
7 | "engines": {
8 | "vscode": "^1.17.0"
9 | },
10 | "galleryBanner": {
11 | "color": "#2d4794",
12 | "theme": "dark"
13 | },
14 | "keywords": [
15 | "typescript",
16 | "imports",
17 | "require",
18 | "auto import",
19 | "multi-root ready"
20 | ],
21 | "categories": [
22 | "Other"
23 | ],
24 | "activationEvents": [
25 | "onLanguage:typescript",
26 | "onLanguage:typescriptreact"
27 | ],
28 | "main": "./out/src/extension",
29 | "contributes": {
30 | "commands": [
31 | {
32 | "command": "extension.scanNodeModules",
33 | "title": "Scan node_modules for imports"
34 | }
35 | ],
36 | "configuration": {
37 | "type": "object",
38 | "title": "Auto Import configuration",
39 | "properties": {
40 | "autoimport.filesToScan": {
41 | "type": "string",
42 | "default": "**/*.{ts,tsx}",
43 | "description": "Glob for files to watch and scan, e.g ./src/** ./src/app/**/*.ts. Defaults to **/*.{ts,tsx}"
44 | },
45 | "autoimport.showNotifications": {
46 | "type": "boolean",
47 | "default": false,
48 | "description": "Specifies wether to show notifications from Auto Import"
49 | },
50 | "autoimport.doubleQuotes": {
51 | "type": "boolean",
52 | "default": false,
53 | "description": "Specifies wether to use double quotes"
54 | },
55 | "autoimport.spaceBetweenBraces": {
56 | "type": "boolean",
57 | "default": true,
58 | "description": "Specifies wether to use spaces between first and last brace"
59 | },
60 | "autoimport.autoComplete": {
61 | "type": "boolean",
62 | "default": true,
63 | "description": "Adds found items to intellisense and automatically imports then"
64 | },
65 | "autoimport.useSemiColon": {
66 | "type": "boolean",
67 | "default": true,
68 | "description": "Use ; at the end of a line e.g Import * from ./app or Import * from ./app; - Default True"
69 | }
70 | }
71 | }
72 | },
73 | "icon": "icon.png",
74 | "homepage": "https://github.com/soates/Auto-Import",
75 | "scripts": {
76 | "vscode:prepublish": "tsc -p ./",
77 | "compile": "tsc -watch -p ./",
78 | "postinstall": "node ./node_modules/vscode/bin/install",
79 | "test": "node ./node_modules/vscode/bin/test"
80 | },
81 | "devDependencies": {
82 | "typescript": "^2.0.3",
83 | "vscode": "^1.0.0",
84 | "@types/node": "^6.0.40"
85 | },
86 | "dependencies": {
87 | "lodash": "^4.13.1"
88 | }
89 | }
--------------------------------------------------------------------------------
/src/auto-import.ts:
--------------------------------------------------------------------------------
1 | import { NodeUpload } from './node-upload';
2 | import { ImportAction } from './import-action';
3 | import { ImportFixer } from './import-fixer';
4 | import { ImportScanner } from './import-scanner';
5 | import { ImportDb } from './import-db';
6 | import { ImportCompletion } from './import-completion';
7 |
8 | import * as vscode from 'vscode';
9 |
10 | export class AutoImport {
11 |
12 | public static statusBar;
13 |
14 | constructor(private context: vscode.ExtensionContext) { }
15 |
16 | public start(): boolean {
17 |
18 | let folder = vscode.workspace.rootPath;
19 |
20 | if (folder === undefined) {
21 | return false;
22 | }
23 |
24 | return true;
25 | }
26 |
27 | public attachCommands(): void {
28 |
29 | let codeActionFixer = vscode.languages.registerCodeActionsProvider('typescript', new ImportAction())
30 |
31 | let importScanner = vscode.commands.registerCommand('extension.importScan', (request: any) => {
32 |
33 | let scanner = new ImportScanner(vscode.workspace.getConfiguration('autoimport'))
34 |
35 | if (request.showOutput) {
36 | scanner.scan(request);
37 | } else if (request.edit) {
38 | scanner.edit(request);
39 | }
40 | else if (request.delete) {
41 | scanner.delete(request);
42 | }
43 | });
44 |
45 | let nodeScanner = vscode.commands.registerCommand('extension.scanNodeModules', () => {
46 | new NodeUpload(vscode.workspace.getConfiguration('autoimport')).scanNodeModules();
47 | });
48 |
49 | let importFixer = vscode.commands.registerCommand('extension.fixImport', (d, r, c, t, i) => {
50 | new ImportFixer().fix(d, r, c, t, i);
51 | });
52 |
53 | let completetion = vscode.languages.registerCompletionItemProvider('typescript', new ImportCompletion(this.context, vscode.workspace.getConfiguration('autoimport').get('autoComplete')), '');
54 |
55 | AutoImport.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1);
56 |
57 | AutoImport.statusBar.text = '{..} : Scanning.. ';
58 |
59 | AutoImport.statusBar.show();
60 |
61 | this.context.subscriptions.push(importScanner, importFixer, nodeScanner, codeActionFixer, AutoImport.statusBar, completetion);
62 | }
63 |
64 | public attachFileWatcher(): void {
65 |
66 | var multiWorkspace = vscode.workspace.workspaceFolders.length > 0;
67 |
68 | if (multiWorkspace === true) {
69 |
70 | vscode.workspace.workspaceFolders.forEach(workspace => {
71 |
72 | let glob = vscode.workspace.getConfiguration('autoimport').get('filesToScan');
73 |
74 | const relativePattern = new vscode.RelativePattern(workspace, glob);
75 |
76 | let watcher = vscode.workspace.createFileSystemWatcher(relativePattern);
77 |
78 | watcher.onDidChange((file: vscode.Uri) => {
79 | vscode.commands
80 | .executeCommand('extension.importScan', { workspace, file, edit: true });
81 | })
82 |
83 | watcher.onDidCreate((file: vscode.Uri) => {
84 | vscode.commands
85 | .executeCommand('extension.importScan', { workspace, file, edit: true });
86 | })
87 |
88 | watcher.onDidDelete((file: vscode.Uri) => {
89 | vscode.commands
90 | .executeCommand('extension.importScan', { workspace, file, delete: true });
91 | })
92 |
93 |
94 | });
95 |
96 | } else {
97 |
98 | let glob = vscode.workspace.getConfiguration('autoimport').get('filesToScan');
99 |
100 | let watcher = vscode.workspace.createFileSystemWatcher(glob);
101 |
102 | let workspace = undefined;
103 |
104 | watcher.onDidChange((file: vscode.Uri) => {
105 | vscode.commands
106 | .executeCommand('extension.importScan', { workspace, file, edit: true });
107 | })
108 |
109 | watcher.onDidCreate((file: vscode.Uri) => {
110 | vscode.commands
111 | .executeCommand('extension.importScan', { workspace, file, edit: true });
112 | })
113 |
114 | watcher.onDidDelete((file: vscode.Uri) => {
115 | vscode.commands
116 | .executeCommand('extension.importScan', { workspace, file, delete: true });
117 | })
118 | }
119 |
120 |
121 | }
122 |
123 | public scanIfRequired(): void {
124 |
125 | let settings = this.context.workspaceState.get('auto-import-settings')
126 |
127 | let firstRun = (settings === undefined || settings.firstRun);
128 |
129 | if (vscode.workspace.getConfiguration('autoimport').get('showNotifications')) {
130 | vscode.window
131 | .showInformationMessage('[AutoImport] Building cache');
132 | }
133 |
134 | var multiWorkspace = vscode.workspace.workspaceFolders.length > 0;
135 |
136 | if (multiWorkspace === true) {
137 |
138 | vscode.workspace.workspaceFolders.forEach(workspace => {
139 |
140 | vscode.commands
141 | .executeCommand('extension.importScan', { workspace, showOutput: true });
142 |
143 | });
144 | } else {
145 |
146 | vscode.commands
147 | .executeCommand('extension.importScan', { showOutput: true });
148 | }
149 |
150 |
151 | settings.firstRun = true;
152 |
153 | this.context.workspaceState.update('auto-import-settings', settings);
154 | }
155 |
156 | public static setStatusBar() {
157 | AutoImport.statusBar.text = `{..} : ${ImportDb.count}`;
158 | }
159 | }
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | import { ErrorHelper } from './helpers/error-helper';
4 | import { AutoImport } from './auto-import';
5 |
6 | export function activate(context: vscode.ExtensionContext) {
7 |
8 | try {
9 |
10 | if (context.workspaceState.get('auto-import-settings') === undefined) {
11 | context.workspaceState.update('auto-import-settings', {});
12 | }
13 |
14 | let extension = new AutoImport(context);
15 |
16 | let start = extension.start();
17 |
18 | if (!start) {
19 | return;
20 | }
21 |
22 | extension.attachCommands();
23 |
24 | extension.attachFileWatcher();
25 |
26 | extension.scanIfRequired();
27 |
28 |
29 | } catch (error) {
30 | ErrorHelper.handle(error);
31 | }
32 |
33 | }
34 |
35 | export function deactivate() {
36 |
37 | }
--------------------------------------------------------------------------------
/src/helpers/error-helper.ts:
--------------------------------------------------------------------------------
1 | export class ErrorHelper {
2 | public static handle(error: Error) {
3 | console.log(error);
4 | }
5 | }
--------------------------------------------------------------------------------
/src/helpers/path-helper.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | export class PathHelper {
4 |
5 | public static normalisePath(relativePath) {
6 | let removeFileExtenion = (rp) => {
7 | if (rp) {
8 | rp = rp.substring(0, rp.lastIndexOf('.'))
9 | }
10 | return rp;
11 | }
12 |
13 | let makeRelativePath = (rp) => {
14 |
15 | let preAppend = './';
16 |
17 | if (!rp.startsWith(preAppend) && !rp.startsWith('../')) {
18 | rp = preAppend + rp;
19 | }
20 |
21 | if (/^win/.test(process.platform)) {
22 | rp = rp.replace(/\\/g, '/');
23 | }
24 |
25 | return rp;
26 | }
27 |
28 | relativePath = makeRelativePath(relativePath);
29 | relativePath = removeFileExtenion(relativePath);
30 |
31 | return relativePath;
32 | }
33 |
34 | public static getRelativePath(a, b): string {
35 | return path.relative(path.dirname(a), b);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/import-action.ts:
--------------------------------------------------------------------------------
1 | import { PathHelper } from './helpers/path-helper';
2 | import * as vscode from 'vscode';
3 |
4 | import { ImportDb, ImportObject } from './import-db';
5 |
6 | export interface Context {
7 | document: vscode.TextDocument;
8 | range: vscode.Range;
9 | context: vscode.CodeActionContext;
10 | token: vscode.CancellationToken;
11 | imports?: Array
12 | }
13 |
14 | export class ImportAction {
15 |
16 |
17 | public provideCodeActions(document: vscode.TextDocument, range: vscode.Range,
18 | context: vscode.CodeActionContext, token: vscode.CancellationToken): vscode.Command[] {
19 |
20 | let actionContext = this.createContext(document, range, context, token);
21 |
22 | if (this.canHandleAction(actionContext)) {
23 | return this.actionHandler(actionContext);
24 | }
25 | }
26 |
27 | private canHandleAction(context: Context): boolean {
28 |
29 | let diagnostic: vscode.Diagnostic = context.context.diagnostics[0];
30 |
31 | if (!diagnostic) {
32 | return false;
33 | }
34 |
35 | if (diagnostic.message.startsWith('Typescript Cannot find name') || diagnostic.message.startsWith('Cannot find name')) {
36 | let imp = diagnostic.message.replace('Typescript Cannot find name', '')
37 | .replace('Cannot find name', '')
38 | .replace(/{|}|from|import|'|"| |\.|;/gi, '')
39 |
40 | try {
41 |
42 | let found = ImportDb.getImport(imp, context.document.uri);
43 |
44 | if (found) {
45 | context.imports = found;
46 | return true
47 | }
48 |
49 | } catch (exception) {
50 | return false;
51 | }
52 | }
53 |
54 | return false;
55 | }
56 |
57 | private actionHandler(context: Context): vscode.Command[] {
58 | let path = (imp: ImportObject) => {
59 | if ((imp.file).discovered) {
60 | return imp.file.fsPath;
61 | } else {
62 | let rp = PathHelper.normalisePath(
63 | PathHelper.getRelativePath(context.document.uri.fsPath, imp.file.fsPath));
64 | return rp;
65 | }
66 | };
67 |
68 | let handlers = [];
69 | context.imports.forEach(i => {
70 | handlers.push({
71 | title: `[AI] Import ${i.name} from ${path(i)}`,
72 | command: 'extension.fixImport',
73 | arguments: [context.document, context.range, context.context, context.token, context.imports]
74 | });
75 | });
76 |
77 | return handlers;
78 | }
79 |
80 | private createContext(document: vscode.TextDocument, range: vscode.Range,
81 | context: vscode.CodeActionContext, token: vscode.CancellationToken): Context {
82 | return {
83 | document, range, context, token
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/src/import-completion.ts:
--------------------------------------------------------------------------------
1 | import { PathHelper } from './helpers/path-helper';
2 | import { ImportDb, ImportObject } from './import-db';
3 | import { ImportFixer } from './import-fixer';
4 |
5 | import * as vscode from 'vscode';
6 | import { workspace } from 'vscode';
7 |
8 |
9 | export class ImportCompletion implements vscode.CompletionItemProvider {
10 |
11 | constructor(private context: vscode.ExtensionContext, private enabled: boolean) {
12 | let fixer = vscode.commands.registerCommand('extension.resolveImport', (args) => {
13 | new ImportFixer().fix(args.document, undefined, undefined, undefined, [args.imp]);
14 | });
15 |
16 | context.subscriptions.push(fixer);
17 | }
18 |
19 | public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position,
20 | token: vscode.CancellationToken): Promise {
21 |
22 | if (!this.enabled) {
23 | return Promise.resolve([]);
24 | }
25 |
26 | return new Promise((resolve, reject) => {
27 |
28 | let wordToComplete = '';
29 |
30 | let range = document.getWordRangeAtPosition(position);
31 |
32 | if (range) {
33 | wordToComplete = document.getText(new vscode.Range(range.start, position)).toLowerCase();
34 | }
35 |
36 | let workspace = vscode.workspace.getWorkspaceFolder(document.uri);
37 |
38 | let matcher = f => f.name.toLowerCase().indexOf(wordToComplete) > -1;
39 |
40 | if (workspace !== undefined) {
41 | matcher = f => f.name.toLowerCase().indexOf(wordToComplete) > -1 && f.workspace.name == workspace.name;
42 | }
43 |
44 | let found = ImportDb.all()
45 | .filter(matcher);
46 |
47 | return resolve(found.map(i => this.buildCompletionItem(i, document)));
48 | })
49 | }
50 |
51 |
52 | private buildCompletionItem(imp: ImportObject, document: vscode.TextDocument): any {
53 |
54 | let path = this.createDescription(imp, document);
55 |
56 | return {
57 | label: imp.name,
58 | kind: vscode.CompletionItemKind.Reference,
59 | detail: `[AI] import ${imp.name} (Auto-Import)`,
60 | documentation: `[AI] Import ${imp.name} from ${path}`,
61 | command: { title: 'AI: Autocomplete', command: 'extension.resolveImport', arguments: [{ imp, document }] }
62 | }
63 | }
64 |
65 | private createDescription(imp: ImportObject, document: vscode.TextDocument) {
66 | let path = (imp: ImportObject) => {
67 | if ((imp.file).discovered) {
68 | return imp.file.fsPath;
69 | } else {
70 | let rp = PathHelper.normalisePath(
71 | PathHelper.getRelativePath(document.uri.fsPath, imp.file.fsPath));
72 | return rp;
73 | }
74 | };
75 | return path(imp);
76 | }
77 | }
--------------------------------------------------------------------------------
/src/import-db.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as Path from 'path';
3 | import * as vscode from 'vscode';
4 |
5 | export interface ImportObject {
6 | name: string,
7 | file: vscode.Uri,
8 | workspace: vscode.WorkspaceFolder
9 | }
10 |
11 |
12 | export class ImportDb {
13 |
14 | private static imports: Array = new Array();
15 |
16 | public static get count() {
17 |
18 | return ImportDb.imports.length;
19 | }
20 |
21 | public static all(): Array {
22 | return ImportDb.imports;
23 | }
24 |
25 | public static getImport(name: string, doc: vscode.Uri): Array {
26 |
27 | let workspace = vscode.workspace.getWorkspaceFolder(doc);
28 |
29 | let matcher = (i: ImportObject) => i.name === name;
30 |
31 | if (workspace !== undefined) {
32 | matcher = (i: ImportObject) => i.name === name && i.workspace.name === workspace.name;
33 | }
34 |
35 | return ImportDb.imports.filter(matcher);
36 | }
37 |
38 | public static delete(request: any): void {
39 |
40 | try {
41 |
42 | let index = ImportDb.imports.findIndex(m => m.file.fsPath === request.file.fsPath);
43 |
44 | if (index !== -1) {
45 | ImportDb.imports.splice(index, 1);
46 | }
47 |
48 | } catch (error) {
49 |
50 | }
51 |
52 | }
53 |
54 | public static saveImport(name: string, data: any, file: any, workspace: vscode.WorkspaceFolder): void {
55 |
56 | name = name.trim();
57 |
58 | if (name === '' || name.length === 1) {
59 | return;
60 | }
61 |
62 |
63 | let obj: ImportObject = {
64 | name,
65 | file,
66 | workspace
67 | }
68 |
69 | let exists = ImportDb.imports.findIndex(m => m.name === obj.name && m.file.fsPath === file.fsPath);
70 |
71 | if (exists === -1) {
72 | ImportDb.imports.push(obj);
73 | }
74 |
75 | }
76 | }
--------------------------------------------------------------------------------
/src/import-fixer.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode'
2 | import * as path from 'path';
3 |
4 | import { ImportObject } from './import-db';
5 |
6 | export class ImportFixer {
7 |
8 | private spacesBetweenBraces;
9 | private doubleQuotes;
10 | private useSemiColon;
11 |
12 | constructor() {
13 | let config = vscode.workspace.getConfiguration('autoimport');
14 |
15 | this.useSemiColon = config.get('useSemiColon');
16 | this.spacesBetweenBraces = config.get('spaceBetweenBraces');
17 | this.doubleQuotes = config.get('doubleQuotes');
18 | }
19 |
20 | public fix(document: vscode.TextDocument, range: vscode.Range,
21 | context: vscode.CodeActionContext, token: vscode.CancellationToken, imports: Array): void {
22 |
23 | let edit = this.getTextEdit(document, imports);
24 |
25 | vscode.workspace.applyEdit(edit);
26 | }
27 |
28 | public getTextEdit(document: vscode.TextDocument, imports: Array) {
29 |
30 | let edit: vscode.WorkspaceEdit = new vscode.WorkspaceEdit();
31 | let importObj: vscode.Uri | any = imports[0].file;
32 | let importName: string = imports[0].name;
33 |
34 | let relativePath = this.normaliseRelativePath(importObj, this.getRelativePath(document, importObj));
35 |
36 | if (this.alreadyResolved(document, relativePath, importName)) {
37 | return edit;
38 | }
39 |
40 | if (this.shouldMergeImport(document, relativePath)) {
41 | edit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0),
42 | this.mergeImports(document, edit, importName, importObj, relativePath));
43 | } else {
44 | edit.insert(document.uri, new vscode.Position(0, 0),
45 | this.createImportStatement(imports[0].name, relativePath, true));
46 | }
47 |
48 | return edit;
49 | }
50 |
51 | private alreadyResolved(document: vscode.TextDocument, relativePath, importName) {
52 |
53 | let exp = new RegExp('(?:import\ \{)(?:.*)(?:\}\ from\ \')(?:' + relativePath + ')(?:\'\;)')
54 |
55 | let currentDoc = document.getText();
56 |
57 | let foundImport = currentDoc.match(exp)
58 |
59 | if (foundImport && foundImport.length > 0 && foundImport[0].indexOf(importName) > -1) {
60 | return true;
61 | }
62 |
63 | return false;
64 | }
65 |
66 | private shouldMergeImport(document: vscode.TextDocument, relativePath): boolean {
67 | let currentDoc = document.getText();
68 |
69 | let isCommentLine = (text: string): boolean => {
70 | let firstTwoLetters = text.trim().substr(0, 2);
71 | return firstTwoLetters === '//' || firstTwoLetters === '/*';
72 | }
73 |
74 | return currentDoc.indexOf(relativePath) !== -1 && !isCommentLine(currentDoc);
75 | }
76 |
77 | private mergeImports(document: vscode.TextDocument, edit: vscode.WorkspaceEdit, name, file, relativePath: string) {
78 |
79 | let exp = this.useSemiColon === true ?
80 | new RegExp('(?:import\ \{)(?:.*)(?:\}\ from\ \')(?:' + relativePath + ')(?:\'\;)') :
81 | new RegExp('(?:import\ \{)(?:.*)(?:\}\ from\ \')(?:' + relativePath + ')(?:\'\)')
82 |
83 | let currentDoc = document.getText();
84 |
85 | let foundImport = currentDoc.match(exp)
86 |
87 | if (foundImport) {
88 | let workingString = foundImport[0];
89 |
90 | let replaceTarget = this.useSemiColon === true ?
91 | /{|}|from|import|'|"| |;/gi :
92 | /{|}|from|import|'|"| |/gi;
93 |
94 | workingString = workingString
95 | .replace(replaceTarget, '').replace(relativePath, '');
96 |
97 | let importArray = workingString.split(',');
98 |
99 | importArray.push(name)
100 |
101 | let newImport = this.createImportStatement(importArray.join(', '), relativePath);
102 |
103 | currentDoc = currentDoc.replace(exp, newImport);
104 | }
105 |
106 | return currentDoc;
107 | }
108 |
109 | private createImportStatement(imp: string, path: string, endline: boolean = false): string {
110 |
111 | let formattedPath = path.replace(/\"/g, '')
112 | .replace(/\'/g, '');
113 |
114 | let returnStr = '';
115 |
116 | if ((this.doubleQuotes) && (this.spacesBetweenBraces)) {
117 | returnStr = `import { ${imp} } from "${formattedPath}";${endline ? '\r\n' : ''}`;
118 | } else if (this.doubleQuotes) {
119 | returnStr = `import {${imp}} from "${formattedPath}";${endline ? '\r\n' : ''}`;
120 | } else if (this.spacesBetweenBraces) {
121 | returnStr = `import { ${imp} } from '${formattedPath}';${endline ? '\r\n' : ''}`;
122 | } else {
123 | returnStr = `import {${imp}} from '${formattedPath}';${endline ? '\r\n' : ''}`;
124 | }
125 |
126 |
127 | if (this.useSemiColon === false) {
128 | returnStr = returnStr.replace(';', '');
129 | }
130 |
131 | return returnStr;
132 | }
133 |
134 | private getRelativePath(document, importObj: vscode.Uri | any): string {
135 | return importObj.discovered ? importObj.fsPath :
136 | path.relative(path.dirname(document.fileName), importObj.fsPath);
137 | }
138 |
139 | private normaliseRelativePath(importObj, relativePath: string): string {
140 |
141 | let removeFileExtenion = (rp) => {
142 | if (rp) {
143 | rp = rp.substring(0, rp.lastIndexOf('.'))
144 | }
145 | return rp;
146 | }
147 |
148 | let makeRelativePath = (rp) => {
149 |
150 | let preAppend = './';
151 |
152 | if (!rp.startsWith(preAppend)) {
153 | rp = preAppend + rp;
154 | }
155 |
156 | if (/^win/.test(process.platform)) {
157 | rp = rp.replace(/\\/g, '/');
158 | }
159 |
160 | return rp;
161 | }
162 |
163 | if (importObj.discovered === undefined) {
164 | relativePath = makeRelativePath(relativePath);
165 | relativePath = removeFileExtenion(relativePath);
166 | }
167 |
168 | return relativePath;
169 | }
170 | }
--------------------------------------------------------------------------------
/src/import-scanner.ts:
--------------------------------------------------------------------------------
1 | import { NodeUpload } from './node-upload';
2 | import * as FS from 'fs';
3 | import * as vscode from 'vscode';
4 | import * as _ from 'lodash';
5 |
6 | import { ImportDb } from './import-db';
7 | import { AutoImport } from './auto-import';
8 |
9 | export class ImportScanner {
10 |
11 | private scanStarted: Date;
12 |
13 | private scanEnded: Date;
14 |
15 | private showOutput: boolean;
16 |
17 | private filesToScan: string;
18 |
19 | private showNotifications: boolean;
20 |
21 | constructor(private config: vscode.WorkspaceConfiguration) {
22 | this.filesToScan = this.config.get('filesToScan');
23 | this.showNotifications = this.config.get('showNotifications');
24 | }
25 |
26 | public scan(request: any): void {
27 |
28 | this.showOutput = request.showOutput ? request.showOutput : false;
29 |
30 | if (this.showOutput) {
31 | this.scanStarted = new Date();
32 | }
33 |
34 | let scanLocation: any = this.filesToScan;
35 |
36 | if (request.workspace !== undefined) {
37 | scanLocation = new vscode.RelativePattern(request.workspace, scanLocation);
38 | }
39 |
40 | vscode.workspace
41 | .findFiles(scanLocation, '**/node_modules/**', 99999)
42 | .then((files) => this.processWorkspaceFiles(files));
43 |
44 | vscode.commands
45 | .executeCommand('extension.scanNodeModules');
46 |
47 | }
48 |
49 | public edit(request: any): void {
50 | ImportDb.delete(request);
51 | this.loadFile(request.file, request.workspace, true);
52 | new NodeUpload(vscode.workspace.getConfiguration('autoimport')).scanNodeModules();
53 |
54 | }
55 |
56 | public delete(request: any): void {
57 | ImportDb.delete(request);
58 | AutoImport.setStatusBar();
59 | }
60 |
61 |
62 | private processWorkspaceFiles(files: vscode.Uri[]): void {
63 |
64 | let pruned = files.filter((f) => {
65 | return f.fsPath.indexOf('typings') === -1 &&
66 | f.fsPath.indexOf('node_modules') === -1 &&
67 | f.fsPath.indexOf('jspm_packages') === -1;
68 | });
69 |
70 | pruned.forEach((f, i) => {
71 |
72 | let workspace: vscode.WorkspaceFolder
73 | = vscode.workspace.getWorkspaceFolder(f)
74 |
75 | this.loadFile(f, workspace, i === (pruned.length - 1));
76 |
77 |
78 | });
79 | }
80 |
81 | private loadFile(file: vscode.Uri, workspace: vscode.WorkspaceFolder, last: boolean): void {
82 |
83 | FS.readFile(file.fsPath, 'utf8', (err, data) => {
84 |
85 | if (err) {
86 | return console.log(err);
87 | }
88 |
89 | this.processFile(data, file, workspace);
90 |
91 | if (last) {
92 | AutoImport.setStatusBar();
93 | }
94 |
95 | if (last && this.showOutput && this.showNotifications) {
96 | this.scanEnded = new Date();
97 |
98 | let str = `[AutoImport] cache creation complete - (${Math.abs(this.scanStarted - this.scanEnded)}ms)`;
99 |
100 | vscode.window
101 | .showInformationMessage(str);
102 | }
103 |
104 | });
105 | }
106 |
107 | private processFile(data: any, file: vscode.Uri, workspace: vscode.WorkspaceFolder): void {
108 |
109 | var classMatches = data.match(/(export class) ([a-zA-z])\w+/g),
110 | interfaceMatches = data.match(/(export interface) ([a-zA-z])\w+/g),
111 | propertyMatches = data.match(/(export let) ([a-zA-z])\w+/g),
112 | varMatches = data.match(/(export var) ([a-zA-z])\w+/g),
113 | constMatches = data.match(/(export const) ([a-zA-z])\w+/g),
114 | enumMatches = data.match(/(export enum) ([a-zA-z])\w+/g),
115 | typeMatches = data.match(/(export type) ([a-zA-z])\w+/g)
116 |
117 | if (classMatches) {
118 | classMatches.forEach(m => {
119 | let workingFile: string =
120 | m.replace('export', '').replace('class', '');
121 |
122 | ImportDb.saveImport(workingFile, data, file, workspace);
123 | });
124 | }
125 |
126 | if (interfaceMatches) {
127 | interfaceMatches.forEach(m => {
128 | let workingFile: string =
129 | m.replace('export', '').replace('interface', '');
130 |
131 | ImportDb.saveImport(workingFile, data, file, workspace);
132 | });
133 | }
134 |
135 | if (propertyMatches || varMatches || constMatches || enumMatches || typeMatches) {
136 |
137 | [].concat(propertyMatches, varMatches, constMatches, enumMatches, typeMatches).filter(m => m).forEach(m => {
138 | let workingFile: string =
139 | m.replace('export', '').replace('let', '').replace('var', '').replace('const', '').replace('enum', '').replace('type', '');
140 |
141 | ImportDb.saveImport(workingFile, data, file, workspace);
142 | });
143 | }
144 | }
145 | }
--------------------------------------------------------------------------------
/src/node-upload.ts:
--------------------------------------------------------------------------------
1 | import { ImportDb } from './import-db';
2 | import * as FS from 'fs';
3 | import * as vscode from 'vscode';
4 | import * as _ from 'lodash';
5 |
6 | export class NodeUpload {
7 |
8 | private filesToScan: string;
9 |
10 | private useAutoImportNet: boolean;
11 |
12 |
13 | constructor(private config: vscode.WorkspaceConfiguration) {
14 | this.filesToScan = this.config.get('filesToScan');
15 | this.useAutoImportNet = this.config.get('useAutoImportNet');
16 | }
17 |
18 | public scanNodeModules() {
19 | this.getMappings().then((mappings) => {
20 |
21 | for (let key in mappings.mappings) {
22 | let map = mappings.mappings[key];
23 | if (map) {
24 | map.forEach(exp => {
25 | ImportDb.saveImport(exp, exp, { fsPath: key, discovered: true }, mappings.workspace)
26 | });
27 | }
28 | }
29 |
30 | });
31 | }
32 |
33 | public getMappings(): Promise {
34 | return new Promise((resolve) => {
35 |
36 | let mappings: any = {}
37 |
38 | let mapArrayToLocation = (exports, location) => {
39 | if (mappings[location]) {
40 | mappings[location] = (mappings[location]).concat(exports);
41 | } else {
42 | mappings[location] = exports;
43 | }
44 | };
45 |
46 | vscode.workspace.workspaceFolders.forEach(workspace => {
47 |
48 | let glob = vscode.workspace.getConfiguration('autoimport').get('filesToScan');
49 |
50 | const relativePattern = new vscode.RelativePattern(workspace, glob);
51 |
52 | vscode.workspace.findFiles(relativePattern, '**/node_modules/**', 99999).then((files) => {
53 | files.forEach((f, i) => {
54 | FS.readFile(f.fsPath, 'utf8', (err, data) => {
55 |
56 | if (err) {
57 | return console.log(err);
58 | }
59 |
60 | let matches = data.match(/\bimport\s+(?:.+\s+from\s+)?[\'"]([^"\']+)["\']/g);
61 |
62 | if (matches) {
63 | matches.forEach(m => {
64 | if (m.indexOf('./') === -1 && m.indexOf('!') === -1) {
65 | let exports = m.match(/\bimport\s+(?:.+\s+from\s+)/),
66 | location = m.match(/[\'"]([^"\']+)["\']/g);
67 |
68 | if (exports && location) {
69 | let exportArray = exports[0]
70 | .replace('import', '')
71 | .replace('{', '')
72 | .replace('}', '')
73 | .replace('from', '')
74 | .split(',')
75 | .map(e => {
76 | e = e.replace(/\s/g, ''); return e;
77 | })
78 |
79 | mapArrayToLocation(exportArray, location[0].replace("'", '')
80 | .replace("'", ""));
81 | }
82 | }
83 | });
84 | }
85 |
86 | if (i == (files.length - 1)) {
87 | for (let key in mappings) {
88 | if (mappings.hasOwnProperty(key)) {
89 | mappings[key] = _.uniq(mappings[key]);
90 | }
91 | }
92 | return resolve({ mappings, workspace });
93 | }
94 | });
95 | });
96 | });
97 |
98 | });
99 |
100 |
101 |
102 | });
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "out",
6 | "lib": [
7 | "es6"
8 | ],
9 | "sourceMap": true,
10 | "rootDir": "."
11 | },
12 | "exclude": [
13 | "node_modules",
14 | ".vscode-test"
15 | ]
16 | }
--------------------------------------------------------------------------------