Skip to content

Commit

Permalink
feat(cli): add --language=experimental-default
Browse files Browse the repository at this point in the history
Allow users to more easily opt into TypeScript support by adding
--language=experimental-default. This option will eventually become
the default, but because TypeScript support is experimental, it needs
to be opt-in.
  • Loading branch information
strager committed Oct 19, 2023
1 parent 44f3f16 commit ad94377
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 5 deletions.
2 changes: 2 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Semantic Versioning.
* Emacs: The Debian/Ubuntu package now installs the Emacs plugin. Manual
installation of the .el files is no longer required.
* TypeScript support (still experimental):
* CLI: The new `--language=experimental-default` option auto-detects
the language based on `.ts`, `.tsx`, and `.d.ts` in the file path.
* Class method overload signatures are now parsed.
* [E0398][] is now reported when using both `abstract` and `static` on a
single class property.
Expand Down
7 changes: 7 additions & 0 deletions docs/cli.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ Added in quick-lint-js version 0.3.0.
** *.js*: *javascript-jsx*
** *.jsx*: *javascript-jsx*
** (anything else): *javascript-jsx*
* *experimental-default*: infer the _languageid_ from the file's extension (EXPERIMENTAL; subject to change in future versions of quick-lint-js):
** *.js*: *javascript-jsx*
** *.jsx*: *javascript-jsx*
** *.d.ts*: *experimental-typescript-definition*
** *.ts*: *experimental-typescript*
** *.tsx*: *experimental-typescript-jsx*
** (anything else): *javascript-jsx*
* *javascript*: the latest ECMAScript standard with proposed features.
* *javascript-jsx*: like *javascript* but with JSX (React) extensions.
* *experimental-typescript*: the latest TypeScript version.
Expand Down
16 changes: 15 additions & 1 deletion src/quick-lint-js/cli/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <quick-lint-js/container/string-view.h>
#include <quick-lint-js/io/output-stream.h>
#include <quick-lint-js/port/warning.h>
#include <quick-lint-js/util/classify-path.h>
#include <quick-lint-js/util/integer.h>
#include <quick-lint-js/util/narrow-cast.h>
#include <string_view>
Expand Down Expand Up @@ -115,6 +116,8 @@ Options parse_options(int argc, char** argv) {
unused_language_option = arg_value;
if (arg_value == "default"sv) {
language = Raw_Input_File_Language::default_;
} else if (arg_value == "experimental-default"sv) {
language = Raw_Input_File_Language::experimental_default;
} else if (arg_value == "javascript"sv) {
language = Raw_Input_File_Language::javascript;
} else if (arg_value == "javascript-jsx"sv) {
Expand Down Expand Up @@ -264,9 +267,20 @@ Resolved_Input_File_Language File_To_Lint::get_language() const {

Resolved_Input_File_Language get_language(const char* file,
Raw_Input_File_Language language) {
static_cast<void>(file); // Unused for now.
if (language == Raw_Input_File_Language::default_) {
return Resolved_Input_File_Language::javascript_jsx;
} else if (language == Raw_Input_File_Language::experimental_default) {
Path_Classification classification = classify_path(file);
if (classification.typescript_jsx) {
return Resolved_Input_File_Language::typescript_jsx;
} else if (classification.typescript) {
if (classification.typescript_definition) {
return Resolved_Input_File_Language::typescript_definition;
}
return Resolved_Input_File_Language::typescript;
} else {
return Resolved_Input_File_Language::javascript_jsx;
}
} else {
return static_cast<Resolved_Input_File_Language>(language);
}
Expand Down
7 changes: 6 additions & 1 deletion src/quick-lint-js/cli/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ enum class Output_Format {
};

enum class Raw_Input_File_Language : unsigned char {
default_, // Explicit (--language=default) or implicit (no --language).
// Explicit (--language=default) or implicit (no --language).
default_,
// Explicit --language=experimental-default.
// TODO(#690): Make experimental_default the default and delete default_.
experimental_default,

javascript,
javascript_jsx,
typescript,
Expand Down
4 changes: 4 additions & 0 deletions src/quick-lint-js/io/file-path.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include <quick-lint-js/port/have.h>
#include <quick-lint-js/util/cpp.h>
#include <string>

#if defined(_WIN32)
Expand All @@ -14,6 +15,9 @@
#define QLJS_ALL_PATH_DIRECTORY_SEPARATORS "/"
#endif

#define QLJS_ALL_PATH_DIRECTORY_SEPARATORS_SV \
QLJS_CPP_CONCAT(QLJS_ALL_PATH_DIRECTORY_SEPARATORS, sv)

namespace quick_lint_js {
std::string parent_path(std::string&&);

Expand Down
24 changes: 21 additions & 3 deletions src/quick-lint-js/util/classify-path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,36 @@
// See end of file for extended copyright information.

#include <cstddef>
#include <quick-lint-js/assert.h>
#include <quick-lint-js/container/string-view.h>
#include <quick-lint-js/io/file-path.h>
#include <quick-lint-js/port/char8.h>
#include <quick-lint-js/util/classify-path.h>
#include <quick-lint-js/util/uri.h>
#include <string_view>

namespace quick_lint_js {
Path_Classification classify_uri(String8_View uri) {
// FIXME(strager): Should this unescape % encoding?
String8_View base_name = uri_base_name(uri);
return classify_file_base_name(uri_base_name(uri));
}

Path_Classification classify_path(String8_View path) {
String8_View base_name =
to_string8_view(path_file_name(to_string_view(path)));
return classify_file_base_name(base_name);
}

Path_Classification classify_path(const char *path) {
return classify_path(to_string8_view(std::string_view(path)));
}

Path_Classification classify_file_base_name(String8_View name) {
QLJS_SLOW_ASSERT(name.find(u8'/') == name.npos);
return Path_Classification{
.typescript_definition = base_name.find(u8".d."_sv) != base_name.npos,
.typescript_jsx = ends_with(base_name, u8".tsx"_sv),
.typescript_definition = name.find(u8".d."_sv) != name.npos,
.typescript = ends_with(name, u8".ts"_sv),
.typescript_jsx = ends_with(name, u8".tsx"_sv),
};
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/quick-lint-js/util/classify-path.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,20 @@ struct Path_Classification {
// https://github.com/microsoft/TypeScript/blob/daa7e985f5adc972aa241e5b0761c7dc433e94bf/src/compiler/parser.ts#L10408
bool typescript_definition;

// True if the path's base name ends with '.ts', including '.d.ts'. False
// otherwise, for example if the path's base name ends with '.js' or '.tsx'.
bool typescript;

// True if the path's base name ends with '.tsx'.
bool typescript_jsx;
};

Path_Classification classify_uri(String8_View uri);
Path_Classification classify_path(String8_View path);
Path_Classification classify_path(const char* path);

// Precondition: name has no directory components.
Path_Classification classify_file_base_name(String8_View name);
}

// quick-lint-js finds bugs in JavaScript programs.
Expand Down
44 changes: 44 additions & 0 deletions test/test-options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
#include <initializer_list>
#include <iostream>
#include <quick-lint-js/cli/options.h>
#include <quick-lint-js/container/concat.h>
#include <quick-lint-js/diag/diag-code-list.h>
#include <quick-lint-js/diag/diagnostic-types.h>
#include <quick-lint-js/io/file-path.h>
#include <quick-lint-js/io/output-stream.h>
#include <quick-lint-js/util/narrow-cast.h>
#include <string_view>
Expand Down Expand Up @@ -365,6 +367,20 @@ TEST(Test_Options, language) {
EXPECT_EQ(o.files_to_lint[3].language, default_language) << "--stdin";
}

{
Options o = parse_options_no_errors({"--language=default", "file.js"});
ASSERT_EQ(o.files_to_lint.size(), 1);
EXPECT_EQ(o.files_to_lint[0].language, Raw_Input_File_Language::default_);
}

{
Options o =
parse_options_no_errors({"--language=experimental-default", "file.js"});
ASSERT_EQ(o.files_to_lint.size(), 1);
EXPECT_EQ(o.files_to_lint[0].language,
Raw_Input_File_Language::experimental_default);
}

{
Options o = parse_options_no_errors(
{"--language=javascript", "one.js", "two.ts", "three.txt"});
Expand Down Expand Up @@ -474,10 +490,38 @@ TEST(Test_Options, default_language_is_javascript_jsx_regardless_of_extension) {
EXPECT_EQ(get_language("hi.jsx", default_language), javascript_jsx);
EXPECT_EQ(get_language("hi.ts", default_language), javascript_jsx);
EXPECT_EQ(get_language("hi.d.ts", default_language), javascript_jsx);
EXPECT_EQ(get_language("hi.d.js", default_language), javascript_jsx);
EXPECT_EQ(get_language("hi.tsx", default_language), javascript_jsx);
EXPECT_EQ(get_language("hi.txt", default_language), javascript_jsx);
}

TEST(Test_Options,
experimental_default_language_guesses_language_from_extension) {
constexpr auto default_language =
Raw_Input_File_Language::experimental_default;
constexpr auto javascript_jsx = Resolved_Input_File_Language::javascript_jsx;
EXPECT_EQ(get_language("<stdin>", default_language), javascript_jsx);
EXPECT_EQ(get_language("hi.js", default_language), javascript_jsx);
EXPECT_EQ(get_language("hi.jsx", default_language), javascript_jsx);
EXPECT_EQ(get_language("hi.ts", default_language),
Resolved_Input_File_Language::typescript);
EXPECT_EQ(get_language("hi.d.ts", default_language),
Resolved_Input_File_Language::typescript_definition);
EXPECT_EQ(get_language("hi.d.js", default_language), javascript_jsx);
EXPECT_EQ(get_language("hi.tsx", default_language),
Resolved_Input_File_Language::typescript_jsx);
EXPECT_EQ(get_language("hi.txt", default_language), javascript_jsx);

for (char separator : QLJS_ALL_PATH_DIRECTORY_SEPARATORS_SV) {
std::string path =
concat("foo.d.ts"sv, std::string_view(&separator, 1), "bar.ts"sv);
SCOPED_TRACE(path);
EXPECT_EQ(get_language(path.c_str(), default_language),
Resolved_Input_File_Language::typescript)
<< ".d.ts in containing folder should be ignored";
}
}

TEST(Test_Options, get_language_overwritten) {
constexpr auto in_javascript = Raw_Input_File_Language::javascript;
constexpr auto in_javascript_jsx = Raw_Input_File_Language::javascript_jsx;
Expand Down

0 comments on commit ad94377

Please sign in to comment.