blob: 64ca5abdb5210800f7c9da3ada2fca51daf52cfd [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:core';
import 'package:analysis_server/src/protocol_server.dart'
show CompletionSuggestion, CompletionSuggestionKind;
import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:path/path.dart' show posix;
import 'package:path/src/context.dart';
/**
* A contributor for calculating uri suggestions for import and part directives.
*/
class UriContributor extends DartCompletionContributor {
/**
* A flag indicating whether file: and package: URI suggestions should
* be included in the list of completion suggestions.
*/
// TODO(danrubel): remove this flag and related functionality
// once the UriContributor limits file: and package: URI suggestions
// to only those paths within context roots.
static bool suggestFilePaths = true;
_UriSuggestionBuilder builder;
@override
Future<List<CompletionSuggestion>> computeSuggestions(
DartCompletionRequest request) async {
// TODO(brianwilkerson) Determine whether this await is necessary.
await null;
builder = new _UriSuggestionBuilder(request);
request.target.containingNode.accept(builder);
return builder.suggestions;
}
}
class _UriSuggestionBuilder extends SimpleAstVisitor {
final DartCompletionRequest request;
final List<CompletionSuggestion> suggestions = <CompletionSuggestion>[];
_UriSuggestionBuilder(this.request);
@override
visitExportDirective(ExportDirective node) {
visitNamespaceDirective(node);
}
@override
visitImportDirective(ImportDirective node) {
visitNamespaceDirective(node);
}
visitNamespaceDirective(NamespaceDirective node) {
StringLiteral uri = node.uri;
if (uri is SimpleStringLiteral) {
int offset = request.offset;
int start = uri.offset;
int end = uri.end;
if (offset > start) {
if (offset < end) {
// Quoted non-empty string
visitSimpleStringLiteral(uri);
} else if (offset == end) {
if (end == start + 1) {
// Quoted empty string
visitSimpleStringLiteral(uri);
} else {
String data = request.sourceContents;
if (end == data.length) {
String ch = data[end - 1];
if (ch != '"' && ch != "'") {
// Insertion point at end of file
// and missing closing quote on non-empty string
visitSimpleStringLiteral(uri);
}
}
}
}
} else if (offset == start && offset == end) {
String data = request.sourceContents;
if (end == data.length) {
String ch = data[end - 1];
if (ch == '"' || ch == "'") {
// Insertion point at end of file
// and missing closing quote on empty string
visitSimpleStringLiteral(uri);
}
}
}
}
}
@override
visitSimpleStringLiteral(SimpleStringLiteral node) {
AstNode parent = node.parent;
if (parent is NamespaceDirective && parent.uri == node) {
String partialUri = _extractPartialUri(node);
if (partialUri != null) {
_addDartSuggestions();
if (UriContributor.suggestFilePaths) {
_addPackageSuggestions(partialUri);
_addFileSuggestions(partialUri);
}
}
} else if (parent is PartDirective && parent.uri == node) {
String partialUri = _extractPartialUri(node);
if (partialUri != null) {
if (UriContributor.suggestFilePaths) {
_addFileSuggestions(partialUri);
}
}
}
}
void _addDartSuggestions() {
_addSuggestion('dart:');
SourceFactory factory = request.sourceFactory;
for (SdkLibrary lib in factory.dartSdk.sdkLibraries) {
if (!lib.isInternal && !lib.isImplementation) {
if (!lib.shortName.startsWith('dart:_')) {
_addSuggestion(lib.shortName,
relevance: lib.shortName == 'dart:core'
? DART_RELEVANCE_LOW
: DART_RELEVANCE_DEFAULT);
}
}
}
}
void _addFileSuggestions(String partialUri) {
ResourceProvider resProvider = request.resourceProvider;
Context resContext = resProvider.pathContext;
Source source = request.source;
String parentUri;
if ((partialUri.endsWith('/'))) {
parentUri = partialUri;
} else {
parentUri = posix.dirname(partialUri);
if (parentUri != '.' && !parentUri.endsWith('/')) {
parentUri = '$parentUri/';
}
}
String uriPrefix = parentUri == '.' ? '' : parentUri;
// Only handle file uris in the format file:///xxx or /xxx
String parentUriScheme = Uri.parse(parentUri).scheme;
if (!parentUri.startsWith('file://') && parentUriScheme != '') {
return;
}
String dirPath = resProvider.pathContext.fromUri(parentUri);
dirPath = resContext.normalize(dirPath);
if (resContext.isRelative(dirPath)) {
String sourceDirPath = resContext.dirname(source.fullName);
if (resContext.isAbsolute(sourceDirPath)) {
dirPath = resContext.normalize(resContext.join(sourceDirPath, dirPath));
} else {
return;
}
// Do not suggest relative paths reaching outside the 'lib' directory.
bool srcInLib = resContext.split(sourceDirPath).contains('lib');
bool dstInLib = resContext.split(dirPath).contains('lib');
if (srcInLib && !dstInLib) {
return;
}
}
if (dirPath.endsWith('\\.')) {
dirPath = dirPath.substring(0, dirPath.length - 1);
}
Resource dir = resProvider.getResource(dirPath);
if (dir is Folder) {
try {
for (Resource child in dir.getChildren()) {
String completion;
if (child is Folder) {
if (!child.shortName.startsWith('.')) {
completion = '$uriPrefix${child.shortName}/';
}
} else if (child is File) {
if (child.shortName.endsWith('.dart')) {
completion = '$uriPrefix${child.shortName}';
}
}
if (completion != null && completion != source.shortName) {
_addSuggestion(completion);
}
}
} on FileSystemException {
// Guard against I/O exceptions.
}
}
}
void _addPackageFolderSuggestions(
String partial, String prefix, Folder folder) {
try {
for (Resource child in folder.getChildren()) {
if (child is Folder) {
String childPrefix = '$prefix${child.shortName}/';
_addSuggestion(childPrefix);
if (partial.startsWith(childPrefix)) {
_addPackageFolderSuggestions(partial, childPrefix, child);
}
} else {
_addSuggestion('$prefix${child.shortName}');
}
}
} on FileSystemException {
// Guard against I/O exceptions.
return;
}
}
void _addPackageSuggestions(String partial) {
SourceFactory factory = request.sourceFactory;
Map<String, List<Folder>> packageMap = factory.packageMap;
if (packageMap != null) {
_addSuggestion('package:');
packageMap.forEach((String pkgName, List<Folder> folders) {
String prefix = 'package:$pkgName/';
_addSuggestion(prefix);
for (Folder folder in folders) {
if (folder.exists) {
_addPackageFolderSuggestions(partial, prefix, folder);
}
}
});
}
}
void _addSuggestion(String completion,
{int relevance = DART_RELEVANCE_DEFAULT}) {
suggestions.add(new CompletionSuggestion(CompletionSuggestionKind.IMPORT,
relevance, completion, completion.length, 0, false, false));
}
String _extractPartialUri(SimpleStringLiteral node) {
if (request.offset < node.contentsOffset) {
return null;
}
return node.literal.lexeme.substring(
node.contentsOffset - node.offset, request.offset - node.offset);
}
}