OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 import 'dart:async'; | |
6 import 'dart:core'; | |
7 | |
8 import 'package:analysis_server/src/protocol_server.dart' hide Element; | |
9 import 'package:analysis_server/src/services/correction/status.dart'; | |
10 import 'package:analysis_server/src/services/refactoring/refactoring.dart'; | |
11 import 'package:analysis_server/src/services/refactoring/refactoring_internal.da
rt'; | |
12 import 'package:analysis_server/src/services/search/search_engine.dart'; | |
13 import 'package:analyzer/dart/element/element.dart'; | |
14 import 'package:analyzer/file_system/file_system.dart'; | |
15 import 'package:analyzer/src/generated/engine.dart'; | |
16 import 'package:analyzer/src/generated/source.dart'; | |
17 import 'package:path/path.dart' as pathos; | |
18 import 'package:source_span/src/span.dart'; | |
19 import 'package:yaml/yaml.dart'; | |
20 | |
21 /** | |
22 * [ExtractLocalRefactoring] implementation. | |
23 */ | |
24 class MoveFileRefactoringImpl extends RefactoringImpl | |
25 implements MoveFileRefactoring { | |
26 final ResourceProvider resourceProvider; | |
27 final pathos.Context pathContext; | |
28 final SearchEngine searchEngine; | |
29 final AnalysisContext context; | |
30 final Source source; | |
31 | |
32 String oldFile; | |
33 String newFile; | |
34 | |
35 SourceChange change; | |
36 LibraryElement library; | |
37 String oldLibraryDir; | |
38 String newLibraryDir; | |
39 | |
40 MoveFileRefactoringImpl(ResourceProvider resourceProvider, this.searchEngine, | |
41 this.context, this.source, this.oldFile) | |
42 : resourceProvider = resourceProvider, | |
43 pathContext = resourceProvider.pathContext { | |
44 if (source != null) { | |
45 oldFile = source.fullName; | |
46 } | |
47 } | |
48 | |
49 @override | |
50 String get refactoringName => 'Move File'; | |
51 | |
52 @override | |
53 Future<RefactoringStatus> checkFinalConditions() { | |
54 RefactoringStatus result = new RefactoringStatus(); | |
55 return new Future.value(result); | |
56 } | |
57 | |
58 @override | |
59 Future<RefactoringStatus> checkInitialConditions() { | |
60 RefactoringStatus result = new RefactoringStatus(); | |
61 return new Future.value(result); | |
62 } | |
63 | |
64 @override | |
65 Future<SourceChange> createChange() async { | |
66 // move file | |
67 if (source != null) { | |
68 return _createFileChange(); | |
69 } | |
70 // rename project | |
71 if (oldFile != null) { | |
72 Resource projectFolder = resourceProvider.getResource(oldFile); | |
73 if (projectFolder is Folder && projectFolder.exists) { | |
74 Resource pubspecFile = projectFolder.getChild('pubspec.yaml'); | |
75 if (pubspecFile is File && pubspecFile.exists) { | |
76 return _createProjectChange(projectFolder, pubspecFile); | |
77 } | |
78 } | |
79 } | |
80 // no change | |
81 return null; | |
82 } | |
83 | |
84 @override | |
85 bool requiresPreview() => false; | |
86 | |
87 /** | |
88 * Computes the URI to use to reference [newFile] from [reference]. | |
89 */ | |
90 String _computeNewUri(SourceReference reference) { | |
91 String refDir = pathContext.dirname(reference.file); | |
92 // try to keep package: URI | |
93 if (_isPackageReference(reference)) { | |
94 Source newSource = new NonExistingSource( | |
95 newFile, pathos.toUri(newFile), UriKind.FILE_URI); | |
96 Uri restoredUri = context.sourceFactory.restoreUri(newSource); | |
97 if (restoredUri != null) { | |
98 return restoredUri.toString(); | |
99 } | |
100 } | |
101 // if no package: URI, prepare relative | |
102 return _getRelativeUri(newFile, refDir); | |
103 } | |
104 | |
105 Future<SourceChange> _createFileChange() async { | |
106 change = new SourceChange('Update File References'); | |
107 List<Source> librarySources = context.getLibrariesContaining(source); | |
108 await Future.forEach(librarySources, (Source librarySource) async { | |
109 CompilationUnitElement unitElement = | |
110 context.getCompilationUnitElement(source, librarySource); | |
111 if (unitElement != null) { | |
112 // if a defining unit, update outgoing references | |
113 library = unitElement.library; | |
114 if (library.definingCompilationUnit == unitElement) { | |
115 oldLibraryDir = pathContext.dirname(oldFile); | |
116 newLibraryDir = pathContext.dirname(newFile); | |
117 _updateUriReferences(library.imports); | |
118 _updateUriReferences(library.exports); | |
119 _updateUriReferences(library.parts); | |
120 } | |
121 // update reference to the unit | |
122 List<SearchMatch> matches = | |
123 await searchEngine.searchReferences(unitElement); | |
124 List<SourceReference> references = getSourceReferences(matches); | |
125 for (SourceReference reference in references) { | |
126 String newUri = _computeNewUri(reference); | |
127 reference.addEdit(change, "'$newUri'"); | |
128 } | |
129 } | |
130 }); | |
131 return change; | |
132 } | |
133 | |
134 Future<SourceChange> _createProjectChange( | |
135 Folder project, File pubspecFile) async { | |
136 change = new SourceChange('Rename project'); | |
137 String oldPackageName = pathContext.basename(oldFile); | |
138 String newPackageName = pathContext.basename(newFile); | |
139 // add pubspec.yaml change | |
140 { | |
141 // prepare "name" field value location | |
142 SourceSpan nameSpan; | |
143 { | |
144 String pubspecString = pubspecFile.readAsStringSync(); | |
145 YamlMap pubspecNode = loadYamlNode(pubspecString); | |
146 YamlNode nameNode = pubspecNode.nodes['name']; | |
147 nameSpan = nameNode.span; | |
148 } | |
149 int nameOffset = nameSpan.start.offset; | |
150 int nameLength = nameSpan.length; | |
151 // add edit | |
152 change.addEdit(pubspecFile.path, pubspecFile.modificationStamp, | |
153 new SourceEdit(nameOffset, nameLength, newPackageName)); | |
154 } | |
155 // check all local libraries | |
156 for (Source librarySource in context.librarySources) { | |
157 // should be a local library | |
158 if (!project.contains(librarySource.fullName)) { | |
159 continue; | |
160 } | |
161 // we need LibraryElement | |
162 LibraryElement library = context.getLibraryElement(librarySource); | |
163 if (library == null) { | |
164 continue; | |
165 } | |
166 // update all imports | |
167 updateUriElements(List<UriReferencedElement> uriElements) { | |
168 for (UriReferencedElement element in uriElements) { | |
169 String uri = element.uri; | |
170 if (uri != null) { | |
171 String oldPrefix = 'package:$oldPackageName/'; | |
172 if (uri.startsWith(oldPrefix)) { | |
173 doSourceChange_addElementEdit( | |
174 change, | |
175 library, | |
176 new SourceEdit(element.uriOffset + 1, oldPrefix.length, | |
177 'package:$newPackageName/')); | |
178 } | |
179 } | |
180 } | |
181 } | |
182 | |
183 updateUriElements(library.imports); | |
184 updateUriElements(library.exports); | |
185 } | |
186 // done | |
187 return change; | |
188 } | |
189 | |
190 String _getRelativeUri(String path, String from) { | |
191 String uri = pathContext.relative(path, from: from); | |
192 List<String> parts = pathContext.split(uri); | |
193 return pathos.posix.joinAll(parts); | |
194 } | |
195 | |
196 bool _isPackageReference(SourceReference reference) { | |
197 Source source = reference.element.source; | |
198 int offset = reference.range.offset + "'".length; | |
199 String content = context.getContents(source).data; | |
200 return content.startsWith('package:', offset); | |
201 } | |
202 | |
203 /** | |
204 * Checks if the given [path] represents a relative URI. | |
205 * | |
206 * The following URI's are not relative: | |
207 * `/absolute/path/file.dart` | |
208 * `dart:math` | |
209 */ | |
210 bool _isRelativeUri(String path) { | |
211 // absolute URI | |
212 if (Uri.parse(path).isAbsolute) { | |
213 return false; | |
214 } | |
215 // absolute path | |
216 if (pathContext.isAbsolute(path)) { | |
217 return false; | |
218 } | |
219 // OK | |
220 return true; | |
221 } | |
222 | |
223 void _updateUriReference(UriReferencedElement element) { | |
224 if (!element.isSynthetic) { | |
225 String elementUri = element.uri; | |
226 if (_isRelativeUri(elementUri)) { | |
227 String elementPath = pathContext.join(oldLibraryDir, elementUri); | |
228 String newUri = _getRelativeUri(elementPath, newLibraryDir); | |
229 int uriOffset = element.uriOffset; | |
230 int uriLength = element.uriEnd - uriOffset; | |
231 doSourceChange_addElementEdit( | |
232 change, library, new SourceEdit(uriOffset, uriLength, "'$newUri'")); | |
233 } | |
234 } | |
235 } | |
236 | |
237 void _updateUriReferences(List<UriReferencedElement> elements) { | |
238 for (UriReferencedElement element in elements) { | |
239 _updateUriReference(element); | |
240 } | |
241 } | |
242 } | |
OLD | NEW |