I have a function where one of the arguments (b) should only be used in a specific use-case. I would like to give the developer a warning if the argument is used.
const myFn = (a: boolean, b: boolean) => {}
I wondered if you could define inline rules, something like this:
/*eslint b !== undefined: 1 "Don't use this argument unless....." */
const myFn = (a: boolean, b: boolean) => {}
Or how can I achieve this?
Custom ELint Rules - you cant do it as simply as you suggest (not inline), but check out this:
https://www.kenneth-truyers.net/2016/05/27/writing-custom-eslint-rules/#:~:text=In%20order%20to%20write%20a,must%20start%20with%20eslint%2Dplugin
Related
I'm working on a template literal type that'd only match title cased strings. I thought I'd first create a single word matcher using Uppercase<>:
const uppercaseWord: Uppercase<string> = 'U';
but it seems to match lowercase letters too:
const uppercaseWord: Uppercase<string> = 'u';
this one won't throw.
I've tried parametrizing it:
type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;
const upperCaseWord: SingleUpperCase = 'U';
But the type argument could not be inferred. It works by passing a string literal explicitly:
type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;
const upperCaseWord: SingleUpperCase<'u'> = 'U';
it works, but I'd need something more generic. Something that matches any uppercase string. If I tried again passing string as the type argument, the error is resolved, but the lowercase letter can be assigned without an error:
type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;
const upperCaseWord: SingleUpperCase<string> = 'u'; // no error
I wonder if this is even possible given it'd require a regex-like string matching from the compiler.
Aside: I'm not exactly sure what rules you have for "title case"; I don't know if "HTML String" is in title case or not. For what follows, I will assume that you just need to make sure that the first character of the string and the first character after every space (" ") is not lowercase. That means "HTML String" is fine. If you have a different rule, you can adjust the code in the answer below.
There is no specific type in TypeScript that represents title-cased strings. Template literal types don't give this to you; while Uppercase<string> and Capitalize<string> are now their own types as of TypeScript 4.8, implemented in microsoft/TypeScript#47050, they won't enforce handle the spacing requirements.
For a true title-case string type you would need, as you said, something like regular expression validated string types. There is an open issue at microsoft/TypeScript#41160 asking for use cases for such regex-validated types; if the solution below doesn't meet your needs, you might want to comment on that issue with your use case, why it is compelling, and why the alternative solutions don't suffice.
While there is no specific type that works here, you can write a recursive template literal type TitleCase<T> which can be used as a constraint on T. Meaning that T extends TitleCase<T> if and only if T is a title-cased string.
Then, in order to save people from having to annotate their strings with some generic type, you'd write a helper function like asTitleCase() which just returns its input, but produces a compiler error if you pass in a bad string.
So, while your ideal solution here would look like this:
/* THIS IS NOT POSSIBLE
const okay: TitleCase = "This Is Fine"; // okay
const error: TitleCase = "This is not fine"; // error
const alsoError: TitleCase = String(Math.random()); // error
*/
the implementable solution looks like this:
const okay = asTitleCase("This Is Fine"); // no error
const error = asTitleCase("This is not fine"); // error!
// ---------------------> ~~~~~~~~~~~~~~~~~~
// Argument of type '"This is not fine"' is not assignable to
// parameter of type '"This Is Not Fine"'.
const alsoError = asTitleCase(String(Math.random())); // error!
// Argument of type 'string' is not assignable to parameter of type
// '"Please Use a Title Cased String Literal Here, Thx"'
Again, this is what is implementable, not what is ideal. All uses of title-cased string types will need to gain an extra generic type parameter.
Note that you probably don't need to actually write asTitleCase(...) unless you want to see the error right at the declaration. Presumably you have some function (say, lookupBookTitle()) that cares about title case. If so, you'd just make that function generic and enforce the constraint there. So instead of const str = asTitleCase("XXX"); lookupBookTitle(str);, you'd just write const str = "XXX"; lookupBookTitle(str); The only difference is where the error shows up.
Also, inside the implementation of something like lookupBookTitle(), you should probably just widen the input to string and just treat it as if it's already been validated. Even though T extends TitleCase<T> has the effect of enforcing the constraint on callers, the compiler won't be able to follow the logic when T is an unspecified generic type parameter:
// callers see a function that constrains title to TitleCase
function lookupBookTitle<T extends string>(title: VerifyTitleCase<T>): Book;
// implementer just uses string
function lookupBookTitle(title: string) {
const book = db.lookupByTitle(title);
if (!book) throw new Error("NO BOOK");
return book;
}
Anyway, here's the implementation:
type TitleCase<T extends string, D extends string = " "> =
string extends T ? never :
T extends `${infer F}${D}${infer R}` ?
`${Capitalize<F>}${D}${TitleCase<R, D>}` : Capitalize<T>;
The type TitleCase<T, D> splits the string T at the delimiter D, and capitalizes (first character is uppercased) each piece. So it turns a string into a title-cased version of itself:
type X = TitleCase<"the quick brown fox jumps over the lazy dog.">
// type X = "The Quick Brown Fox Jumps Over The Lazy Dog."
Then we can write a VerifyTitleCase<T> type that checks if T extends TitleCase<T>. If so, it resolves to T. If not, it resolves either to TitleCase<T>, or some hard-coded error string that hopefully gives users an idea what went wrong. (There are no "throw types" or "Invalid types" in TypeScript, as requested in microsoft/TypeScript#23689; so using a hard-coded error string literal is a workaround):
type VerifyTitleCase<T extends string> = T extends TitleCase<T> ? T :
TitleCase<T> extends never ? "Please Use a Title Cased String Literal Here, Thx" :
TitleCase<T>
And finally, the helper function:
const asTitleCase = <T extends string>(s: VerifyTitleCase<T>) => s;
Playground link to code
The TypeScript team has been doing great work on string literal typing in recent updates (4.1 & 4.2). I am wondering if there is a way to type a fixed length string.
Ex.
type LambdaServicePrefix = 'my-application-service';
type LambdaFunctionIdentifier = 'dark-matter-upgrader';
type LambdaFunctionName = `${LambdaServicePrefix}-${LambdaFunctionIdentifier}`; // error: longer than 32 characters...
How I imagine it would go is something like, Array<64, string>;. TypeScript has the Tuple type so as an array I could fix length of an array. [string, ... string * 62, string].
type FutureLambdaIdType = `${LambdaServicePrefix}-${string[32]}`;
UPDATED to reflect better recursive conditional type support
There are still, as of TS 4.7, no regular-expression-validated string types in TypeScript. Template literal types handle some, but not all, of the use cases for such regex types. If you have a situation like this where template literal types are insufficient, you might want to go to microsoft/TypeScript#41160 and describe your use case. The idea of a "string whose maximum length is N characters" for some N extends number would be easy enough to express with regex types, but is not easily achievable with template literals.
Still, let's see how close we can get.
A major roadblock stands in the way. TypeScript cannot easily represent the set of all strings less than N characters as a specific type StringsOfLengthUpTo<N>. Conceptually any given StringsOfLengthUpTo<N> is a large union, but since the compiler balks at unions with more than ~10,000 members, you can only describe strings of up to a few characters this way. Assuming you want to support the 95 characters of 7-bit printable ASCII, you will be able to represent StringsOfLengthUpTo<0>, StringsOfLengthUpTo<1>, and even StringsOfLengthUpTo<2>. But StringsOfLengthUpTo<3> would exceed the compiler's capacity, since it would be a union of over 800,000 members. So we have to give up on specific types.
Instead we can think of our type as a constraint used with generics. We need a type like TruncateTo<T, N> which takes a type T extends string and an N extends number and returns T truncated to N characters. Then we can constrain T extends TruncateTo<T, N> and the compiler would automatically warn on too-long strings.
It used to be that shallow recursion limits would prevent us from writing TruncateTo<T, N> for N greater than about 20 or so, but TypeScript 4.5 introduced support for tail recursion elimination on conditional types. That means we can write TruncateTo<T, N> by adding some extra accumulator arguments like this:
type TruncateTo<T extends string, N extends number,
L extends any[] = [], A extends string = ""> =
N extends L['length'] ? A :
T extends `${infer F}${infer R}` ? (
TruncateTo<R, N, [0, ...L], `${A}${F}`>
) :
A
This works by having an A accumulator to store the string we're building up, and an L arraylike accumulator that keeps track of how long that A string is (string literal types don't have a strongly typed length property, see ms/TS#34692 for the relevant request). We build up A one character at a time until we either run out of the original string, or until we reach a length of N. Let's see it in action:
type Fifteen = TruncateTo<"12345678901234567890", 15>;
// type Fifteen = "123456789012345"
type TwentyFive = TruncateTo<"123456789012345678901234567", 25>;
// type TwentyFive = "1234567890123456789012345"
We can't directly write T extends TruncateTo<T, N> as TypeScript complains that this is a circular constraint. But we can at least write a helper function like this:
const atMostN = <T extends string, N extends number>(
num: N, str: T extends TruncateTo<T, N> ? T : TruncateTo<T, N>
) => str;
and then you could call atMostN(32, "someStringLiteral") and it would either succeed or warn based on the the length of the string literal argument. Note that the str input is of a weird conditional type, whose sole purpose is to avoid the circular constraint. T is inferred from str, and then checked against TruncateTo<T, N>. If it succeeds, great. Otherwise, we give str the type of TruncateTo<T, N>, and we'll see an error message. It works like this:
const okay = atMostN(32, "ThisStringIs28CharactersLong"); // okay
type Okay = typeof okay; // "ThisStringIs28CharactersLong"
const bad = atMostN(32, "ThisStringHasALengthOf34Characters"); // error!
// -------------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// '"ThisStringHasALengthOf34Characters"' is not assignable to parameter of type
// '"ThisStringHasALengthOf34Characte"'.
type Bad = typeof bad; // "ThisStringHasALengthOf34Characte"
Is it worth it? Maybe. The original answer here had to do some unsavory things to get even a fixed-length check. The current one isn't so bad, but it's still a bunch of effort to get a compile-time check. So you might still have a use case for regex-validated string types.
Playground link to code
There is no way to represent fixed-length strings with Typescript. There is a very upvoted proposal here, but still this feature has not been released.
If the length is very little, there are some workarounds suchs as the following:
type Char = 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'
type String3 = `${Char}${Char}${Char}`
const a: String3 = 'aa' // error
const b: String3 = 'bbbbb' // error
const c: String3 = 'ccc' // OK
const d: String3 = 'abc' // OK
But you can't handle big lengths since you will run into a "Expression produces a union type that is too complex to represent" error.
It is impossible to limit the length of string by typing or typescript utils.
You can, however, use regex to validate the string (including length):
/^([a-zA-Z0-9_-]){1,64}$/
type IsThirteen<T extends number> = 13 extends T ? true : never
type IsFifteen<T extends number> = 15 extends T ? true : never
type LengthOfString<S extends string, T extends string[] = []> = S extends `${string}${infer R}`
? LengthOfString<R, [...T, string]>
: T['length'];
type IsLengthThirteenOrFifteen<T extends string> = true extends IsThirteen<LengthOfString<T>>
? T
: true extends IsFifteen<LengthOfString<T>>
? T
: never
function IsLengthThirteenOrFifteenGuard <T extends string>(a: IsLengthThirteenOrFifteen<T>) {
return a;
}
const b = IsLengthThirteenOrFifteenGuard('1131111111111')
Sources:
StringOfLength
Playground
I have a function, validateCell, that takes a function, func, as one of its input parameters. It is as follows:
def validateCell[String](cellKey: String, cell: Option[String], func:(String) => Boolean): Tuple2[Boolean, String] = {
cell match {
case Some(cellContents) => (func(cellContents), s"$cellContents is not valid.")
case None => (false, s"$cellKey, not found.")
}
}
I call the function as follows:
val map = Map("Well" -> "A110")
validateCell("Well", map.get("Well"), BarcodeWell.isValidWell)
The function that is passed in this case is as follows, though I don't think it's related to the problem:
def isValidWell(w: String): Boolean = {
val row: String = w.replaceAll("[^A-Za-z]", "")
val col: Int = w.replaceAll("[^0-9]+", "").toInt
isValidRow(row) && isValidColumn(col)
}
I am expecting validateCell to return a Tuple(Boolean, String), but I get the following error:
Error:(5, 55) type mismatch;
found : java.lang.String
required: String(in method validateCell)
case Some(cellContents) => (func(cellContents), s"$cellContents is not valid.")
I can make this error go away by converting the java strings in each tuple that are returned by the case statements to Scala strings like so:
s"$cellContents is not valid.".asInstanceOf[String]
s"$cellKey, not found.".asInstanceOf[String]
This seems really silly. What am I missing here? Shouldn't this conversion be handled by Scala automatically and why are my strings being cast as Java strings in the first place?
There is no difference between Scala strings and Java strings. In fact, Predef.String aliases to java.lang.String. However, you're working with neither of these things; you're working with a type parameter.
def validateCell[String](cellKey: String, cell: Option[String], func:(String) => Boolean): Tuple2[Boolean, String] = {
This is a generic function which takes a type argument whose name is String. When you call validateCell, this type argument is being inferred and filled in for you, by something that definitely isn't a string. My guess is that you're misunderstanding the point of the brackets and that you meant to write
def validateCell(cellKey: String, cell: Option[String], func:(String) => Boolean): Tuple2[Boolean, String] = {
In Kotlin I declared a List<String> like this:
private val items = listOf<String> {
"String1",
"String2",
"String3"
}
the compiler is giving me this error:
Type Mismatch.
Required: String
Found: () -> String
What does it mean? How do I fix it?
P.S. Pretty new to Kotlin so bear with me for asking something obvious.
You passed the argument enclosed in {} which introduced a function literal (lambda) which is why the compiler finds a function type
Found: () -> String
Instead just use parentheses like this:
listOf("String1", "String2")
Some information:
Kotlin allows you to pass functions after the () when passed as the last argument. The parentheses can be left away if the function is the only argument as in your example. Therefore the code is valid but simply does not match the function parameter type.
Try this:
private val items = listOf("String1", "String2", "String3")
I had this problem trying to add headers to retrofit in Kotlin.
Java Code was:
#Headers({"Accept: application/json",
"userName: blah#test.com"
})
It turns out the solution was not listOf or arrayOf, but to just remove the {}
Kotlin Solution:
#Headers("Accept: application/json",
"userName: blah#test.com"
)
Reference: https://github.com/square/retrofit/issues/2518
I need to guarantee a string only contains allowed symbols. Now I do it this way:
def isCorrect(s: String, allowedChars: String): Boolean = {
s.distinct.foreach(c => {
if (!allowedChars.contains(c))
return false
})
true
}
Needless to say this does not look too pretty. Is there a better, more functional way to do this?
For the record, you can make this a little more generic by not limiting yourself to strings of characters, and a little more functional (in my view) by switching the order of arguments and using two argument lists. Here's how I'd write it:
def isCorrect[A](allowed: Set[A])(s: Seq[A]) = s forall allowed
Now you can treat this method as a function and "partially apply" it to create more specialized functions:
val isDigits = isCorrect("0123456789".toSet) _
val isAs = isCorrect(Set('A')) _
Which allows you to do the following:
scala> isDigits("218903")
res1: Boolean = true
scala> isAs("218903")
res2: Boolean = false
scala> isDigits("AAAAAAA")
res3: Boolean = false
scala> isAs("AAAAAAA")
res4: Boolean = true
Or you could still just use something like isCorrect("abcdr".toSet)("abracadabra").
def isCorrect(s:String, allowedChars:String):Boolean = {
s.forall{allowedChars.contains(_)}
}
Or, even more succinctly, following #ziggystar's suggestion:
def isCorrect(s: String, allowedChars: Seq[Char]) = s forall allowedChars.contains
I dont know if it is the most functional way to do it, but you can do this:
def isCorrect(s: String, allowedChars: String): Boolean = {
return s.distinct.forall(c => allowedChars.contains(c))
}
the distinct isnt really necessary.
This isn't more functional, but uses a regexp:
def isCorrect(s: String, allowedChars: String): Boolean =
s.matches ("^["+ allowedChars +"]*$")
Since regex are often optimized, I would consider this approach in performance critical code - not without testing, measuring, and maybe with precompiled patterns - if appropriate for the problem at hand.
As more functional flavoured I see Travis code.