Skip to content

Commit

Permalink
Fix mistakes with description and code
Browse files Browse the repository at this point in the history
  • Loading branch information
hyperupcall committed Sep 29, 2023
1 parent 268cf61 commit b3b73f1
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 77 deletions.
88 changes: 21 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,113 +12,67 @@ ECMAScript proposal, specification, and reference implementation for `String.pro

## Motivation

The lack of a built-in way to remove a prefix substring or a suffix substring from a string is a great inconvenience and incompleteness of JavaScript. Fixing this paper-cut will bring JavaScript more in line with the conveniences of other modern-day languages.
The lack of a built-in way to remove a prefix substring or a suffix substring from a string is an inconvenience and incompleteness of JavaScript. Fixing this paper-cut will bring JavaScript more in line with the conveniences of other modern-day languages.

Current alternatives to a native method are too slow, verbose, and hard-to-read.

Commonly, [`.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) used:
Commonly, [`.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice) is used, but it's both verbose and not semantic.

```js
// removePrefix
let str = 'greninja'
str.slice('gren'.length - 1)
// => ninja
let substr = 'gre'
if (str.startsWith(substr)) {
str.slice(substr.length)
// => ninja
}


// removeSuffix
let str = 'charizard'
str.slice(0, str.length - 'izard'.length)
// => char
let substr = 'izard'
if (str.endsWith(substr)) {
str.slice(0, str.length - substr.length)
// => char
}
```

This approach is difficult to read (especially when nested/repeated) and not semantic.

Sometimes, regular expressions are used:
Sometimes, regular expressions are used, but it has suboptimal performance ([jsPerf](https://jsperf.app/haxumu/2)) and requires knowledge of regular expressions.

```js
let str = 'mudkip'
str.replace(/kip$/, '')
// => mud
```

This isn't performant [jsPerf](https://jsperf.app/haxumu) and requires knowledge of regular expressions.

## Proposed Solution

We propose adding `String.prototype.removePrefix` and `String.prototype.removeSuffix` to the String prototype.

```js
'greninja'.removePrefix('gren')
'greninja'.removePrefix('gre')
// => ninja

'charizard'.removeSuffix('izard')
// => char
```

Fixing this paper-cut will bring JavaScript more in line with the conveniences of other modern-day languages.

## High-level API

The proposed signature is:
The proposed signatures are:

```js
String.prototype.removePrefix(prefix: string): string
```

```js
String.prototype.removeSuffix(suffix: string): string
```

## Naming
See [polyfill.js](./polyfill.js) for details.

## Comparison to other languages

- Ruby (since v2.5) has [`.delete_prefix`](https://ruby-doc.org/current/String.html#method-i-delete_prefix) and [`.delete_suffix`](https://ruby-doc.org/current/String.html#method-i-delete_suffix)
- Python (since v3.9) has [`.removeprefix`](https://docs.python.org/3/library/stdtypes.html#str.removeprefix) and [`.removesuffix`](https://docs.python.org/3/library/stdtypes.html#str.removesuffix)
- Ruby (since v2.5) has [`.delete_prefix`](https://ruby-doc.org/current/String.html#method-i-delete_prefix) and [`.delete_suffix`](https://ruby-doc.org/current/String.html#method-i-delete_suffix)
- Rust (since v1.45.0) has [`.strip_prefix`](https://doc.rust-lang.org/std/string/struct.String.html#method.strip_prefix) and [`.strip_suffix`](https://doc.rust-lang.org/std/string/struct.String.html#method.strip_suffix)
- Go (since v1.1) has [`strings.TrimPrefix`](https://pkg.go.dev/strings#TrimPrefix) and [`strings.TrimSuffix`](https://pkg.go.dev/strings#TrimSuffix)

## Credit

[proposal-string-replaceall](https://github.com/tc39/proposal-string-replaceall) and [proposal-string-pad-start-end](https://github.com/tc39/proposal-string-pad-start-end).

## Before creating a proposal

Please ensure the following:
1. You have read the [process document](https://tc39.github.io/process-document/)
1. You have reviewed the [existing proposals](https://github.com/tc39/proposals/)
1. You are aware that your proposal requires being a member of TC39, or locating a TC39 delegate to “champion” your proposal

## Create your proposal repo

Follow these steps:
1. Go to your repo settings page:
1. Under the “Pages” section on the left sidebar, and set the source to “deploy from a branch” and check “Enforce HTTPS”
1. Under the “Actions” section on the left sidebar, under “General”, select “Read and write permissions” under “Workflow permissions” and click “Save”
1. [“How to write a good explainer”][explainer] explains how to make a good first impression.

> Each TC39 proposal should have a `README.md` file which explains the purpose
> of the proposal and its shape at a high level.
>
> ...
>
> The rest of this page can be used as a template ...
Your explainer can point readers to the `index.html` generated from `spec.emu`
via markdown like

```markdown
You can browse the [ecmarkup output](https://ACCOUNT.github.io/PROJECT/)
or browse the [source](https://github.com/ACCOUNT/PROJECT/blob/HEAD/spec.emu).
```

where *ACCOUNT* and *PROJECT* are the first two path elements in your project's Github URL.
For example, for github.com/**tc39**/**template-for-proposals**, *ACCOUNT* is “tc39”
and *PROJECT* is “template-for-proposals”.


## Maintain your proposal repo

1. Make your changes to `spec.emu` (ecmarkup uses HTML syntax, but is not HTML, so I strongly suggest not naming it “.html”)
1. Any commit that makes meaningful changes to the spec, should run `npm run build` to verify that the build will succeed and the output looks as expected.
1. Whenever you update `ecmarkup`, run `npm run build` to verify that the build will succeed and the output looks as expected.
## Naming

[explainer]: https://github.com/tc39/how-we-work/blob/HEAD/explainer.md
The naming parallels Python's `.remove{prefix,suffix}` function. A quick peruse of [Mootools](https://mootools.net) and [Sugar](https://sugarjs.com/docs/#/String) revealed no obvious naming collisions.
17 changes: 13 additions & 4 deletions polyfill.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
'use strict'
if (!String.prototype.removePrefix) {
String.prototype.removePrefix = function removePrefix() {

if (!String.prototype.removePrefix) {
String.prototype.removePrefix = function removePrefix(/** @type {string} */ substr) {
if (this.startsWith(substr)) {
return this.slice(substr.length)
}
return this
}
}

if (!String.prototype.removeSuffix) {
String.prototype.removeSuffix = function removeSuffix() {

String.prototype.removeSuffix = function removeSuffix(/** @type {string} */ substr) {
if (this.endsWith(substr)) {
return this.slice(0, this.length - substr.length)
}
return this
}
}

console.log('greninja'.removePrefix('gre'))
console.log('charizard'.removeSuffix('izard'))
60 changes: 54 additions & 6 deletions spec.emu
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,48 @@ contributors: Edwin Kofler
<emu-clause id="sec-startswith" type="abstract operation">
<h1>
StartsWith (
_argument_: an ECMAScript language value,
_str_: an ECMAScript language value,
)
</h1>
<dl class="header">
<dt>description</dt>
<dd>It converts _argument_ to an integer representing its Number value with fractional part truncated, or to +∞ or -∞ when that Number value is infinite.</dd>
<dd>It returns _str_.</dd>
</dl>
<emu-alg>
1. Return _argument_.
1. Return _str_.
</emu-alg>
</emu-clause>

<emu-clause id="sec-endswith" type="abstract operation">
<h1>
EndsWith (
_str_: an ECMAScript language value,
)
</h1>
<dl class="header">
<dt>description</dt>
<dd>It returns _str_.</dd>
</dl>
<emu-alg>
1. Return _str_.
</emu-alg>
</emu-clause>

<emu-clause id="sec-slice" type="abstract operation">
<h1>
Slice (
_start_: an ECMAScript language value,
_end_: an ECMAScript language value,
)
</h1>
<dl class="header">
<dt>description</dt>
<dd>It returns _str_. If _start_ is undefined.</dd>
</dl>
<emu-alg>
<!-- TODO: optional not working -->
1. If _start_ is *undefined*, Return _str_.
1. Else, Return _str_.
</emu-alg>
</emu-clause>

Expand All @@ -31,9 +64,24 @@ contributors: Edwin Kofler
1. Let _O_ be ? RequireObjectCoercible(*this* value).
1. Let _S_ be ? ToString(_O_).
1. Let _isPrefix_ be ? StartsWith(_S_).
1. If _isPrefix_ is *false*, return _S_.
1. Let _len_ be the length of _S_.
1. Let _substring_ be ? Slice(0, _len_).
1. Return _substring_.
</emu-alg>
</emu-clause>

<emu-clause id="String.prototype.removeSuffix">
<h1>String.prototype.removeSuffix ( _prefix_ )</h1>
<p>When the `removeSuffix` method is called, the following steps are taken:</p>
<emu-alg>
1. Let _O_ be ? RequireObjectCoercible(*this* value).
1. Let _S_ be ? ToString(_O_).
1. Let _isSuffix_ be ? EndsWith(_S_).
1. If _isSuffix_ is *false*, return _S_.
1. Let _len_ be the length of _S_.
1. Let _substring_ be the *substring* of _S_ from 0 to _len_.
1. If _isPrefix_ is *true*, return _substring_.
1. Return _S_.
1. Let _prefixlen_ be the length of _prefix_.
1. Let _substring_ be ? Slice(0, _len_ - _prefixlen_).
1. Return _substring_.
</emu-alg>
</emu-clause>

0 comments on commit b3b73f1

Please sign in to comment.