Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
- Add transformSchema and related utilities for easily transforming schemas
[#1251](https://github.com/apollographql/apollo-tooling/pull/1251)
- `apollo-language-server`
- Fix windows file paths by normalizing all URIs to a consistent format [#1213](https://github.com/apollographql/apollo-tooling/pull/1213).
- Fix positionToOffset to consider windows line endings [#1213](https://github.com/apollographql/apollo-tooling/pull/1213).
- Extend Engine API for federated schema uploads and checks [#1251](https://github.com/apollographql/apollo-tooling/pull/1251)
- Reorganize files and exports [#1251](https://github.com/apollographql/apollo-tooling/pull/1251)
- `apollo-tools`
Expand Down
15 changes: 9 additions & 6 deletions packages/apollo-language-server/src/fileSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import minimatch = require("minimatch");
import glob from "glob";
import { invariant } from "@apollographql/apollo-tools";
import URI from "vscode-uri";
import { normalizeURI } from "./utilities";

export class FileSet {
private rootURI: URI;
Expand Down Expand Up @@ -30,18 +31,20 @@ export class FileSet {
}

includesFile(filePath: string): boolean {
return this.allFiles().includes(filePath);
return this.allFiles().includes(normalizeURI(filePath));
}

allFiles(): string[] {
// since glob.sync takes a single pattern, but we allow an array of `includes`, we can join all the
// `includes` globs into a single pattern and pass to glob.sync. The `ignore` option does, however, allow
// an array of globs to ignore, so we can pass it in directly
const joinedIncludes = `{${this.includes.join(",")}}`;
return glob.sync(joinedIncludes, {
cwd: this.rootURI.fsPath,
absolute: true,
ignore: this.excludes
});
return glob
.sync(joinedIncludes, {
cwd: this.rootURI.fsPath,
absolute: true,
ignore: this.excludes
})
.map(normalizeURI);
}
}
2 changes: 1 addition & 1 deletion packages/apollo-language-server/src/project/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export abstract class GraphQLProject implements GraphQLSchemaProvider {
}

includesFile(uri: DocumentUri) {
return this.fileSet.includesFile(URI.parse(uri).fsPath);
return this.fileSet.includesFile(uri);
}

async scanAllIncludedFiles() {
Expand Down
55 changes: 55 additions & 0 deletions packages/apollo-language-server/src/utilities/__tests__/uri.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { normalizeURI } from "../uri";

describe("Unix URIs", () => {
// this is the format that `glob` returns on unix
const uriToMatchForUnix = "/test/myFile.js";

// single forward slash (unix)
it("handles /Users/me URIs", () => {
const uri = "/test/myFile.js";
const parsed = normalizeURI(uri);
expect(parsed).toEqual(uriToMatchForUnix);
});

// single escaped backslash
// treat these as forward slashes?
it("handles \\Users\\me URIs", () => {
const uri = "\\test\\myFile.js";
const parsed = normalizeURI(uri);
expect(parsed).toEqual(uriToMatchForUnix);
});
});

describe("Windows URIs", () => {
// this is the format that `glob` returns for windows
const uriToMatchForWindows = "c:/test/myFile.js";

// this format is sent by the native extension notification system on windows
it("handles file:///c%3A/ URIs", () => {
const uri = "file:///c%3A/test/myFile.js";
const parsed = normalizeURI(uri);
expect(parsed).toEqual(uriToMatchForWindows);
});

// same as above without URI encoded :
it("handles handles file:///c:/ URIs", () => {
const uri = "file:///c:/test/myFile.js";
const parsed = normalizeURI(uri);
expect(parsed).toEqual(uriToMatchForWindows);
});

// result of glob.sync
it("handles c:/ URIs", () => {
const uri = "c:/test/myFile.js";
const parsed = normalizeURI(uri);
expect(parsed).toEqual(uriToMatchForWindows);
});

// from status bar notification
// single (escaped) backslash
it("handles c:\\ URIs", () => {
const uri = "c:\\test\\myFile.js";
const parsed = normalizeURI(uri);
expect(parsed).toEqual(uriToMatchForWindows);
});
});
1 change: 1 addition & 0 deletions packages/apollo-language-server/src/utilities/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./debouncer";
export * from "./uri";
3 changes: 2 additions & 1 deletion packages/apollo-language-server/src/utilities/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export function positionFromSourceLocation(

export function positionToOffset(source: Source, position: Position): number {
const lineRegexp = /\r\n|[\n\r]/g;
const lineEndingLength = /\r\n/g.test(source.body) ? 2 : 1;

const linesUntilPosition = source.body
.split(lineRegexp)
Expand All @@ -132,7 +133,7 @@ export function positionToOffset(source: Source, position: Position): number {
position.character +
linesUntilPosition
.map(
line => line.length + 1 // count EOL
line => line.length + lineEndingLength // count EOL
)
.reduce((a, b) => a + b, 0)
);
Expand Down
19 changes: 19 additions & 0 deletions packages/apollo-language-server/src/utilities/uri.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import URI from "vscode-uri";

const withUnixSeparator = (uriString: string) =>
uriString.split(/[\/\\]/).join("/");

export const normalizeURI = (uriString: string) => {
let parsed;
if (uriString.indexOf("file:///") === 0) {
parsed = URI.file(URI.parse(uriString).fsPath);
} else if (uriString.match(/^[a-zA-Z]:[\/\\].*/)) {
// uri with a drive prefix but not file:///
parsed = URI.file(
URI.parse("file:///" + withUnixSeparator(uriString)).fsPath
);
} else {
parsed = URI.parse(withUnixSeparator(uriString));
}
return withUnixSeparator(parsed.fsPath);
};